From 8c9afd30f24cc73583b3a815d756ed2fd348b041 Mon Sep 17 00:00:00 2001 From: vladimirkl <72238+vladimirkl@users.noreply.github.com> Date: Tue, 7 May 2024 14:10:19 +0300 Subject: [PATCH] Fix exception on null json / jsonb value in postgres --- .../context/json/PostgresJsonExtensions.scala | 4 +- .../getquill/postgres/PostgresJsonSpec.scala | 105 ++++++++++++++++-- 2 files changed, 96 insertions(+), 13 deletions(-) diff --git a/quill-jdbc-zio/src/main/scala/io/getquill/context/json/PostgresJsonExtensions.scala b/quill-jdbc-zio/src/main/scala/io/getquill/context/json/PostgresJsonExtensions.scala index 8e2ebc3b..85c808c5 100644 --- a/quill-jdbc-zio/src/main/scala/io/getquill/context/json/PostgresJsonExtensions.scala +++ b/quill-jdbc-zio/src/main/scala/io/getquill/context/json/PostgresJsonExtensions.scala @@ -26,7 +26,7 @@ trait PostgresJsonExtensions extends Encoders with Decoders { implicit def jsonbAstDecoder: Decoder[JsonbValue[Json]] = astDecoder(JsonbValue(_)) def astEncoder[Wrapper](valueToString: Wrapper => String, jsonType: String): Encoder[Wrapper] = - encoder(Types.VARCHAR, (index, jsonValue, row) => { + encoder(Types.OTHER, (index, jsonValue, row) => { val obj = new org.postgresql.util.PGobject() obj.setType(jsonType) val jsonString = valueToString(jsonValue) @@ -50,7 +50,7 @@ trait PostgresJsonExtensions extends Encoders with Decoders { jsonType: String, jsonEncoder: JsonEncoder[JsValue] ): Encoder[Wrapper] = - encoder(Types.VARCHAR, (index, jsonValue, row) => { + encoder(Types.OTHER, (index, jsonValue, row) => { val obj = new org.postgresql.util.PGobject() obj.setType(jsonType) val jsonString = jsonEncoder.encodeJson(unwrap(jsonValue), None).toString diff --git a/quill-jdbc-zio/src/test/scala/io/getquill/postgres/PostgresJsonSpec.scala b/quill-jdbc-zio/src/test/scala/io/getquill/postgres/PostgresJsonSpec.scala index b9734091..6e01f341 100644 --- a/quill-jdbc-zio/src/test/scala/io/getquill/postgres/PostgresJsonSpec.scala +++ b/quill-jdbc-zio/src/test/scala/io/getquill/postgres/PostgresJsonSpec.scala @@ -1,11 +1,12 @@ package io.getquill.postgres import io.getquill._ +import org.scalatest.BeforeAndAfterEach import zio.Chunk import zio.json.ast.Json -import zio.json.{ DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder } // +import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} -class PostgresJsonSpec extends ZioSpec { +class PostgresJsonSpec extends ZioSpec with BeforeAndAfterEach { val context = testContext import testContext._ @@ -15,22 +16,28 @@ class PostgresJsonSpec extends ZioSpec { case class JsonEntity(name: String, value: JsonValue[PersonJson]) case class JsonbEntity(name: String, value: JsonbValue[PersonJsonb]) - val jsonJoe = JsonValue(PersonJson("Joe", 123)) - val jsonValue = JsonEntity("JoeEntity", jsonJoe) - val jsonbJoe = JsonbValue(PersonJsonb("Joe", 123)) + case class JsonOptEntity(name: String, value: Option[JsonValue[PersonJson]]) + case class JsonbOptEntity(name: String, value: Option[JsonbValue[PersonJsonb]]) + + val jsonJoe = JsonValue(PersonJson("Joe", 123)) + val jsonValue = JsonEntity("JoeEntity", jsonJoe) + val jsonbJoe = JsonbValue(PersonJsonb("Joe", 123)) val jsonbValue = JsonbEntity("JoeEntity", jsonbJoe) case class JsonAstEntity(name: String, value: JsonValue[Json]) case class JsonbAstEntity(name: String, value: JsonbValue[Json]) + case class JsonAstOptEntity(name: String, value: Option[JsonValue[Json]]) + case class JsonbAstOptEntity(name: String, value: Option[JsonbValue[Json]]) + implicit val personJsonEncoder: JsonEncoder[PersonJson] = DeriveJsonEncoder.gen[PersonJson] implicit val personJsonDecoder: JsonDecoder[PersonJson] = DeriveJsonDecoder.gen[PersonJson] implicit val personJsonbEncoder: JsonEncoder[PersonJsonb] = DeriveJsonEncoder.gen[PersonJsonb] implicit val personJsonbDecoder: JsonDecoder[PersonJsonb] = DeriveJsonDecoder.gen[PersonJsonb] - override def beforeAll() = { - super.beforeAll() + override def beforeEach() = { + super.beforeEach() testContext.run(quote(query[JsonbEntity].delete)).runSyncUnsafe() testContext.run(quote(query[JsonEntity].delete)).runSyncUnsafe() () @@ -50,12 +57,49 @@ class PostgresJsonSpec extends ZioSpec { } } + "encodes and decodes optional json entity" - { + inline def jsonOptQuery = quote(querySchema[JsonOptEntity]("JsonEntity")) + inline def jsonbOptQuery = quote(querySchema[JsonbOptEntity]("JsonEntity")) + + "some json" in { + val value = JsonOptEntity("JoeEntity", Some(jsonJoe)) + + testContext.run(jsonOptQuery.insertValue(lift(value))).runSyncUnsafe() + val inserted = testContext.run(jsonOptQuery).runSyncUnsafe().head + inserted mustEqual value + } + + "some jsonb" in { + val value = JsonbOptEntity("JoeEntity", Some(jsonbJoe)) + + testContext.run(jsonbOptQuery.insertValue(lift(value))).runSyncUnsafe() + val inserted = testContext.run(jsonbOptQuery).runSyncUnsafe().head + inserted mustEqual value + } + + "none json" in { + val value = JsonOptEntity("JoeEntity", None) + + testContext.run(jsonOptQuery.insertValue(lift(value))).runSyncUnsafe() + val inserted = testContext.run(jsonOptQuery).runSyncUnsafe().head + inserted mustEqual value + } + + "none jsonb" in { + val value = JsonbOptEntity("JoeEntity", None) + + testContext.run(jsonbOptQuery.insertValue(lift(value))).runSyncUnsafe() + val inserted = testContext.run(jsonbOptQuery).runSyncUnsafe().head + inserted mustEqual value + } + } + "encodes and decodes json ast" - { - val jsonJoe = Json.Obj(Chunk("age" -> Json.Num(123), "name" -> Json.Str("Joe"))) - inline def jsonAstQuery = quote { querySchema[JsonAstEntity]("JsonEntity") } - inline def jsonbAstQuery = quote { querySchema[JsonbAstEntity]("JsonbEntity") } + val jsonJoe = Json.Obj(Chunk("age" -> Json.Num(123), "name" -> Json.Str("Joe"))) + inline def jsonAstQuery = quote(querySchema[JsonAstEntity]("JsonEntity")) + inline def jsonbAstQuery = quote(querySchema[JsonbAstEntity]("JsonbEntity")) - val jsonAstValue = JsonAstEntity("JoeEntity", JsonValue(jsonJoe)) + val jsonAstValue = JsonAstEntity("JoeEntity", JsonValue(jsonJoe)) val jsonbAstValue = JsonbAstEntity("JoeEntity", JsonbValue(jsonJoe)) "json" in { @@ -63,10 +107,49 @@ class PostgresJsonSpec extends ZioSpec { val inserted = testContext.run(jsonAstQuery).runSyncUnsafe().head inserted mustEqual jsonAstValue } + "jsonb" in { testContext.run(jsonbAstQuery.insertValue(lift(jsonbAstValue))).runSyncUnsafe() val inserted = testContext.run(jsonbAstQuery).runSyncUnsafe().head inserted mustEqual jsonbAstValue } } + + "encodes and decodes optional json ast" - { + val jsonJoe = Json.Obj(Chunk("age" -> Json.Num(123), "name" -> Json.Str("Joe"))) + inline def jsonAstOptQuery = quote(querySchema[JsonAstOptEntity]("JsonEntity")) + inline def jsonbAstOptQuery = quote(querySchema[JsonbAstOptEntity]("JsonbEntity")) + + val jsonbAstValue = JsonbAstEntity("JoeEntity", JsonbValue(jsonJoe)) + + "some json" in { + val value = JsonAstOptEntity("JoeEntity", Some(JsonValue(jsonJoe))) + testContext.run(jsonAstOptQuery.insertValue(lift(value))).runSyncUnsafe() + val inserted = testContext.run(jsonAstOptQuery).runSyncUnsafe().head + inserted mustEqual value + } + + "some jsonb" in { + val value = JsonbAstOptEntity("JoeEntity", Some(JsonbValue(jsonJoe))) + testContext.run(jsonbAstOptQuery.insertValue(lift(value))).runSyncUnsafe() + val inserted = testContext.run(jsonbAstOptQuery).runSyncUnsafe().head + inserted mustEqual value + } + + "none json" in { + val value = JsonAstOptEntity("JoeEntity", None) + testContext.run(jsonAstOptQuery.insertValue(lift(value))).runSyncUnsafe() + val inserted = testContext.run(jsonAstOptQuery).runSyncUnsafe().head + inserted mustEqual value + } + + "none jsonb" in { + val value = JsonbAstOptEntity("JoeEntity", None) + testContext.run(jsonbAstOptQuery.insertValue(lift(value))).runSyncUnsafe() + val inserted = testContext.run(jsonbAstOptQuery).runSyncUnsafe().head + inserted mustEqual value + } + + } + }