Skip to content

Commit 347288e

Browse files
authored
#133 Fix doobie error when status is not OK and data is missing (#137)
* core: rework StatusHandling such that now it requires only FunctionStatus not the entire Row * slick: apply changes in core StatusHandling * doobie: apply core changes and make the data in functions with status Option by default
1 parent b8eca19 commit 347288e

File tree

15 files changed

+192
-134
lines changed

15 files changed

+192
-134
lines changed

core/src/main/scala/za/co/absa/db/fadb/DBFunction.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ package za.co.absa.db.fadb
1818

1919
import cats.MonadError
2020
import cats.implicits.toFlatMapOps
21+
import za.co.absa.db.fadb.exceptions.StatusException
2122
import za.co.absa.db.fadb.status.aggregation.StatusAggregator
2223
import za.co.absa.db.fadb.status.handling.StatusHandling
23-
import za.co.absa.db.fadb.status.{FailedOrRows, FailedOrRow, Row}
24+
import za.co.absa.db.fadb.status.{FailedOrRow, FailedOrRows, FunctionStatus}
2425

2526
import scala.language.higherKinds
2627

@@ -148,7 +149,7 @@ abstract class DBFunctionWithStatus[I, R, E <: DBEngine[F], F[_]](functionNameOv
148149
protected def query(values: I)(implicit me: MonadError[F, Throwable]): F[dBEngine.QueryWithStatusType[R]]
149150

150151
// To be provided by an implementation of QueryStatusHandling
151-
override def checkStatus[D](statusWithData: Row[D]): FailedOrRow[D]
152+
override def checkStatus(functionStatus: FunctionStatus): Option[StatusException]
152153
}
153154

