Skip to content

Commit

Permalink
Merge pull request #31 from AugustNagro/nullable-uuid
Browse files Browse the repository at this point in the history
support optional uuid columns
  • Loading branch information
AugustNagro authored Jun 27, 2024
2 parents 2822529 + 90d1768 commit 0d39f44
Show file tree
Hide file tree
Showing 13 changed files with 311 additions and 171 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,21 @@ case class Address(
) derives DbCodec
```

#### UUID DbCodec doesn't work for my database

Some databases directly support the UUID type; these include Postgres, Clickhouse, and H2. When using the built-in `DbCodec[UUID]`, defined in `DbCodec.scala`, serialization and deserialization of `java.util.UUID` will work as expected.

Other databases like MySql, Oracle, and Sqlite, however, do not natively support UUID columns. Users have to choose an alternate datatype to store the UUID: most commonly `varchar(36)` or `binary(16)`. The JDBC drivers for these databases do not support direct serialization and deserialization of `java.util.UUID`, therefore the default `DbCodec[UUID]` will not be sufficient. Instead, import the appropriate codec from `com.augustnagro.magnum.UUIDCodec`. For example,

```scala
import com.augustnagro.magnum.*
import com.augustnagro.magnum.UUIDCodec.VarCharUUIDCodec
import java.util.UUID

