Skip to content

Commit

Permalink
Feature/doobie with status (#106)
Browse files Browse the repository at this point in the history
Added support for Doobie, status handling using Either.
  • Loading branch information
salamonpavel authored Jan 3, 2024
1 parent ad7bce5 commit c91623c
Show file tree
Hide file tree
Showing 73 changed files with 3,037 additions and 1,049 deletions.
34 changes: 19 additions & 15 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ jobs:
fail-fast: false
matrix:
include:
- scala: 2.11.12
scalaShort: "2.11"
overall: 0.0
changed: 80.0
- scala: 2.12.17
scalaShort: "2.12"
overall: 0.0
changed: 80.0
- scala: 2.13.12
scalaShort: "2.13"
overall: 0.0
changed: 80.0
name: Build and test
steps:
- name: Checkout code
Expand All @@ -53,6 +53,7 @@ jobs:
${{ github.workspace }}/core/target/scala-${{ matrix.scalaShort }}/jacoco/report/jacoco.xml
${{ github.workspace }}/examples/target/scala-${{ matrix.scalaShort }}/jacoco/report/jacoco.xml
${{ github.workspace }}/slick/target/scala-${{ matrix.scalaShort }}/jacoco/report/jacoco.xml
${{ github.workspace }}/doobie/target/scala-${{ matrix.scalaShort }}/jacoco/report/jacoco.xml
key: ${{ runner.os }}-${{ matrix.scalaShort }}-${{ hashFiles('**/jacoco.xml') }}

jacoco:
Expand All @@ -62,14 +63,14 @@ jobs:
fail-fast: false
matrix:
include:
- scala: 2.11.12
scalaShort: "2.11"
overall: 0.0
changed: 80.0
- scala: 2.12.17
scalaShort: "2.12"
overall: 0.0
changed: 80.0
- scala: 2.13.12
scalaShort: "2.13"
overall: 0.0
changed: 80.0
name: JaCoCo Code Coverage ${{matrix.scala}}
steps:
- name: Checkout code
Expand All @@ -80,18 +81,20 @@ jobs:
${{ github.workspace }}/core/target/scala-${{ matrix.scalaShort }}/jacoco/report/jacoco.xml
${{ github.workspace }}/examples/target/scala-${{ matrix.scalaShort }}/jacoco/report/jacoco.xml
${{ github.workspace }}/slick/target/scala-${{ matrix.scalaShort }}/jacoco/report/jacoco.xml
${{ github.workspace }}/doobie/target/scala-${{ matrix.scalaShort }}/jacoco/report/jacoco.xml
key: ${{ runner.os }}-${{ matrix.scalaShort }}-${{ hashFiles('**/jacoco.xml') }}
- name: Setup Scala
uses: olafurpg/setup-scala@v10
with:
java-version: "adopt@1.8"
- name: Add coverage to PR
id: jacoco
uses: madrapps/jacoco-report@v1.3
uses: madrapps/jacoco-report@v1.5
with:
paths: >
${{ github.workspace }}/core/target/scala-${{ matrix.scalaShort }}/jacoco/report/jacoco.xml,
${{ github.workspace }}/slick/target/scala-${{ matrix.scalaShort }}/jacoco/report/jacoco.xml
${{ github.workspace }}/doobie/target/scala-${{ matrix.scalaShort }}/jacoco/report/jacoco.xml
# examples don't need code coverage - at least not now
token: ${{ secrets.GITHUB_TOKEN }}
min-coverage-overall: ${{ matrix.overall }}
Expand All @@ -102,9 +105,10 @@ jobs:
run: |
echo "Total coverage ${{ steps.jacoco.outputs.coverage-overall }}"
echo "Changed Files coverage ${{ steps.jacoco.outputs.coverage-changed-files }}"
- name: Fail PR if changed files coverage is less than ${{ matrix.changed }}%
if: ${{ steps.jacoco.outputs.coverage-changed-files < 80.0 }}
uses: actions/github-script@v6
with:
script: |
core.setFailed('Changed files coverage is less than ${{ matrix.changed }}%!')
# temporarily disabled until we have a better way how to test against a database
# - name: Fail PR if changed files coverage is less than ${{ matrix.changed }}%
# if: ${{ steps.jacoco.outputs.coverage-changed-files < 80.0 }}
# uses: actions/github-script@v6
# with:
# script: |
# core.setFailed('Changed files coverage is less than ${{ matrix.changed }}%!')
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ dist
test-output
build.log
.bsp
/.bloop/
/.metals/
32 changes: 32 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
version = "3.5.3"
runner.dialect = scala213

maxColumn = 120

align.preset = some
align.multiline = false

align.tokens = [
{
code = "<-"
},
{
code = "=>"
owners = [{
regex = "Case"
}]
}
]

indent.main = 2
indent.defnSite = 2

lineEndings = unix

docstrings.blankFirstLine = yes
docstrings.style = AsteriskSpace
docstrings.wrap = no
docstrings.removeEmpty = true

align.openParenDefnSite = false
align.openParenCallSite = false
64 changes: 41 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,48 +54,50 @@ Currently, the library is developed with Postgres as the target DB. But the appr

#### Sbt

Import one of the two available module at the moment. Slick module works with Scala Futures. Doobie module works with any effect type (typically IO or ZIO) provided cats effect's Async instance is available.

```scala
libraryDependencies ++= Seq(
"za.co.absa.fa-db" %% "core" % "X.Y.Z",
"za.co.absa.fa-db" %% "slick" % "X.Y.Z"
)
libraryDependencies *= "za.co.absa.fa-db" %% "slick" % "X.Y.Z"
libraryDependencies *= "za.co.absa.fa-db" %% "doobie" % "X.Y.Z"
```

#### Maven

##### Scala 2.11
##### Scala 2.12

Modules:
* Core [![Maven Central](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/core_2.11/badge.svg)](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/core_2.11)
* Slick [![Maven Central](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/slick_2.11/badge.svg)](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/slick_2.11)
* Core [![Maven Central](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/core_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/core_2.12)
* Slick [![Maven Central](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/slick_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/slick_2.12)
* Doobie [![Maven Central](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/doobie_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/doobie_2.12)

```xml
<dependency>
<groupId>za.co.absa.fa-db</groupId>
<artifactId>core_2.11</artifactId>
<version>${latest_version}</version>
<groupId>za.co.absa.fa-db</groupId>
<artifactId>slick_2.12</artifactId>
<version>${latest_version}</version>
</dependency>
<dependency>
<groupId>za.co.absa.fa-db</groupId>
<artifactId>slick_2.11</artifactId>
<version>${latest_version}</version>
<groupId>za.co.absa.fa-db</groupId>
<artifactId>doobie_2.12</artifactId>
<version>${latest_version}</version>
</dependency>
```

### Scala 2.12
### Scala 2.13
Modules:
* Core [![Maven Central](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/core_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/core_2.12)
* Slick [![Maven Central](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/slick_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/slick_2.12)
* Core [![Maven Central](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/core_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/core_2.13)
* Slick [![Maven Central](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/slick_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/slick_2.13)
* Doobie [![Maven Central](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/doobie_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/za.co.absa.fa-db/doobie_2.13)

```xml
<dependency>
<groupId>za.co.absa.fa-db</groupId>
<artifactId>core_2.12</artifactId>
<version>${latest_version}</version>
<groupId>za.co.absa.fa-db</groupId>
<artifactId>slick_2.13</artifactId>
<version>${latest_version}</version>
</dependency>
<dependency>
<groupId>za.co.absa.fa-db</groupId>
<artifactId>slick_2.12</artifactId>
<artifactId>doobie_2.13</artifactId>
<version>${latest_version}</version>
</dependency>
```
Expand All @@ -109,13 +111,15 @@ Text about status codes returned from the database function can be found [here](

## Slick module

Slick module is the first (and so far only) implementation of fa-db able to execute. As the name suggests it runs on
[Slick library](https://github.com/slick/slick) and also brings in the [Slickpg library](https://github.com/tminglei/slick-pg/) for extended Postgres type support.
As the name suggests it runs on [Slick library](https://github.com/slick/slick) and also brings in the [Slickpg library](https://github.com/tminglei/slick-pg/) for extended Postgres type support.

It brings:

* `class SlickPgEngine` - implementation of _Core_'s `DBEngine` executing the queries via Slick
* `trait SlickFunction` and `trait SlickFunctionWithStatusSupport` - mix-in traits to use with `FaDbFunction` descendants
* `class SlickSingleResultFunction` - abstract class for DB functions returning single result
* `class SlickMultipleResultFunction` - abstract class for DB functions returning sequence of results
* `class SlickOptionalResultFunction` - abstract class for DB functions returning optional result
* `class SlickSingleResultFunctionWithStatus` - abstract class for DB functions with status handling; it requires an implementation of `StatusHandling` to be mixed-in (`StandardStatusHandling` available out-of-the-box)
* `trait FaDbPostgresProfile` - to bring support for Postgres and its extended data types in one class (except JSON, as there are multiple implementations for this data type in _Slick-Pg_)
* `object FaDbPostgresProfile` - instance of the above trait for direct use

Expand All @@ -137,6 +141,20 @@ val hStore: Option[Map[String, String]] = pr.nextHStoreOption
val macAddr: Option[MacAddrString] = pr.nextMacAddrOption
```

## Doobie module

As the name suggests it runs on [Doobie library](https://tpolecat.github.io/doobie/). The main benefit of the module is that it allows to use any effect type (typically IO or ZIO) therefore is more suitable for functional programming. It also brings in the [Doobie-Postgres library](https://tpolecat.github.io/doobie/docs/14-PostgreSQL.html) for extended Postgres type support.

It brings:

* `class DoobieEngine` - implementation of _Core_'s `DBEngine` executing the queries via Doobie. The class is type parameterized with the effect type.
* `class DoobieSingleResultFunction` - abstract class for DB functions returning single result
* `class DoobieMultipleResultFunction` - abstract class for DB functions returning sequence of results
* `class DoobieOptionalResultFunction` - abstract class for DB functions returning optional result
* `class DoobieSingleResultFunctionWithStatus` - abstract class for DB functions with status handling; it requires an implementation of `StatusHandling` to be mixed-in (`StandardStatusHandling` available out-of-the-box)

Since Doobie also interoperates with ZIO, there is an example of how a database connection can be properly established within a ZIO application. Please see [this file](doobie/zio-setup.md) for more details.

## Testing

### How to generate unit tests code coverage report
Expand Down
22 changes: 18 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ import com.github.sbt.jacoco.report.JacocoReportSettings

ThisBuild / organization := "za.co.absa.fa-db"

lazy val scala211 = "2.11.12"
lazy val scala212 = "2.12.17"
lazy val scala213 = "2.13.12"

ThisBuild / scalaVersion := scala211
ThisBuild / crossScalaVersions := Seq(scala211, scala212)
ThisBuild / scalaVersion := scala212
ThisBuild / crossScalaVersions := Seq(scala212, scala213)

ThisBuild / versionScheme := Some("early-semver")

Expand All @@ -49,7 +49,7 @@ lazy val commonJacocoExcludes: Seq[String] = Seq(
)

lazy val parent = (project in file("."))
.aggregate(faDbCore, faDBSlick, faDBExamples)
.aggregate(faDbCore, faDBSlick, faDBDoobie, faDBExamples)
.settings(
name := "root",
libraryDependencies ++= rootDependencies(scalaVersion.value),
Expand Down Expand Up @@ -88,6 +88,20 @@ lazy val faDBSlick = (project in file("slick"))
jacocoExcludes := commonJacocoExcludes
)

lazy val faDBDoobie = (project in file("doobie"))
.configs(IntegrationTest)
.settings(
name := "doobie",
libraryDependencies ++= doobieDependencies(scalaVersion.value),
javacOptions ++= commonJavacOptions,
scalacOptions ++= commonScalacOptions,
Defaults.itSettings,
).dependsOn(faDbCore)
.settings(
jacocoReportSettings := commonJacocoReportSettings.withTitle(s"fa-db:doobie Jacoco Report - scala:${scalaVersion.value}"),
jacocoExcludes := commonJacocoExcludes
)

lazy val faDBExamples = (project in file("examples"))
.configs(IntegrationTest)
.settings(
Expand Down
82 changes: 47 additions & 35 deletions core/src/main/scala/za/co/absa/fadb/DBEngine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,57 +16,69 @@

package za.co.absa.fadb

import scala.concurrent.{ExecutionContext, Future}
import cats.Monad
import cats.implicits.toFunctorOps
import za.co.absa.fadb.exceptions.StatusException

import scala.language.higherKinds

/**
* A basis to represent a database executor
*/
trait DBEngine {
* `DBEngine` is an abstract class that represents a database engine.
* It provides methods to execute queries and fetch results from a database.
* @tparam F - The type of the context in which the database queries are executed.
*/
abstract class DBEngine[F[_]: Monad] {

/**
* A type representing the (SQL) query within the engine
* @tparam T - the return type of the query
*/
type QueryType[T] <: Query[T]
* A type representing the (SQL) query within the engine
* @tparam R - the return type of the query
*/
type QueryType[R] <: Query[R]
type QueryWithStatusType[R] <: QueryWithStatus[_, _, R]

implicit val executor: ExecutionContext
/**
* The actual query executioner of the queries of the engine
* @param query - the query to execute
* @tparam R - return type of the query
* @return - sequence of the results of database query
*/
protected def run[R](query: QueryType[R]): F[Seq[R]]

/**
* The actual query executioner of the queries of the engine
* @param query - the query to execute
* @tparam R - return the of the query
* @return - sequence of the results of database query
*/
protected def run[R](query: QueryType[R]): Future[Seq[R]]
* The actual query executioner of the queries of the engine with status
* @param query - the query to execute
* @tparam R - return type of the query
* @return - result of database query with status
*/
def runWithStatus[R](query: QueryWithStatusType[R]): F[Either[StatusException, R]]

/**
* Public method to execute when query is expected to return multiple results
* @param query - the query to execute
* @tparam R - return the of the query
* @return - sequence of the results of database query
*/
def fetchAll[R](query: QueryType[R]): Future[Seq[R]] = run(query)
* Public method to execute when query is expected to return multiple results
* @param query - the query to execute
* @tparam R - return type of the query
* @return - sequence of the results of database query
*/
def fetchAll[R](query: QueryType[R]): F[Seq[R]] = {
run(query)
}

/**
* Public method to execute when query is expected to return exactly one row
* @param query - the query to execute
* @tparam R - return the of the query
* @return - sequence of the results of database query
*/
def fetchHead[R](query: QueryType[R]): Future[R] = {
* Public method to execute when query is expected to return exactly one row
* @param query - the query to execute
* @tparam R - return type of the query
* @return - sequence of the results of database query
*/
def fetchHead[R](query: QueryType[R]): F[R] = {
run(query).map(_.head)
}

/**
* Public method to execute when query is expected to return one or no results
* @param query - the query to execute
* @tparam R - return the of the query
* @return - sequence of the results of database query
*/

def fetchHeadOption[R](query: QueryType[R]): Future[Option[R]] = {
* Public method to execute when query is expected to return one or no results
* @param query - the query to execute
* @tparam R - return type of the query
* @return - sequence of the results of database query
*/
def fetchHeadOption[R](query: QueryType[R]): F[Option[R]] = {
run(query).map(_.headOption)
}
}

Loading

0 comments on commit c91623c

Please sign in to comment.