Skip to content
Open
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
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ jobs:
psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/07_testing_pg_types_data.sql
psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/08_testing.simple_function.sql
psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/09_testing.table_lifecycle.ddl
psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/10_testing.strange_columns.sql
psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/11_testing.strange_columns_data.sql

- name: Build and run integration tests
run: sbt ++${{matrix.scala}} testIT
2 changes: 2 additions & 0 deletions .github/workflows/jacoco_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ jobs:
psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/07_testing_pg_types_data.sql
psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/08_testing.simple_function.sql
psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/09_testing.table_lifecycle.ddl
psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/10_testing.strange_columns.sql
psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/11_testing.strange_columns_data.sql

- name: Build and run tests
continue-on-error: true
Expand Down
80 changes: 44 additions & 36 deletions balta/src/main/scala/za/co/absa/db/balta/classes/DBFunction.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@

package za.co.absa.db.balta.classes

import DBFunction.{DBFunctionWithNamedParamsToo, DBFunctionWithPositionedParamsOnly, ParamsMap}

import scala.collection.immutable.ListMap
import za.co.absa.db.balta.typeclasses.{QueryParamValue, QueryParamType}
import za.co.absa.db.balta.classes.DBFunction.{DBFunctionWithNamedParamsToo, DBFunctionWithPositionedParamsOnly}
import za.co.absa.db.balta.typeclasses.QueryParamType
import za.co.absa.db.balta.classes.inner.Params.{NamedParams, OrderedParams}
import za.co.absa.db.mag.core.SqlEntry
import za.co.absa.db.mag.core.SqlEntryComposition._

