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 @@ -96,6 +96,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
59 changes: 32 additions & 27 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,9 @@

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}

/**
* A class that represents a database function call. It can be used to execute a function and verify the result.
Expand All @@ -29,18 +28,20 @@ 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 {
orderedParams: OrderedParams,
namedParams: NamedParams) 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
}
val positionedParamEntries = orderedParams.values.map(_.sqlEntry)
val namedParamEntries = namedParams.items.map{ case (columnName, queryParamValue) =>
columnName.sqlEntry + " := " + queryParamValue.sqlEntry
}
val paramEntries = positionedParamEntries ++ namedParamEntries
Comment thread
lsulak marked this conversation as resolved.
val paramsLine = paramEntries.mkString(",")
s"SELECT * FROM $functionName($paramsLine) $orderBy"
}
Expand Down Expand Up @@ -92,7 +93,7 @@ sealed abstract class DBFunction private(functionName: String,
*/
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)
runQuery(sql(orderByPart), orderedParams.values ++ namedParams.values)(verify)
}

/**
Expand All @@ -104,9 +105,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,15 +126,13 @@ 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())
Comment thread
lsulak marked this conversation as resolved.
}
}


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.
*
Expand All @@ -146,16 +143,24 @@ object DBFunction {
DBFunctionWithPositionedParamsOnly(functionName)
}

def apply(functionName: String, params: NamedParams): DBFunctionWithNamedParamsToo = {
Comment thread
lsulak marked this conversation as resolved.
DBFunctionWithNamedParamsToo(functionName, OrderedParams(), params)
}

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

/**
* 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)
*/
sealed case class DBFunctionWithPositionedParamsOnly private(functionName: String,
params: ParamsMap = ListMap.empty
) extends DBFunction(functionName, params) {
orderedParams: OrderedParams = OrderedParams()
) extends DBFunction(functionName, orderedParams, namedParams = NamedParams()) {
Comment on lines 161 to +163

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix syntax error: Remove trailing comma.

The trailing comma on line 162 after OrderedParams() causes a compilation error because there are no additional parameters following it. This matches the pipeline failure: "identifier expected but ')' found."

🔎 Proposed fix
   sealed case class DBFunctionWithPositionedParamsOnly private(functionName: String,
-                                                               orderedParams: OrderedParams = OrderedParams(),
+                                                               orderedParams: OrderedParams = OrderedParams()
                                                               ) extends DBFunction(functionName, orderedParams, namedParams = NamedParams()) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sealed case class DBFunctionWithPositionedParamsOnly private(functionName: String,
params: ParamsMap = ListMap.empty
) extends DBFunction(functionName, params) {
orderedParams: OrderedParams = OrderedParams(),
) extends DBFunction(functionName, orderedParams, namedParams = NamedParams()) {
sealed case class DBFunctionWithPositionedParamsOnly private(functionName: String,
orderedParams: OrderedParams = OrderedParams()
) extends DBFunction(functionName, orderedParams, namedParams = NamedParams()) {
🧰 Tools
🪛 GitHub Actions: Build

[error] 163-163: identifier expected but ')' found.

🤖 Prompt for AI Agents
In balta/src/main/scala/za/co/absa/db/balta/classes/DBFunction.scala around
lines 161 to 163, there's a trailing comma after OrderedParams() that causes a
syntax/compilation error; remove the trailing comma so the case class parameter
list becomes valid (i.e., delete the comma after OrderedParams() and ensure
spacing/indentation remains correct).

/**
* 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 +169,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))
}

/**
Expand All @@ -187,10 +190,12 @@ 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)
orderedParams: OrderedParams = OrderedParams(),
namedParams: NamedParams = NamedParams()
) extends DBFunction(functionName, orderedParams, namedParams)
}

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import za.co.absa.db.balta.typeclasses.QueryParamValue
*/
trait DBQuerySupport {

protected def runQuery[R](sql: String, queryValues: List[QueryParamValue])
protected def runQuery[R](sql: String, queryValues: Vector[QueryParamValue])
(verify: QueryResult => R /* Assertion */)
(implicit connection: DBConnection): R = {
val preparedStatement = connection.connection.prepareStatement(sql)
Expand Down
27 changes: 14 additions & 13 deletions balta/src/main/scala/za/co/absa/db/balta/classes/DBTable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package za.co.absa.db.balta.classes
import za.co.absa.db.balta.classes.inner.Params
import za.co.absa.db.balta.classes.inner.Params.NamedParams
import za.co.absa.db.balta.typeclasses.{QueryParamValue, QueryParamType}
import za.co.absa.db.balta.classes.inner.Params.OrderedParams

/**
* This class represents a database table. It allows to perform INSERT, SELECT and COUNT operations on the table easily.
Expand All @@ -35,10 +36,11 @@ case class DBTable(tableName: String) extends DBQuerySupport{
* @return - the inserted row.
*/
def insert(values: Params)(implicit connection: DBConnection): QueryResultRow = {
val columns = values.keys.map {keys =>
val keysString = keys.mkString(",") // TODO https://github.com/AbsaOSS/balta/issues/2
s"($keysString)"
}.getOrElse("")
val columns = values match {
case namedParams: NamedParams => namedParams.paramNames.map(_.sqlEntry).mkString("(", ",", ")")
case _: OrderedParams => ""
}

val paramStr = values.values.map(_.sqlEntry).mkString(",")
val sql = s"INSERT INTO $tableName $columns VALUES($paramStr) RETURNING *;"
runQuery(sql, values.values){_.next()}
Expand All @@ -49,18 +51,18 @@ case class DBTable(tableName: String) extends DBQuerySupport{
*
* @param keyName - the name of the key column
* @param keyValue - the value of the key column
* @param fieldName - the name of the field to be returned
* @param columnName - the name of the field to be returned
* @param connection - a database connection used for the SELECT operation.
* @tparam K - the type of the key value
* @tparam T - the type of the returned field value
* @return - the value of the field, if the value is NULL, then `Some(None)` is returned; if no row is found,
* then `None` is returned.
*/
def fieldValue[K: QueryParamType, T](keyName: String, keyValue: K, fieldName: String)
def fieldValue[K: QueryParamType, T](keyName: String, keyValue: K, columnName: String)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def fieldValue[K: QueryParamType, T](keyName: String, keyValue: K, columnName: String)
def columnValue[K: QueryParamType, T](keyName: String, keyValue: K, columnName: String)

also the method description/doc can be changed slighlty

(implicit connection: DBConnection): Option[Option[T]] = {
where(Params.add(keyName, keyValue)){resultSet =>
if (resultSet.hasNext) {
Some(resultSet.next().getAs[T](fieldName))
Some(resultSet.next().getAs[T](columnName))
} else {
None
}
Expand Down Expand Up @@ -212,7 +214,7 @@ case class DBTable(tableName: String) extends DBQuerySupport{
composeCountAndRun(strToOption(condition))
}

private def composeSelectAndRun[R](whereCondition: Option[String], orderByExpr: Option[String], values: List[QueryParamValue] = List.empty)
private def composeSelectAndRun[R](whereCondition: Option[String], orderByExpr: Option[String], values: Vector[QueryParamValue] = Vector.empty)
(verify: QueryResult => R)
(implicit connection: DBConnection): R = {
val where = whereCondition.map("WHERE " + _).getOrElse("")
Expand All @@ -221,15 +223,15 @@ case class DBTable(tableName: String) extends DBQuerySupport{
runQuery(sql, values)(verify)
}

private def composeDeleteAndRun[R](whereCondition: Option[String], values: List[QueryParamValue] = List.empty)
private def composeDeleteAndRun[R](whereCondition: Option[String], values: Vector[QueryParamValue] = Vector.empty)
(verify: QueryResult => R)
(implicit connection: DBConnection): R = {
val where = whereCondition.map("WHERE " + _).getOrElse("")
val sql = s"DELETE FROM $tableName $where RETURNING *;"
runQuery(sql, values)(verify)
}

private def composeCountAndRun[R](whereCondition: Option[String], values: List[QueryParamValue] = List.empty)
private def composeCountAndRun(whereCondition: Option[String], values: Vector[QueryParamValue] = Vector.empty)
(implicit connection: DBConnection): Long = {
val where = whereCondition.map("WHERE " + _).getOrElse("")
val sql = s"SELECT count(1) AS cnt FROM $tableName $where;"
Expand All @@ -247,9 +249,8 @@ case class DBTable(tableName: String) extends DBQuerySupport{
}

private def paramsToWhereCondition(params: NamedParams): String = {
params.pairs.foldRight(List.empty[String]) {case ((fieldName, value), acc) =>
// TODO https://github.com/AbsaOSS/balta/issues/2
val condition = s"$fieldName ${value.equalityOperator} ${value.sqlEntry}"
params.items.foldRight(List.empty[String]) {case ((columnName, value), acc) =>
val condition = s"${columnName.sqlEntry} ${value.equalityOperator} ${value.sqlEntry}"
condition :: acc
}.mkString(" AND ")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

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

import za.co.absa.db.balta.classes.QueryResultRow.{Extractors, FieldNames}
import za.co.absa.db.balta.classes.QueryResultRow.{Extractors, ColumnNames}

import java.sql.{ResultSet, ResultSetMetaData, SQLException}

Expand All @@ -31,7 +31,7 @@ class QueryResult(resultSet: ResultSet) extends Iterator[QueryResultRow] {

private [this] var nextRow: Option[QueryResultRow] = None

private [this] implicit val fieldNames: FieldNames = QueryResultRow.fieldNamesFromMetadata(resultSetMetaData)
private [this] implicit val columnNames: ColumnNames = QueryResultRow.columnNamesFromMetadata(resultSetMetaData)
private [this] implicit val extractors: Extractors = QueryResultRow.createExtractors(resultSetMetaData)

override def hasNext: Boolean = {
Expand Down
Loading
Loading