Skip to content
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
5 changes: 3 additions & 2 deletions core/src/main/scala/za/co/absa/db/fadb/DBFunction.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ package za.co.absa.db.fadb

import cats.MonadError
import cats.implicits.toFlatMapOps
import za.co.absa.db.fadb.exceptions.StatusException
import za.co.absa.db.fadb.status.aggregation.StatusAggregator
import za.co.absa.db.fadb.status.handling.StatusHandling
import za.co.absa.db.fadb.status.{FailedOrRows, FailedOrRow, Row}
import za.co.absa.db.fadb.status.{FailedOrRow, FailedOrRows, FunctionStatus}

import scala.language.higherKinds

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

// To be provided by an implementation of QueryStatusHandling
override def checkStatus[D](statusWithData: Row[D]): FailedOrRow[D]
override def checkStatus(functionStatus: FunctionStatus): Option[StatusException]
}

object DBFunction {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,20 @@

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

import za.co.absa.db.fadb.status.{FailedOrRow, Row}
import za.co.absa.db.fadb.exceptions.StatusException
import za.co.absa.db.fadb.status.FunctionStatus

/**
* `StatusHandling` is a base trait that defines the interface for handling the status of a function invocation.
* It provides a method to check the status of a function invocation with data.
* It provides a method to check the status of a function invocation.
*/
trait StatusHandling {

/**
* Checks the status of a function invocation.
* @param statusWithData - The status of the function invocation with data.
* @return Either a `StatusException` if the status code indicates an error, or the data (along with the status
* information so that it's retrievable) if the status code is successful.
* @param functionStatus - The status of the function invocation.
* @return Some with a `StatusException` if the status code indicates an error,
* or None if the status code is successful.
*/
def checkStatus[D](statusWithData: Row[D]): FailedOrRow[D]
def checkStatus(functionStatus: FunctionStatus): Option[StatusException]
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package za.co.absa.db.fadb.status.handling.implementations

import za.co.absa.db.fadb.exceptions._
import za.co.absa.db.fadb.status.{FailedOrRow, Row}
import za.co.absa.db.fadb.status.FunctionStatus
import za.co.absa.db.fadb.status.handling.StatusHandling

/**
Expand All @@ -29,16 +29,15 @@ trait StandardStatusHandling extends StatusHandling {
/**
* Checks the status of a function invocation.
*/
override def checkStatus[D](statusWithData: Row[D]): FailedOrRow[D] = {
val functionStatus = statusWithData.functionStatus
override def checkStatus(functionStatus: FunctionStatus): Option[StatusException] = {
functionStatus.statusCode / 10 match {
case 1 => Right(statusWithData)
case 2 => Left(ServerMisconfigurationException(functionStatus))
case 3 => Left(DataConflictException(functionStatus))
case 4 => Left(DataNotFoundException(functionStatus))
case 5 | 6 | 7 | 8 => Left(ErrorInDataException(functionStatus))
case 9 => Left(OtherStatusException(functionStatus))
case _ => Left(StatusOutOfRangeException(functionStatus))
case 1 => None
case 2 => Some(ServerMisconfigurationException(functionStatus))
case 3 => Some(DataConflictException(functionStatus))
case 4 => Some(DataNotFoundException(functionStatus))
case 5 | 6 | 7 | 8 => Some(ErrorInDataException(functionStatus))
case 9 => Some(OtherStatusException(functionStatus))
case _ => Some(StatusOutOfRangeException(functionStatus))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@

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

import za.co.absa.db.fadb.exceptions.OtherStatusException
import za.co.absa.db.fadb.exceptions.{OtherStatusException, StatusException}
import za.co.absa.db.fadb.status.FunctionStatus
import za.co.absa.db.fadb.status.handling.StatusHandling
import za.co.absa.db.fadb.status.{FailedOrRow, Row}

/**
* Trait represents user defined status handling
*/
trait UserDefinedStatusHandling extends StatusHandling {
def OKStatuses: Set[Integer]

override def checkStatus[D](statusWithData: Row[D]): FailedOrRow[D] =
if (OKStatuses.contains(statusWithData.functionStatus.statusCode)) {
Right(statusWithData)
override def checkStatus(functionStatus: FunctionStatus): Option[StatusException] =
if (OKStatuses.contains(functionStatus.statusCode)) {
None
} else {
Left(OtherStatusException(statusWithData.functionStatus))
Some(OtherStatusException(functionStatus))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,73 +25,65 @@ class StandardStatusHandlingUnitTests extends AnyFunSuiteLike {

private val standardQueryStatusHandling = new StandardStatusHandling {}

test("checkStatus should return Right when status code is in the range 10-19") {
test("checkStatus should return None when status code is in the range 10-19") {
for (statusCode <- 10 to 19) {
val functionStatus = FunctionStatus(statusCode, "Success")
val statusWithData = Row(functionStatus, "Data")
val result = standardQueryStatusHandling.checkStatus(statusWithData)
result shouldBe Right(statusWithData)
val result = standardQueryStatusHandling.checkStatus(functionStatus)
result shouldBe None
}
}

test("checkStatus should return Left with ServerMisconfigurationException when status code is in the range 20-29") {
test("checkStatus should return Some with ServerMisconfigurationException when status code is in the range 20-29") {
for (statusCode <- 20 to 29) {
val functionStatus = FunctionStatus(statusCode, "Server Misconfiguration")
val statusWithData = Row(functionStatus, "Data")
val result = standardQueryStatusHandling.checkStatus(statusWithData)
result shouldBe Left(ServerMisconfigurationException(functionStatus))
val result = standardQueryStatusHandling.checkStatus(functionStatus)
result shouldBe Some(ServerMisconfigurationException(functionStatus))
}
}

test("checkStatus should return Left with DataConflictException when status code is in the range 30-39") {
test("checkStatus should return Some with DataConflictException when status code is in the range 30-39") {
for (statusCode <- 30 to 39) {
val functionStatus = FunctionStatus(statusCode, "Data Conflict")
val statusWithData = Row(functionStatus, "Data")
val result = standardQueryStatusHandling.checkStatus(statusWithData)
result shouldBe Left(DataConflictException(functionStatus))
val result = standardQueryStatusHandling.checkStatus(functionStatus)
result shouldBe Some(DataConflictException(functionStatus))
}
}

test("checkStatus should return Left with DataNotFoundException when status code is in the range 40-49") {
test("checkStatus should return Some with DataNotFoundException when status code is in the range 40-49") {
for (statusCode <- 40 to 49) {
val functionStatus = FunctionStatus(statusCode, "Data Not Found")
val statusWithData = Row(functionStatus, "Data")
val result = standardQueryStatusHandling.checkStatus(statusWithData)
result shouldBe Left(DataNotFoundException(functionStatus))
val result = standardQueryStatusHandling.checkStatus(functionStatus)
result shouldBe Some(DataNotFoundException(functionStatus))
}
}

test("checkStatus should return Left with ErrorInDataException when status code is in the range 50-89") {
test("checkStatus should return Some with ErrorInDataException when status code is in the range 50-89") {
for (statusCode <- 50 to 89) {
val functionStatus = FunctionStatus(statusCode, "Error in Data")
val statusWithData = Row(functionStatus, "Data")
val result = standardQueryStatusHandling.checkStatus(statusWithData)
result shouldBe Left(ErrorInDataException(functionStatus))
val result = standardQueryStatusHandling.checkStatus(functionStatus)
result shouldBe Some(ErrorInDataException(functionStatus))
}
}

test("checkStatus should return Left with OtherStatusException when status code is in the range 90-99") {
test("checkStatus should return Some with OtherStatusException when status code is in the range 90-99") {
for (statusCode <- 90 to 99) {
val functionStatus = FunctionStatus(statusCode, "Other Status")
val statusWithData = Row(functionStatus, "Data")
val result = standardQueryStatusHandling.checkStatus(statusWithData)
result shouldBe Left(OtherStatusException(functionStatus))
val result = standardQueryStatusHandling.checkStatus(functionStatus)
result shouldBe Some(OtherStatusException(functionStatus))
}
}

test("checkStatus should return Left with StatusOutOfRangeException when status code is not in any known range") {
test("checkStatus should return Some with StatusOutOfRangeException when status code is not in any known range") {
for (statusCode <- 0 to 9) {
val functionStatus = FunctionStatus(statusCode, "Out of range")
val statusWithData = Row(functionStatus, "Data")
val result = standardQueryStatusHandling.checkStatus(statusWithData)
result shouldBe Left(StatusOutOfRangeException(functionStatus))
val result = standardQueryStatusHandling.checkStatus(functionStatus)
result shouldBe Some(StatusOutOfRangeException(functionStatus))
}

for (statusCode <- 100 to 110) {
val functionStatus = FunctionStatus(statusCode, "Out of range")
val statusWithData = Row(functionStatus, "Data")
val result = standardQueryStatusHandling.checkStatus(statusWithData)
result shouldBe Left(StatusOutOfRangeException(functionStatus))
val result = standardQueryStatusHandling.checkStatus(functionStatus)
result shouldBe Some(StatusOutOfRangeException(functionStatus))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ class DoobieEngine[F[_]: Async](val transactor: Transactor[F]) extends DBEngine[
* and Doobie's `Read` wasn't able to work with it.
*
* @param query the Doobie query to execute
* @param readStatusWithDataR the `Read[StatusWithData[R]]` instance used to read the query result into `StatusWithData[R]`
* @param readStatusWithData the `Read[StatusWithData[R]]` instance used to read the query result into `StatusWithData[R]`
* @return the query result
*/
private def executeQueryWithStatus[R](
query: QueryWithStatusType[R]
)(implicit readStatusWithDataR: Read[StatusWithData[R]]): F[Seq[FailedOrRow[R]]] = {
)(implicit readStatusWithData: Read[StatusWithData[R]]): F[Seq[FailedOrRow[R]]] = {
query.fragment.query[StatusWithData[R]].to[Seq].transact(transactor).map(_.map(query.getResultOrException))
}

Expand All @@ -84,6 +84,6 @@ class DoobieEngine[F[_]: Async](val transactor: Transactor[F]) extends DBEngine[
* @return the query result
*/
override def runWithStatus[R](query: QueryWithStatusType[R]): F[Seq[FailedOrRow[R]]] = {
executeQueryWithStatus(query)(query.readStatusWithDataR)
executeQueryWithStatus(query)(query.readStatusWithData)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import doobie.util.Read
import doobie.util.fragment.Fragment
import za.co.absa.db.fadb.DBFunction._
import za.co.absa.db.fadb.DBSchema
import za.co.absa.db.fadb.status.{FailedOrRow, Row}
import za.co.absa.db.fadb.exceptions.StatusException
import za.co.absa.db.fadb.status.FunctionStatus

import scala.language.higherKinds

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

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

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

/**
* Function that generates a sequence of `Fragment`s representing the SQL query from input values for the function.
Expand All @@ -141,12 +137,10 @@ trait DoobieFunctionWithStatus[I, R, F[_]] extends DoobieFunctionBase[R] {
* @param selectEntry columns names for the select statement
* @param functionName name of the function
* @param alias alias for the sql query
* @param read Read instance for R type
* @param me MonadError instance for F type
* @return the `Fragment` representing the SQL query
*/
private def meSql(values: I, selectEntry: String, functionName: String, alias: String)(implicit
read: Read[StatusWithData[R]],
me: MonadError[F, Throwable]
): F[Fragment] = {
me.catchNonFatal {
Expand Down Expand Up @@ -185,18 +179,17 @@ trait DoobieFunctionWithStatus[I, R, F[_]] extends DoobieFunctionBase[R] {
/**
* Generates a `Fragment` representing the SQL query for the function.
* @param values the input values for the function
* @param read Read instance for `StatusWithData[R]`
* @param me MonadError instance for F type
* @return the `Fragment` representing the SQL query
*/
protected final def sql(
values: I
)(implicit read: Read[StatusWithData[R]], me: MonadError[F, Throwable]): F[Fragment] = {
meSql(values, selectEntry, functionName, alias)(read, me)
)(me: MonadError[F, Throwable]): F[Fragment] = {
meSql(values, selectEntry, functionName, alias)(me)
}

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

/**
Expand Down Expand Up @@ -233,7 +226,7 @@ object DoobieFunction {
* @param schema the database schema
* @param dbEngine the `DoobieEngine` instance used to execute SQL queries
* @param readR Read instance for `R`
* @param readSelectWithStatus Read instance for `StatusWithData[R]`
* @param readStatusWithData Read instance for `StatusWithData[R]`
* @tparam F the effect type, which must have an `Async` and a `Monad` instance
*/
abstract class DoobieSingleResultFunctionWithStatus[I, R, F[_]](
Expand All @@ -243,7 +236,7 @@ object DoobieFunction {
override val schema: DBSchema,
val dbEngine: DoobieEngine[F],
val readR: Read[R],
val readSelectWithStatus: Read[StatusWithData[R]]
val readStatusWithData: Read[StatusWithData[R]]
) extends DBSingleResultFunctionWithStatus[I, R, DoobieEngine[F], F](functionNameOverride)
with DoobieFunctionWithStatus[I, R, F]

Expand All @@ -269,7 +262,8 @@ object DoobieFunction {
)(implicit
override val schema: DBSchema,
val dbEngine: DoobieEngine[F],
val readR: Read[R]
val readR: Read[R],
val readStatusWithData: Read[StatusWithData[R]]
) extends DBMultipleResultFunctionWithStatus[I, R, DoobieEngine[F], F](functionNameOverride)
with DoobieFunctionWithStatus[I, R, F]

Expand All @@ -286,7 +280,8 @@ object DoobieFunction {
)(implicit
override val schema: DBSchema,
val dbEngine: DoobieEngine[F],
val readR: Read[R]
val readR: Read[R],
val readStatusWithData: Read[StatusWithData[R]]
) extends DBMultipleResultFunctionWithAggStatus[I, R, DoobieEngine[F], F](functionNameOverride)
with DoobieFunctionWithStatus[I, R, F]

Expand All @@ -312,7 +307,8 @@ object DoobieFunction {
)(implicit
override val schema: DBSchema,
val dbEngine: DoobieEngine[F],
val readR: Read[R]
val readR: Read[R],
val readStatusWithData: Read[StatusWithData[R]]
) extends DBOptionalResultFunctionWithStatus[I, R, DoobieEngine[F], F](functionNameOverride)
with DoobieFunctionWithStatus[I, R, F]
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package za.co.absa.db.fadb.doobie

import doobie.util.Read
import doobie.util.fragment.Fragment
import za.co.absa.db.fadb.exceptions.StatusException
import za.co.absa.db.fadb.status.{FailedOrRow, FunctionStatus, Row}
import za.co.absa.db.fadb.{Query, QueryWithStatus}

Expand All @@ -36,27 +37,30 @@ class DoobieQuery[R](val fragment: Fragment)(implicit val readR: Read[R]) extend
*
* @param fragment the Doobie fragment representing the SQL query
* @param checkStatus the function to check the status of the query
* @param readStatusWithDataR the `Read[StatusWithData[R]]` instance used to read the query result into `StatusWithData[R]`
* @param readStatusWithData the `Read[StatusWithData[R]]` instance used to read the query result into `StatusWithData[R]`
*/
class DoobieQueryWithStatus[R](
val fragment: Fragment,
checkStatus: Row[R] => FailedOrRow[R]
)(implicit val readStatusWithDataR: Read[StatusWithData[R]])
extends QueryWithStatus[StatusWithData[R], R, R] {
checkStatus: FunctionStatus => Option[StatusException]
)(implicit val readStatusWithData: Read[StatusWithData[R]])
extends QueryWithStatus[StatusWithData[R], Option[R], R] {

/*
* Processes the status of the query and returns the status with data
* @param initialResult - the initial result of the query
* @return data with status
*/
override def processStatus(initialResult: StatusWithData[R]): Row[R] =
override def processStatus(initialResult: StatusWithData[R]): Row[Option[R]] =
Row(FunctionStatus(initialResult.status, initialResult.statusText), initialResult.data)

/*
* Converts the status with data to either a status exception or the data
* @param statusWithData - the status with data
* @return either a status exception or the data
*/
override def toStatusExceptionOrData(statusWithData: Row[R]): FailedOrRow[R] =
checkStatus(statusWithData)
override def toStatusExceptionOrData(statusWithData: Row[Option[R]]): FailedOrRow[R] =
checkStatus(statusWithData.functionStatus).toLeft(
statusWithData.data.getOrElse(throw new IllegalStateException("Status is OK but data is missing"))
).map(r => Row(statusWithData.functionStatus, r))

}
Loading
Loading