/**
* A class that represents a database function call. It can be used to execute a function and verify the result.
Expand All @@ -29,20 +30,21 @@ import za.co.absa.db.balta.typeclasses.{QueryParamValue, QueryParamType}
* name; note that the position defined parameters can be added only at the beginning of the parameter list
*
* @param functionName - the name of the function
* @param params - the list of parameters
* @param orderedParams - the list of parameters identified by their position (preceding the named parameters)
* @param namedParams - the list of parameters identified by their name (following the positioned parameters)
*
*/
sealed abstract class DBFunction private(functionName: String,
params: ParamsMap) extends DBQuerySupport {

private def sql(orderBy: String): String = {
val paramEntries = params.map{case(key, setterFnc) =>
key match {
case Left(_) => setterFnc.sqlEntry
case Right(name) => s"$name := ${setterFnc.sqlEntry}" // TODO https://github.com/AbsaOSS/balta/issues/2
}
sealed abstract class DBFunction private(functionName: SqlEntry,
orderedParams: OrderedParams,
namedParams: NamedParams) extends DBQuerySupport {

private def sql(orderBy: Option[SqlEntry]): SqlEntry = {
val positionedParamEntries = orderedParams.values.map(_.sqlEntry)
val namedParamEntries = namedParams.items.map{ case (columnName, queryParamValue) =>
columnName.sqlEntry := queryParamValue.sqlEntry
}
val paramsLine = paramEntries.mkString(",")
s"SELECT * FROM $functionName($paramsLine) $orderBy"
val paramEntries = positionedParamEntries ++ namedParamEntries
SELECT(ALL) FROM functionName(paramEntries) ORDER BY (orderBy)
}

/**
Expand Down Expand Up @@ -91,8 +93,8 @@ sealed abstract class DBFunction private(functionName: String,
* @return - the result of the verify function
*/
def execute[R](orderBy: String)(verify: QueryResult => R /* Assertion */)(implicit connection: DBConnection): R = {
val orderByPart = if (orderBy.nonEmpty) {s"ORDER BY $orderBy"} else ""
runQuery(sql(orderByPart), params.values.toList)(verify)
val orderByPart = SqlEntry(orderBy).toOption
runQuery(sql(orderByPart), orderedParams.values ++ namedParams.values)(verify)
}

/**
Expand All @@ -104,9 +106,7 @@ sealed abstract class DBFunction private(functionName: String,
* @return - a new instance of the DBFunction class with the new parameter
*/
def setParam[T: QueryParamType](paramName: String, value: T): DBFunctionWithNamedParamsToo = {
val key = Right(paramName) // TODO normalization TODO https://github.com/AbsaOSS/balta/issues/1
val queryValue = implicitly[QueryParamType[T]].toQueryParamValue(value)
DBFunctionWithNamedParamsToo(functionName, params + (key -> queryValue))
DBFunctionWithNamedParamsToo(functionName, orderedParams, namedParams.add(paramName, value))
}

/**
Expand All @@ -127,35 +127,43 @@ sealed abstract class DBFunction private(functionName: String,
* @return - a new instance of the DBFunction class without any parameters set
*/
def clear(): DBFunctionWithPositionedParamsOnly = {
DBFunctionWithPositionedParamsOnly(functionName)
DBFunctionWithPositionedParamsOnly(functionName, OrderedParams(), NamedParams())
}
}


object DBFunction {

type ParamsMap = ListMap[Either[Int, String], QueryParamValue]

/**
* Creates a new instance of the DBFunction class with the given function name without any parameters set.
*
* @param functionName - the name of the function
* @return - a new instance of the DBFunction class
*/
def apply(functionName: String): DBFunctionWithPositionedParamsOnly = {
DBFunctionWithPositionedParamsOnly(functionName)
DBFunctionWithPositionedParamsOnly(SqlEntry(functionName))
}

def apply(functionName: String, params: NamedParams): DBFunctionWithNamedParamsToo = {
DBFunctionWithNamedParamsToo(SqlEntry(functionName), OrderedParams(), params)
}

def apply(functionName: String, params: OrderedParams): DBFunctionWithPositionedParamsOnly = {
DBFunctionWithPositionedParamsOnly(SqlEntry(functionName), params, NamedParams())
}

/**
* Class that represents a database function call with parameters defined by their position only. It's the default
* class when creating a new instance of the DBFunction class without any parameters set.
*
* @param functionName - the name of the function
* @param params - the list of parameters
* @param orderedParams - the list of parameters identified by their position (preceding the named parameters)
* @param namedParams - the list of parameters identified by their name (following the positioned parameters)
*/
sealed case class DBFunctionWithPositionedParamsOnly private(functionName: String,
params: ParamsMap = ListMap.empty
) extends DBFunction(functionName, params) {
sealed case class DBFunctionWithPositionedParamsOnly private(functionName: SqlEntry,
orderedParams: OrderedParams = OrderedParams(),
namedParams: NamedParams = NamedParams()
) extends DBFunction(functionName, orderedParams, namedParams) {
/**
* Sets a parameter for the function call. It actually creates a new instance of the DBFunction class with the new
* parameter. The new parameter is the last in the parameter list.
Expand All @@ -164,9 +172,7 @@ object DBFunction {
* @return - a new instance of the DBFunction class with the new parameter
*/
def setParam[T: QueryParamType](value: T): DBFunctionWithPositionedParamsOnly = {
val key = Left(params.size + 1)
val queryValue = implicitly[QueryParamType[T]].toQueryParamValue(value)
DBFunctionWithPositionedParamsOnly(functionName, params + (key -> queryValue))
DBFunctionWithPositionedParamsOnly(functionName, orderedParams.add(value), namedParams)
}

/**
Expand All @@ -187,9 +193,11 @@ object DBFunction {
* position (at the beginning of the list) and by their name (for the rest of the list).
*
* @param functionName - the name of the function
* @param params - the list of parameters
* @param orderedParams - the list of parameters identified by their position (preceding the named parameters)
* @param namedParams - the list of parameters identified by their name (following the positioned parameters)
*/
sealed case class DBFunctionWithNamedParamsToo private(functionName: String,
params: ParamsMap = ListMap.empty
) extends DBFunction(functionName, params)
sealed case class DBFunctionWithNamedParamsToo private(functionName: SqlEntry,
orderedParams: OrderedParams = OrderedParams(),
namedParams: NamedParams = NamedParams()
) extends DBFunction(functionName, orderedParams, namedParams)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@
package za.co.absa.db.balta.classes

import za.co.absa.db.balta.typeclasses.QueryParamValue
import za.co.absa.db.mag.core.SqlEntry

/**
* This is a based trait providing the ability to run an SQL query and verify the result via a provided function.
*/
trait DBQuerySupport {

protected def runQuery[R](sql: String, queryValues: List[QueryParamValue])
protected def runQuery[R](sql: SqlEntry, queryValues: Vector[QueryParamValue])
(verify: QueryResult => R /* Assertion */)
(implicit connection: DBConnection): R = {
val preparedStatement = connection.connection.prepareStatement(sql)
val preparedStatement = connection.connection.prepareStatement(sql.entry)

queryValues.foldLeft(1) { case (parameterIndex, queryValue) =>
queryValue.assign match { // this is better readable-wise than map + getOrElse
Expand Down
Loading