154155
object DBFunction {

core/src/main/scala/za/co/absa/db/fadb/status/handling/StatusHandling.scala

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,20 @@
1616

1717
package za.co.absa.db.fadb.status.handling
1818

19-
import za.co.absa.db.fadb.status.{FailedOrRow, Row}
19+
import za.co.absa.db.fadb.exceptions.StatusException
20+
import za.co.absa.db.fadb.status.FunctionStatus
2021

2122
/**
2223
* `StatusHandling` is a base trait that defines the interface for handling the status of a function invocation.
23-
* It provides a method to check the status of a function invocation with data.
24+
* It provides a method to check the status of a function invocation.
2425
*/
2526
trait StatusHandling {
2627

2728
/**
2829
* Checks the status of a function invocation.
29-
* @param statusWithData - The status of the function invocation with data.
30-
* @return Either a `StatusException` if the status code indicates an error, or the data (along with the status
31-
* information so that it's retrievable) if the status code is successful.
30+
* @param functionStatus - The status of the function invocation.
31+
* @return Some with a `StatusException` if the status code indicates an error,
32+
* or None if the status code is successful.
3233
*/
33-
def checkStatus[D](statusWithData: Row[D]): FailedOrRow[D]
34+
def checkStatus(functionStatus: FunctionStatus): Option[StatusException]
3435
}

core/src/main/scala/za/co/absa/db/fadb/status/handling/implementations/StandardStatusHandling.scala

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package za.co.absa.db.fadb.status.handling.implementations
1818

1919
import za.co.absa.db.fadb.exceptions._
20-
import za.co.absa.db.fadb.status.{FailedOrRow, Row}
20+
import za.co.absa.db.fadb.status.FunctionStatus
2121
import za.co.absa.db.fadb.status.handling.StatusHandling
2222

2323
/**
@@ -29,16 +29,15 @@ trait StandardStatusHandling extends StatusHandling {
2929
/**
3030
* Checks the status of a function invocation.
3131
*/
32-
override def checkStatus[D](statusWithData: Row[D]): FailedOrRow[D] = {
33-
val functionStatus = statusWithData.functionStatus
32+
override def checkStatus(functionStatus: FunctionStatus): Option[StatusException] = {
3433
functionStatus.statusCode / 10 match {
35-
case 1 => Right(statusWithData)
36-
case 2 => Left(ServerMisconfigurationException(functionStatus))
37-
case 3 => Left(DataConflictException(functionStatus))
38-
case 4 => Left(DataNotFoundException(functionStatus))
39-
case 5 | 6 | 7 | 8 => Left(ErrorInDataException(functionStatus))
40-
case 9 => Left(OtherStatusException(functionStatus))
41-
case _ => Left(StatusOutOfRangeException(functionStatus))
34+
case 1 => None
35+
case 2 => Some(ServerMisconfigurationException(functionStatus))
36+
case 3 => Some(DataConflictException(functionStatus))
37+
case 4 => Some(DataNotFoundException(functionStatus))
38+
case 5 | 6 | 7 | 8 => Some(ErrorInDataException(functionStatus))
39+
case 9 => Some(OtherStatusException(functionStatus))
40+
case _ => Some(StatusOutOfRangeException(functionStatus))
4241
}
4342
}
4443
}

core/src/main/scala/za/co/absa/db/fadb/status/handling/implementations/UserDefinedStatusHandling.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,20 @@
1616

1717
package za.co.absa.db.fadb.status.handling.implementations
1818

19-
import za.co.absa.db.fadb.exceptions.OtherStatusException
19+
import za.co.absa.db.fadb.exceptions.{OtherStatusException, StatusException}
20+
import za.co.absa.db.fadb.status.FunctionStatus
2021
import za.co.absa.db.fadb.status.handling.StatusHandling
21-
import za.co.absa.db.fadb.status.{FailedOrRow, Row}
2222

2323
/**
2424
* Trait represents user defined status handling
2525
*/
2626
trait UserDefinedStatusHandling extends StatusHandling {
2727
def OKStatuses: Set[Integer]
2828

29-
override def checkStatus[D](statusWithData: Row[D]): FailedOrRow[D] =
30-
if (OKStatuses.contains(statusWithData.functionStatus.statusCode)) {
31-
Right(statusWithData)
29+
override def checkStatus(functionStatus: FunctionStatus): Option[StatusException] =
30+
if (OKStatuses.contains(functionStatus.statusCode)) {
31+
None
3232
} else {
33-
Left(OtherStatusException(statusWithData.functionStatus))
33+
Some(OtherStatusException(functionStatus))
3434
}
3535
}

core/src/test/scala/za/co/absa/db/fadb/status/handling/implementations/StandardStatusHandlingUnitTests.scala

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,73 +25,65 @@ class StandardStatusHandlingUnitTests extends AnyFunSuiteLike {
2525

2626
private val standardQueryStatusHandling = new StandardStatusHandling {}
2727

28-
test("checkStatus should return Right when status code is in the range 10-19") {
28+
test("checkStatus should return None when status code is in the range 10-19") {
2929
for (statusCode <- 10 to 19) {
3030
val functionStatus = FunctionStatus(statusCode, "Success")
31-
val statusWithData = Row(functionStatus, "Data")
32-
val result = standardQueryStatusHandling.checkStatus(statusWithData)
33-
result shouldBe Right(statusWithData)
31+
val result = standardQueryStatusHandling.checkStatus(functionStatus)
32+
result shouldBe None
3433
}
3534
}
3635

37-
test("checkStatus should return Left with ServerMisconfigurationException when status code is in the range 20-29") {
36+
test("checkStatus should return Some with ServerMisconfigurationException when status code is in the range 20-29") {
3837
for (statusCode <- 20 to 29) {
3938
val functionStatus = FunctionStatus(statusCode, "Server Misconfiguration")
40-
val statusWithData = Row(functionStatus, "Data")
41-
val result = standardQueryStatusHandling.checkStatus(statusWithData)
42-
result shouldBe Left(ServerMisconfigurationException(functionStatus))
39+
val result = standardQueryStatusHandling.checkStatus(functionStatus)
40+
result shouldBe Some(ServerMisconfigurationException(functionStatus))
4341
}
4442
}
4543

46-
test("checkStatus should return Left with DataConflictException when status code is in the range 30-39") {
44+
test("checkStatus should return Some with DataConflictException when status code is in the range 30-39") {
4745
for (statusCode <- 30 to 39) {
4846
val functionStatus = FunctionStatus(statusCode, "Data Conflict")
49-
val statusWithData = Row(functionStatus, "Data")
50-
val result = standardQueryStatusHandling.checkStatus(statusWithData)
51-
result shouldBe Left(DataConflictException(functionStatus))
47+
val result = standardQueryStatusHandling.checkStatus(functionStatus)
48+
result shouldBe Some(DataConflictException(functionStatus))
5249
}
5350
}
5451

55-
test("checkStatus should return Left with DataNotFoundException when status code is in the range 40-49") {
52+
test("checkStatus should return Some with DataNotFoundException when status code is in the range 40-49") {
5653
for (statusCode <- 40 to 49) {
5754
val functionStatus = FunctionStatus(statusCode, "Data Not Found")
58-
val statusWithData = Row(functionStatus, "Data")
59-
val result = standardQueryStatusHandling.checkStatus(statusWithData)
60-
result shouldBe Left(DataNotFoundException(functionStatus))
55+
val result = standardQueryStatusHandling.checkStatus(functionStatus)
56+
result shouldBe Some(DataNotFoundException(functionStatus))
6157
}
6258
}
6359

64-
test("checkStatus should return Left with ErrorInDataException when status code is in the range 50-89") {
60+
test("checkStatus should return Some with ErrorInDataException when status code is in the range 50-89") {
6561
for (statusCode <- 50 to 89) {
6662
val functionStatus = FunctionStatus(statusCode, "Error in Data")
67-
val statusWithData = Row(functionStatus, "Data")
68-
val result = standardQueryStatusHandling.checkStatus(statusWithData)
69-
result shouldBe Left(ErrorInDataException(functionStatus))
63+
val result = standardQueryStatusHandling.checkStatus(functionStatus)
64+
result shouldBe Some(ErrorInDataException(functionStatus))
7065
}
7166
}
7267

73-
test("checkStatus should return Left with OtherStatusException when status code is in the range 90-99") {
68+
test("checkStatus should return Some with OtherStatusException when status code is in the range 90-99") {
7469
for (statusCode <- 90 to 99) {
7570
val functionStatus = FunctionStatus(statusCode, "Other Status")
76-
val statusWithData = Row(functionStatus, "Data")
77-
val result = standardQueryStatusHandling.checkStatus(statusWithData)
78-
result shouldBe Left(OtherStatusException(functionStatus))
71+
val result = standardQueryStatusHandling.checkStatus(functionStatus)
72+
result shouldBe Some(OtherStatusException(functionStatus))
7973
}
8074
}
8175

82-
test("checkStatus should return Left with StatusOutOfRangeException when status code is not in any known range") {
76+
test("checkStatus should return Some with StatusOutOfRangeException when status code is not in any known range") {
8377
for (statusCode <- 0 to 9) {
8478
val functionStatus = FunctionStatus(statusCode, "Out of range")
85-
val statusWithData = Row(functionStatus, "Data")
86-
val result = standardQueryStatusHandling.checkStatus(statusWithData)
87-
result shouldBe Left(StatusOutOfRangeException(functionStatus))
79+
val result = standardQueryStatusHandling.checkStatus(functionStatus)
80+
result shouldBe Some(StatusOutOfRangeException(functionStatus))
8881
}
8982

9083
for (statusCode <- 100 to 110) {
9184
val functionStatus = FunctionStatus(statusCode, "Out of range")
92-
val statusWithData = Row(functionStatus, "Data")
93-
val result = standardQueryStatusHandling.checkStatus(statusWithData)
94-
result shouldBe Left(StatusOutOfRangeException(functionStatus))
85+
val result = standardQueryStatusHandling.checkStatus(functionStatus)
86+
result shouldBe Some(StatusOutOfRangeException(functionStatus))
9587
}
9688
}
9789

doobie/src/main/scala/za/co/absa/db/fadb/doobie/DoobieEngine.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ class DoobieEngine[F[_]: Async](val transactor: Transactor[F]) extends DBEngine[
5959
* and Doobie's `Read` wasn't able to work with it.
6060
*
6161
* @param query the Doobie query to execute
62-
* @param readStatusWithDataR the `Read[StatusWithData[R]]` instance used to read the query result into `StatusWithData[R]`
62+
* @param readStatusWithData the `Read[StatusWithData[R]]` instance used to read the query result into `StatusWithData[R]`
6363
* @return the query result
6464
*/
6565
private def executeQueryWithStatus[R](
6666
query: QueryWithStatusType[R]
67-
)(implicit readStatusWithDataR: Read[StatusWithData[R]]): F[Seq[FailedOrRow[R]]] = {
67+
)(implicit readStatusWithData: Read[StatusWithData[R]]): F[Seq[FailedOrRow[R]]] = {
6868
query.fragment.query[StatusWithData[R]].to[Seq].transact(transactor).map(_.map(query.getResultOrException))
6969
}
7070

@@ -84,6 +84,6 @@ class DoobieEngine[F[_]: Async](val transactor: Transactor[F]) extends DBEngine[
8484
* @return the query result
8585
*/
8686
override def runWithStatus[R](query: QueryWithStatusType[R]): F[Seq[FailedOrRow[R]]] = {
87-
executeQueryWithStatus(query)(query.readStatusWithDataR)
87+
executeQueryWithStatus(query)(query.readStatusWithData)
8888
}
8989
}

doobie/src/main/scala/za/co/absa/db/fadb/doobie/DoobieFunction.scala

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import doobie.util.Read
2222
import doobie.util.fragment.Fragment
2323
import za.co.absa.db.fadb.DBFunction._
2424
import za.co.absa.db.fadb.DBSchema
25-
import za.co.absa.db.fadb.status.{FailedOrRow, Row}
25+
import za.co.absa.db.fadb.exceptions.StatusException
26+
import za.co.absa.db.fadb.status.FunctionStatus
2627

2728
import scala.language.higherKinds
2829

@@ -122,12 +123,7 @@ trait DoobieFunction[I, R, F[_]] extends DoobieFunctionBase[R] {
122123

123124
trait DoobieFunctionWithStatus[I, R, F[_]] extends DoobieFunctionBase[R] {
124125

125-
/**
126-
* The `Read[StatusWithData[R]]` instance used to read the query result with status into `StatusWithData[R]`.
127-
*/
128-
implicit def readStatusWithDataR(implicit readR: Read[R]): Read[StatusWithData[R]] = Read[(Int, String, R)].map {
129-
case (status, status_text, data) => StatusWithData(status, status_text, data)
130-
}
126+
implicit val readStatusWithData: Read[StatusWithData[R]]
131127

132128
/**
133129
* Function that generates a sequence of `Fragment`s representing the SQL query from input values for the function.
@@ -141,12 +137,10 @@ trait DoobieFunctionWithStatus[I, R, F[_]] extends DoobieFunctionBase[R] {
141137
* @param selectEntry columns names for the select statement
142138
* @param functionName name of the function
143139
* @param alias alias for the sql query
144-
* @param read Read instance for R type
145140
* @param me MonadError instance for F type
146141
* @return the `Fragment` representing the SQL query
147142
*/
148143
private def meSql(values: I, selectEntry: String, functionName: String, alias: String)(implicit
149-
read: Read[StatusWithData[R]],
150144
me: MonadError[F, Throwable]
151145
): F[Fragment] = {
152146
me.catchNonFatal {
@@ -185,18 +179,17 @@ trait DoobieFunctionWithStatus[I, R, F[_]] extends DoobieFunctionBase[R] {
185179
/**
186180
* Generates a `Fragment` representing the SQL query for the function.
187181
* @param values the input values for the function
188-
* @param read Read instance for `StatusWithData[R]`
189182
* @param me MonadError instance for F type
190183
* @return the `Fragment` representing the SQL query
191184
*/
192185
protected final def sql(
193186
values: I
194-
)(implicit read: Read[StatusWithData[R]], me: MonadError[F, Throwable]): F[Fragment] = {
195-
meSql(values, selectEntry, functionName, alias)(read, me)
187+
)(me: MonadError[F, Throwable]): F[Fragment] = {
188+
meSql(values, selectEntry, functionName, alias)(me)
196189
}
197190

198191
// This is to be mixed in by an implementation of StatusHandling
199-
def checkStatus[D](statusWithData: Row[D]): FailedOrRow[D]
192+
def checkStatus(functionStatus: FunctionStatus): Option[StatusException]
200193
}
201194

202195
/**
@@ -233,7 +226,7 @@ object DoobieFunction {
233226
* @param schema the database schema
234227
* @param dbEngine the `DoobieEngine` instance used to execute SQL queries
235228
* @param readR Read instance for `R`
236-
* @param readSelectWithStatus Read instance for `StatusWithData[R]`
229+
* @param readStatusWithData Read instance for `StatusWithData[R]`
237230
* @tparam F the effect type, which must have an `Async` and a `Monad` instance
238231
*/
239232
abstract class DoobieSingleResultFunctionWithStatus[I, R, F[_]](
@@ -243,7 +236,7 @@ object DoobieFunction {
243236
override val schema: DBSchema,
244237
val dbEngine: DoobieEngine[F],
245238
val readR: Read[R],
246-
val readSelectWithStatus: Read[StatusWithData[R]]
239+
val readStatusWithData: Read[StatusWithData[R]]
247240
) extends DBSingleResultFunctionWithStatus[I, R, DoobieEngine[F], F](functionNameOverride)
248241
with DoobieFunctionWithStatus[I, R, F]
249242

@@ -269,7 +262,8 @@ object DoobieFunction {
269262
)(implicit
270263
override val schema: DBSchema,
271264
val dbEngine: DoobieEngine[F],
272-
val readR: Read[R]
265+
val readR: Read[R],
266+
val readStatusWithData: Read[StatusWithData[R]]
273267
) extends DBMultipleResultFunctionWithStatus[I, R, DoobieEngine[F], F](functionNameOverride)
274268
with DoobieFunctionWithStatus[I, R, F]
275269

@@ -286,7 +280,8 @@ object DoobieFunction {
286280
)(implicit
287281
override val schema: DBSchema,
288282
val dbEngine: DoobieEngine[F],
289-
val readR: Read[R]
283+
val readR: Read[R],
284+
val readStatusWithData: Read[StatusWithData[R]]
290285
) extends DBMultipleResultFunctionWithAggStatus[I, R, DoobieEngine[F], F](functionNameOverride)
291286
with DoobieFunctionWithStatus[I, R, F]
292287

@@ -312,7 +307,8 @@ object DoobieFunction {
312307
)(implicit
313308
override val schema: DBSchema,
314309
val dbEngine: DoobieEngine[F],
315-
val readR: Read[R]
310+
val readR: Read[R],
311+
val readStatusWithData: Read[StatusWithData[R]]
316312
) extends DBOptionalResultFunctionWithStatus[I, R, DoobieEngine[F], F](functionNameOverride)
317313
with DoobieFunctionWithStatus[I, R, F]
318314
}

doobie/src/main/scala/za/co/absa/db/fadb/doobie/DoobieQuery.scala

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package za.co.absa.db.fadb.doobie
1818

1919
import doobie.util.Read
2020
import doobie.util.fragment.Fragment
21+
import za.co.absa.db.fadb.exceptions.StatusException
2122
import za.co.absa.db.fadb.status.{FailedOrRow, FunctionStatus, Row}
2223
import za.co.absa.db.fadb.{Query, QueryWithStatus}
2324

@@ -36,27 +37,30 @@ class DoobieQuery[R](val fragment: Fragment)(implicit val readR: Read[R]) extend
3637
*
3738
* @param fragment the Doobie fragment representing the SQL query
3839
* @param checkStatus the function to check the status of the query
39-
* @param readStatusWithDataR the `Read[StatusWithData[R]]` instance used to read the query result into `StatusWithData[R]`
40+
* @param readStatusWithData the `Read[StatusWithData[R]]` instance used to read the query result into `StatusWithData[R]`
4041
*/
4142
class DoobieQueryWithStatus[R](
4243
val fragment: Fragment,
43-
checkStatus: Row[R] => FailedOrRow[R]
44-
)(implicit val readStatusWithDataR: Read[StatusWithData[R]])
45-
extends QueryWithStatus[StatusWithData[R], R, R] {
44+
checkStatus: FunctionStatus => Option[StatusException]
45+
)(implicit val readStatusWithData: Read[StatusWithData[R]])
46+
extends QueryWithStatus[StatusWithData[R], Option[R], R] {
4647

4748
/*
4849
* Processes the status of the query and returns the status with data
4950
* @param initialResult - the initial result of the query
5051
* @return data with status
5152
*/
52-
override def processStatus(initialResult: StatusWithData[R]): Row[R] =
53+
override def processStatus(initialResult: StatusWithData[R]): Row[Option[R]] =
5354
Row(FunctionStatus(initialResult.status, initialResult.statusText), initialResult.data)
5455

5556
/*
5657
* Converts the status with data to either a status exception or the data
5758
* @param statusWithData - the status with data
5859
* @return either a status exception or the data
5960
*/
60-
override def toStatusExceptionOrData(statusWithData: Row[R]): FailedOrRow[R] =
61-
checkStatus(statusWithData)
61+
override def toStatusExceptionOrData(statusWithData: Row[Option[R]]): FailedOrRow[R] =
62+
checkStatus(statusWithData.functionStatus).toLeft(
63+
statusWithData.data.getOrElse(throw new IllegalStateException("Status is OK but data is missing"))
64+
).map(r => Row(statusWithData.functionStatus, r))
65+
6266
}

0 commit comments

Comments
 (0)