@Table(MySqlDbType)
case class Person(@Id id: Long, name: String, tracking_id: Option[UUID]) derives DbCodec
```

## Todo
* JSON / XML support
* Support MSSql
Expand Down
2 changes: 1 addition & 1 deletion magnum/src/main/scala/com/augustnagro/magnum/DbCodec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ object DbCodec:

given UUIDCodec: DbCodec[UUID] with
def queryRepr: String = "?"
val cols: IArray[Int] = IArray(Types.JAVA_OBJECT)
val cols: IArray[Int] = IArray(Types.OTHER)
def readSingle(rs: ResultSet, pos: Int): UUID =
rs.getObject(pos, classOf[UUID])
def writeSingle(entity: UUID, ps: PreparedStatement, pos: Int): Unit =
Expand Down
15 changes: 15 additions & 0 deletions magnum/src/main/scala/com/augustnagro/magnum/UUIDCodec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.augustnagro.magnum

import java.sql.{PreparedStatement, ResultSet, Types}
import java.util.UUID

object UUIDCodec:
given VarCharUUIDCodec: DbCodec[UUID] with
def queryRepr: String = "?"
val cols: IArray[Int] = IArray(Types.VARCHAR)
def readSingle(rs: ResultSet, pos: Int): UUID =
rs.getString(pos) match
case null => null
case uuidStr => UUID.fromString(uuidStr)
def writeSingle(entity: UUID, ps: PreparedStatement, pos: Int): Unit =
ps.setString(pos, entity.toString)
27 changes: 18 additions & 9 deletions magnum/src/test/resources/clickhouse-person.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ create table person (
first_name Nullable(String),
last_name String not null,
is_admin Bool not null,
created DateTime not null default now()
created DateTime not null default now(),
social_id Nullable(UUID)
)
engine = MergeTree()
order by created;
Expand All @@ -16,54 +17,62 @@ insert into person values
'George',
'Washington',
true,
toDateTime('2023-03-05 02:26:00')
toDateTime('2023-03-05 02:26:00'),
toUUID('d06443a6-3efb-46c4-a66a-a80a8a9a5388')
),
(
toUUID('12970806-606d-42ff-bb9c-3187bbd360dd'),
'Alexander',
'Hamilton',
true,
toDateTime('2023-03-05 02:27:00')
toDateTime('2023-03-05 02:27:00'),
toUUID('529b6c6d-7228-4da5-81d7-13b706f78ddb')
),
(
toUUID('834a2bd2-6842-424f-97e0-fe5ed02c3653'),
'John',
'Adams',
true,
toDateTime('2023-03-05 02:28:00')
toDateTime('2023-03-05 02:28:00'),
null
),
(
toUUID('60492bb2-fe02-4d02-9c6d-ae03fa6f2243'),
'Benjamin',
'Franklin',
true,
toDateTime('2023-03-05 02:29:00')
toDateTime('2023-03-05 02:29:00'),
null
),
(
toUUID('2244eef4-b581-4305-824f-efe8f70e6bb7'),
'John',
'Jay',
true,
toDateTime('2023-03-05 02:30:00')
toDateTime('2023-03-05 02:30:00'),
null
),
(
toUUID('fb3c479a-0521-4f06-b6ad-218db867518c'),
'Thomas',
'Jefferson',
true,
toDateTime('2023-03-05 02:31:00')
toDateTime('2023-03-05 02:31:00'),
null
),
(
toUUID('8fefe1d8-20eb-44a0-84da-d328334e1e11'),
'James',
'Madison',
true,
toDateTime('2023-03-05 02:32:00')
toDateTime('2023-03-05 02:32:00'),
null
),
(
toUUID('8a2d842a-0d03-463c-8c34-43b38120f9e4'),
null,
'Nagro',
false,
toDateTime('2023-03-05 02:33:00')
toDateTime('2023-03-05 02:33:00'),
null
);
21 changes: 11 additions & 10 deletions magnum/src/test/resources/h2-person.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ create table person (
first_name varchar(50),
last_name varchar(50) not null,
is_admin boolean not null,
created timestamp default current_timestamp
created timestamp default current_timestamp,
social_id UUID
);

insert into person (first_name, last_name, is_admin, created) values
('George', 'Washington', true, now()),
('Alexander', 'Hamilton', true, now()),
('John', 'Adams', true, now()),
('Benjamin', 'Franklin', true, now()),
('John', 'Jay', true, now()),
('Thomas', 'Jefferson', true, now()),
('James', 'Madison', true, now()),
(null, 'Nagro', false, now());
insert into person (first_name, last_name, is_admin, created, social_id) values
('George', 'Washington', true, now(), 'd06443a6-3efb-46c4-a66a-a80a8a9a5388'),
('Alexander', 'Hamilton', true, now(), '529b6c6d-7228-4da5-81d7-13b706f78ddb'),
('John', 'Adams', true, now(), null),
('Benjamin', 'Franklin', true, now(), null),
('John', 'Jay', true, now(), null),
('Thomas', 'Jefferson', true, now(), null),
('James', 'Madison', true, now(), null),
(null, 'Nagro', false, now(), null);
21 changes: 11 additions & 10 deletions magnum/src/test/resources/mysql-person.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ create table person (
first_name varchar(50),
last_name varchar(50) not null,
is_admin boolean not null,
created timestamp default current_timestamp
created timestamp default current_timestamp,
social_id varchar(36)
);

insert into person (first_name, last_name, is_admin, created) values
('George', 'Washington', true, now()),
('Alexander', 'Hamilton', true, now()),
('John', 'Adams', true, now()),
('Benjamin', 'Franklin', true, now()),
('John', 'Jay', true, now()),
('Thomas', 'Jefferson', true, now()),
('James', 'Madison', true, now()),
(null, 'Nagro', false, now());
insert into person (first_name, last_name, is_admin, created, social_id) values
('George', 'Washington', true, now(), 'd06443a6-3efb-46c4-a66a-a80a8a9a5388'),
('Alexander', 'Hamilton', true, now(), '529b6c6d-7228-4da5-81d7-13b706f78ddb'),
('John', 'Adams', true, now(), null),
('Benjamin', 'Franklin', true, now(), null),
('John', 'Jay', true, now(), null),
('Thomas', 'Jefferson', true, now(), null),
('James', 'Madison', true, now(), null),
(null, 'Nagro', false, now(), null);
21 changes: 11 additions & 10 deletions magnum/src/test/resources/pg-person.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ create table person (
first_name varchar(50),
last_name varchar(50) not null,
is_admin boolean not null,
created timestamptz not null default now()
created timestamptz not null default now(),
social_id UUID
);

insert into person (first_name, last_name, is_admin, created) values
('George', 'Washington', true, now()),
('Alexander', 'Hamilton', true, now()),
('John', 'Adams', true, now()),
('Benjamin', 'Franklin', true, now()),
('John', 'Jay', true, now()),
('Thomas', 'Jefferson', true, now()),
('James', 'Madison', true, now()),
(null, 'Nagro', false, timestamp '1997-08-12');
insert into person (first_name, last_name, is_admin, created, social_id) values
('George', 'Washington', true, now(), 'd06443a6-3efb-46c4-a66a-a80a8a9a5388'),
('Alexander', 'Hamilton', true, now(), '529b6c6d-7228-4da5-81d7-13b706f78ddb'),
('John', 'Adams', true, now(), null),
('Benjamin', 'Franklin', true, now(), null),
('John', 'Jay', true, now(), null),
('Thomas', 'Jefferson', true, now(), null),
('James', 'Madison', true, now(), null),
(null, 'Nagro', false, timestamp '1997-08-12', null);
38 changes: 25 additions & 13 deletions magnum/src/test/scala/ClickHouseTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ class ClickHouseTests extends FunSuite, TestContainersFixtures:
firstName: Option[String],
lastName: String,
isAdmin: Boolean,
created: OffsetDateTime
created: OffsetDateTime,
socialId: Option[UUID]
) derives DbCodec

val personRepo = Repo[Person, Person, UUID]
Expand Down Expand Up @@ -201,7 +202,8 @@ class ClickHouseTests extends FunSuite, TestContainersFixtures:
firstName = Some("John"),
lastName = "Smith",
isAdmin = false,
created = OffsetDateTime.now
created = OffsetDateTime.now,
socialId = Some(UUID.randomUUID())
)
)
personRepo.insert(
Expand All @@ -210,7 +212,8 @@ class ClickHouseTests extends FunSuite, TestContainersFixtures:
firstName = None,
lastName = "Prince",
isAdmin = true,
created = OffsetDateTime.now
created = OffsetDateTime.now,
socialId = None
)
)
assertEquals(personRepo.count, 10L)
Expand All @@ -225,14 +228,16 @@ class ClickHouseTests extends FunSuite, TestContainersFixtures:
firstName = Some("John"),
lastName = "Smith",
isAdmin = false,
created = OffsetDateTime.now
created = OffsetDateTime.now,
socialId = Some(UUID.randomUUID())
),
Person(
id = UUID.randomUUID,
firstName = None,
lastName = "Prince",
isAdmin = true,
created = OffsetDateTime.now
created = OffsetDateTime.now,
socialId = None
)
)
)
Expand All @@ -247,7 +252,8 @@ class ClickHouseTests extends FunSuite, TestContainersFixtures:
firstName = Some("John"),
lastName = "Smith",
isAdmin = false,
created = OffsetDateTime.now
created = OffsetDateTime.now,
socialId = Some(UUID.randomUUID())
)
)
assertEquals(personRepo.count, 9L)
Expand All @@ -261,14 +267,16 @@ class ClickHouseTests extends FunSuite, TestContainersFixtures:
firstName = Some("John"),
lastName = "Smith",
isAdmin = false,
created = OffsetDateTime.now
created = OffsetDateTime.now,
socialId = Some(UUID.randomUUID())
),
Person(
id = UUID.randomUUID,
firstName = None,
lastName = "Prince",
isAdmin = true,
created = OffsetDateTime.now
created = OffsetDateTime.now,
socialId = None
)
)
val people = personRepo.insertAllReturning(ps)
Expand All @@ -283,7 +291,8 @@ class ClickHouseTests extends FunSuite, TestContainersFixtures:
firstName = None,
lastName = null,
isAdmin = false,
created = OffsetDateTime.now
created = OffsetDateTime.now,
socialId = None
)
personRepo.insert(invalidP)

Expand All @@ -310,14 +319,16 @@ class ClickHouseTests extends FunSuite, TestContainersFixtures:
firstName = Some("John"),
lastName = "Smith",
isAdmin = false,
created = OffsetDateTime.now
created = OffsetDateTime.now,
socialId = Some(UUID.randomUUID())
),
Person(
id = UUID.randomUUID,
firstName = None,
lastName = "Prince",
isAdmin = true,
created = OffsetDateTime.now
created = OffsetDateTime.now,
socialId = None
)
)
personRepo.insertAll(newPeople)
Expand Down Expand Up @@ -347,13 +358,14 @@ class ClickHouseTests extends FunSuite, TestContainersFixtures:
firstName = Some("John"),
lastName = "Smith",
isAdmin = false,
created = OffsetDateTime.now
created = OffsetDateTime.now,
socialId = Some(UUID.randomUUID())
)
val update =
sql"insert into $person ${person.insertColumns} values ($p)".update
assertNoDiff(
update.frag.sqlString,
"insert into person (id, first_name, last_name, is_admin, created) values (?, ?, ?, ?, ?)"
"insert into person (id, first_name, last_name, is_admin, created, social_id) values (?, ?, ?, ?, ?, ?)"
)
val rowsInserted = update.run()
assertEquals(rowsInserted, 1)
Expand Down
Loading

0 comments on commit 0d39f44

Please sign in to comment.