From 98e72259209d5ee90989083649621d9a9b3bae90 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Wed, 17 Jul 2024 15:34:00 +0200 Subject: [PATCH 01/11] #26: Move code into sub package db --- README.md | 15 +++++-- .../za/co/absa/balta/classes/package.scala | 42 ------------------- .../co/absa/{ => db}/balta/DBTestSuite.scala | 26 ++++-------- .../absa/db/balta/classes/DBConnection.scala | 31 ++++++++++++++ .../{ => db}/balta/classes/DBFunction.scala | 7 ++-- .../balta/classes/DBQuerySupport.scala | 4 +- .../absa/{ => db}/balta/classes/DBTable.scala | 8 ++-- .../{ => db}/balta/classes/QueryResult.scala | 2 +- .../balta/classes/QueryResultRow.scala | 3 +- .../classes/setter/AllowedParamTypes.scala | 6 +-- .../balta/classes/setter/CustomDBType.scala | 2 +- .../balta/classes/setter/Params.scala | 2 +- .../balta/classes/setter/SetterFnc.scala | 5 +-- .../balta/classes/simple/ConnectionInfo.scala | 33 +++++++++++++++ .../db/balta/classes/simple/JsonBString.scala | 19 +++++++++ .../{ => db}/balta/implicits/package.scala | 4 +- build.sbt | 2 - publish.sbt | 2 + 18 files changed, 127 insertions(+), 86 deletions(-) delete mode 100644 balta/src/main/scala/za/co/absa/balta/classes/package.scala rename balta/src/main/scala/za/co/absa/{ => db}/balta/DBTestSuite.scala (87%) create mode 100644 balta/src/main/scala/za/co/absa/db/balta/classes/DBConnection.scala rename balta/src/main/scala/za/co/absa/{ => db}/balta/classes/DBFunction.scala (96%) rename balta/src/main/scala/za/co/absa/{ => db}/balta/classes/DBQuerySupport.scala (93%) rename balta/src/main/scala/za/co/absa/{ => db}/balta/classes/DBTable.scala (96%) rename balta/src/main/scala/za/co/absa/{ => db}/balta/classes/QueryResult.scala (97%) rename balta/src/main/scala/za/co/absa/{ => db}/balta/classes/QueryResultRow.scala (97%) rename balta/src/main/scala/za/co/absa/{ => db}/balta/classes/setter/AllowedParamTypes.scala (92%) rename balta/src/main/scala/za/co/absa/{ => db}/balta/classes/setter/CustomDBType.scala (95%) rename balta/src/main/scala/za/co/absa/{ => db}/balta/classes/setter/Params.scala (99%) rename balta/src/main/scala/za/co/absa/{ => db}/balta/classes/setter/SetterFnc.scala (97%) create mode 100644 balta/src/main/scala/za/co/absa/db/balta/classes/simple/ConnectionInfo.scala create mode 100644 balta/src/main/scala/za/co/absa/db/balta/classes/simple/JsonBString.scala rename balta/src/main/scala/za/co/absa/{ => db}/balta/implicits/package.scala (92%) diff --git a/README.md b/README.md index 30c9af6..ac42f3b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,19 @@ # Balta -Scala library to write Postgres DB code tests with +Scala library to used for writing Postgres DB code tests. + +--- + +### Build Status + +[![Build](https://github.com/AbsaOSS/balta/workflows/Build/badge.svg)](https://github.com/AbsaOSS/fa-db/actions) + +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/za.co.absa..db/balta_2.12/badge.svg)](https://search.maven.org/search?q=g:za.co.absa.fa-db) + +--- Balta is a Scala library to help creating database tests, particularly testing Database functions. It is based on the -popular [ScalaTest](http://www.scalatest.org/) library and uses [PostgreSQL](https://www.postgresql.org/) as the -database engine. +popular [ScalaTest](http://www.scalatest.org/) library and uses [PostgreSQL](https://www.postgresql.org/) as the database engine. It's a natural complement to the use of [Fa-Db library](https://github.com/AbsaOSS/fa-db) in applications. diff --git a/balta/src/main/scala/za/co/absa/balta/classes/package.scala b/balta/src/main/scala/za/co/absa/balta/classes/package.scala deleted file mode 100644 index e24fb09..0000000 --- a/balta/src/main/scala/za/co/absa/balta/classes/package.scala +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2023 ABSA Group Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package za.co.absa.balta - -import java.sql.Connection - -package object classes { - case class JsonBString(value: String) extends AnyVal - - class DBConnection(val connection: Connection) extends AnyVal - - /** - * This is a function that sets a parameter of a prepared statement. - * - * @param dbUrl - the JDBC URL of the database - * @param username - the username to use when connecting to the database - * @param password - the password to use when connecting to the database - * @param persistData - whether to persist the data to the database (usually false for tests, set to true for - * debugging purposes) - */ - case class ConnectionInfo( - dbUrl: String, - username: String, - password: String, - persistData: Boolean - ) - -} diff --git a/balta/src/main/scala/za/co/absa/balta/DBTestSuite.scala b/balta/src/main/scala/za/co/absa/db/balta/DBTestSuite.scala similarity index 87% rename from balta/src/main/scala/za/co/absa/balta/DBTestSuite.scala rename to balta/src/main/scala/za/co/absa/db/balta/DBTestSuite.scala index 830f2ff..15e7eb8 100644 --- a/balta/src/main/scala/za/co/absa/balta/DBTestSuite.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/DBTestSuite.scala @@ -14,17 +14,17 @@ * limitations under the License. */ -package za.co.absa.balta +package za.co.absa.db.balta import org.scalactic.source import org.scalatest.Tag import org.scalatest.funsuite.AnyFunSuite -import za.co.absa.balta.classes.DBFunction.DBFunctionWithPositionedParamsOnly -import za.co.absa.balta.classes.setter.{AllowedParamTypes, Params} -import za.co.absa.balta.classes.setter.Params.{NamedParams, OrderedParams} -import za.co.absa.balta.classes.{ConnectionInfo, DBConnection, DBFunction, DBTable, QueryResult} +import za.co.absa.db.balta.classes.DBFunction.DBFunctionWithPositionedParamsOnly +import classes.setter.{AllowedParamTypes, Params} +import za.co.absa.db.balta.classes.setter.Params.{NamedParams, OrderedParams} +import classes.{DBConnection, DBFunction, DBTable, QueryResult} +import za.co.absa.db.balta.classes.simple.ConnectionInfo -import java.sql.DriverManager import java.time.OffsetDateTime import java.util.Properties @@ -39,13 +39,7 @@ abstract class DBTestSuite extends AnyFunSuite { /* the DB connection is ``lazy`, so it actually can be created only when needed and therefore the credentials overridden in the successor */ - protected lazy implicit val dbConnection: DBConnection = { - createConnection( - connectionInfo.dbUrl, - connectionInfo.username, - connectionInfo.password - ) - } + protected lazy implicit val dbConnection: DBConnection = DBConnection(connectionInfo) /** * This is the connection info for the DB. It can be overridden in the derived classes to provide specific credentials @@ -152,12 +146,6 @@ abstract class DBTestSuite extends AnyFunSuite { } // private functions - private def createConnection(url: String, username: String, password: String): DBConnection = { - val conn = DriverManager.getConnection(url, username, password) - conn.setAutoCommit(false) - new DBConnection(conn) - } - private def readConnectionInfoFromConfig = { val properties = new Properties() properties.load(getClass.getResourceAsStream("/database.properties")) diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/DBConnection.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/DBConnection.scala new file mode 100644 index 0000000..e3a1c87 --- /dev/null +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/DBConnection.scala @@ -0,0 +1,31 @@ +/* + * Copyright 2023 ABSA Group Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package za.co.absa.db.balta.classes + +import za.co.absa.db.balta.classes.simple.ConnectionInfo + +import java.sql.{Connection, DriverManager} + +class DBConnection(val connection: Connection) extends AnyVal + +object DBConnection { + def apply(connectionInfo: =>ConnectionInfo): DBConnection = { + val connection = DriverManager.getConnection(connectionInfo.dbUrl, connectionInfo.username, connectionInfo.password) + connection.setAutoCommit(false) + new DBConnection(connection) + } +} diff --git a/balta/src/main/scala/za/co/absa/balta/classes/DBFunction.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/DBFunction.scala similarity index 96% rename from balta/src/main/scala/za/co/absa/balta/classes/DBFunction.scala rename to balta/src/main/scala/za/co/absa/db/balta/classes/DBFunction.scala index 9264424..8da05f6 100644 --- a/balta/src/main/scala/za/co/absa/balta/classes/DBFunction.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/DBFunction.scala @@ -14,10 +14,11 @@ * limitations under the License. */ -package za.co.absa.balta.classes +package za.co.absa.db.balta.classes -import za.co.absa.balta.classes.DBFunction.{DBFunctionWithNamedParamsToo, DBFunctionWithPositionedParamsOnly, ParamsMap} -import za.co.absa.balta.classes.setter.{AllowedParamTypes, SetterFnc} +import DBFunction.{DBFunctionWithNamedParamsToo, DBFunctionWithPositionedParamsOnly, ParamsMap} +import za.co.absa.db.balta.classes.setter.{AllowedParamTypes, SetterFnc} +import za.co.absa.db.balta.classes.setter.{AllowedParamTypes, SetterFnc} import scala.collection.immutable.ListMap diff --git a/balta/src/main/scala/za/co/absa/balta/classes/DBQuerySupport.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/DBQuerySupport.scala similarity index 93% rename from balta/src/main/scala/za/co/absa/balta/classes/DBQuerySupport.scala rename to balta/src/main/scala/za/co/absa/db/balta/classes/DBQuerySupport.scala index 2668b68..ac3e1f1 100644 --- a/balta/src/main/scala/za/co/absa/balta/classes/DBQuerySupport.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/DBQuerySupport.scala @@ -14,9 +14,9 @@ * limitations under the License. */ -package za.co.absa.balta.classes +package za.co.absa.db.balta.classes -import za.co.absa.balta.classes.setter.SetterFnc +import za.co.absa.db.balta.classes.setter.SetterFnc /** * This is a based trait providing the ability to run an SQL query and verify the result via a provided function. diff --git a/balta/src/main/scala/za/co/absa/balta/classes/DBTable.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/DBTable.scala similarity index 96% rename from balta/src/main/scala/za/co/absa/balta/classes/DBTable.scala rename to balta/src/main/scala/za/co/absa/db/balta/classes/DBTable.scala index 0f142b1..13a2574 100644 --- a/balta/src/main/scala/za/co/absa/balta/classes/DBTable.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/DBTable.scala @@ -14,10 +14,12 @@ * limitations under the License. */ -package za.co.absa.balta.classes +package za.co.absa.db.balta.classes -import za.co.absa.balta.classes.setter.{AllowedParamTypes, Params, SetterFnc} -import za.co.absa.balta.classes.setter.Params.NamedParams +import za.co.absa.db.balta.classes.setter.{AllowedParamTypes, Params, SetterFnc} +import za.co.absa.db.balta.classes.setter.Params.NamedParams +import za.co.absa.db.balta.classes.setter.Params.NamedParams +import za.co.absa.db.balta.classes.setter.{AllowedParamTypes, Params} /** * This class represents a database table. It allows to perform INSERT, SELECT and COUNT operations on the table easily. diff --git a/balta/src/main/scala/za/co/absa/balta/classes/QueryResult.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResult.scala similarity index 97% rename from balta/src/main/scala/za/co/absa/balta/classes/QueryResult.scala rename to balta/src/main/scala/za/co/absa/db/balta/classes/QueryResult.scala index 58a0f55..a429a6a 100644 --- a/balta/src/main/scala/za/co/absa/balta/classes/QueryResult.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResult.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package za.co.absa.balta.classes +package za.co.absa.db.balta.classes import java.sql.ResultSet diff --git a/balta/src/main/scala/za/co/absa/balta/classes/QueryResultRow.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala similarity index 97% rename from balta/src/main/scala/za/co/absa/balta/classes/QueryResultRow.scala rename to balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala index 28969e5..ab474a4 100644 --- a/balta/src/main/scala/za/co/absa/balta/classes/QueryResultRow.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala @@ -14,9 +14,10 @@ * limitations under the License. */ -package za.co.absa.balta.classes +package za.co.absa.db.balta.classes import org.postgresql.util.PGobject +import za.co.absa.db.balta.classes.simple.JsonBString import java.sql.{Date, ResultSet, Time} import java.time.{Instant, OffsetDateTime} diff --git a/balta/src/main/scala/za/co/absa/balta/classes/setter/AllowedParamTypes.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/setter/AllowedParamTypes.scala similarity index 92% rename from balta/src/main/scala/za/co/absa/balta/classes/setter/AllowedParamTypes.scala rename to balta/src/main/scala/za/co/absa/db/balta/classes/setter/AllowedParamTypes.scala index 6db1171..7383c40 100644 --- a/balta/src/main/scala/za/co/absa/balta/classes/setter/AllowedParamTypes.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/setter/AllowedParamTypes.scala @@ -14,11 +14,11 @@ * limitations under the License. */ -package za.co.absa.balta.classes.setter +package za.co.absa.db.balta.classes.setter -import za.co.absa.balta.classes.JsonBString +import za.co.absa.db.balta.classes.simple.JsonBString -import java.time.{Instant, OffsetDateTime, LocalTime, LocalDate} +import java.time.{Instant, LocalDate, LocalTime, OffsetDateTime} import java.util.UUID /** diff --git a/balta/src/main/scala/za/co/absa/balta/classes/setter/CustomDBType.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/setter/CustomDBType.scala similarity index 95% rename from balta/src/main/scala/za/co/absa/balta/classes/setter/CustomDBType.scala rename to balta/src/main/scala/za/co/absa/db/balta/classes/setter/CustomDBType.scala index 78921c9..2c23ef6 100644 --- a/balta/src/main/scala/za/co/absa/balta/classes/setter/CustomDBType.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/setter/CustomDBType.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package za.co.absa.balta.classes.setter +package za.co.absa.db.balta.classes.setter /** * This is a case class representing a custom DB type. diff --git a/balta/src/main/scala/za/co/absa/balta/classes/setter/Params.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/setter/Params.scala similarity index 99% rename from balta/src/main/scala/za/co/absa/balta/classes/setter/Params.scala rename to balta/src/main/scala/za/co/absa/db/balta/classes/setter/Params.scala index fc2effb..e428b40 100644 --- a/balta/src/main/scala/za/co/absa/balta/classes/setter/Params.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/setter/Params.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package za.co.absa.balta.classes.setter +package za.co.absa.db.balta.classes.setter import scala.collection.immutable.ListMap diff --git a/balta/src/main/scala/za/co/absa/balta/classes/setter/SetterFnc.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/setter/SetterFnc.scala similarity index 97% rename from balta/src/main/scala/za/co/absa/balta/classes/setter/SetterFnc.scala rename to balta/src/main/scala/za/co/absa/db/balta/classes/setter/SetterFnc.scala index e8e3b9f..02ba649 100644 --- a/balta/src/main/scala/za/co/absa/balta/classes/setter/SetterFnc.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/setter/SetterFnc.scala @@ -14,13 +14,12 @@ * limitations under the License. */ -package za.co.absa.balta.classes.setter - -import za.co.absa.balta.classes.JsonBString +package za.co.absa.db.balta.classes.setter import java.sql.{Date, PreparedStatement, Time, Timestamp, Types => SqlTypes} import java.util.UUID import org.postgresql.util.PGobject +import za.co.absa.db.balta.classes.simple.JsonBString import java.time.{Instant, LocalDate, LocalTime, OffsetDateTime, ZoneId, ZoneOffset} diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/simple/ConnectionInfo.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/simple/ConnectionInfo.scala new file mode 100644 index 0000000..c2ff9b6 --- /dev/null +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/simple/ConnectionInfo.scala @@ -0,0 +1,33 @@ +/* + * Copyright 2023 ABSA Group Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package za.co.absa.db.balta.classes.simple + +/** + * This is a function that sets a parameter of a prepared statement. + * + * @param dbUrl - the JDBC URL of the database + * @param username - the username to use when connecting to the database + * @param password - the password to use when connecting to the database + * @param persistData - whether to persist the data to the database (usually false for tests, set to true for + * debugging purposes) + */ +case class ConnectionInfo( + dbUrl: String, + username: String, + password: String, + persistData: Boolean + ) diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/simple/JsonBString.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/simple/JsonBString.scala new file mode 100644 index 0000000..13a8b4a --- /dev/null +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/simple/JsonBString.scala @@ -0,0 +1,19 @@ +/* + * Copyright 2023 ABSA Group Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package za.co.absa.db.balta.classes.simple + +case class JsonBString(value: String) extends AnyVal diff --git a/balta/src/main/scala/za/co/absa/balta/implicits/package.scala b/balta/src/main/scala/za/co/absa/db/balta/implicits/package.scala similarity index 92% rename from balta/src/main/scala/za/co/absa/balta/implicits/package.scala rename to balta/src/main/scala/za/co/absa/db/balta/implicits/package.scala index 3c13977..c3e9388 100644 --- a/balta/src/main/scala/za/co/absa/balta/implicits/package.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/implicits/package.scala @@ -14,9 +14,9 @@ * limitations under the License. */ -package za.co.absa.balta +package za.co.absa.db.balta -import za.co.absa.balta.classes.DBConnection +import za.co.absa.db.balta.classes.DBConnection import java.sql.Connection import scala.language.implicitConversions diff --git a/build.sbt b/build.sbt index d1edc01..52093c3 100644 --- a/build.sbt +++ b/build.sbt @@ -16,8 +16,6 @@ import Dependencies._ -ThisBuild / organization := "za.co.absa" - lazy val scala211 = "2.11.12" lazy val scala212 = "2.12.18" lazy val scala213 = "2.13.11" diff --git a/publish.sbt b/publish.sbt index 93d5266..6514c8f 100644 --- a/publish.sbt +++ b/publish.sbt @@ -49,6 +49,8 @@ ThisBuild / developers := List( ) ) +ThisBuild / organization := "za.co.absa.db.balta" + ThisBuild / organizationName := "ABSA Group Limited" ThisBuild / organizationHomepage := Some(url("https://www.absa.africa")) From 9f84b1295979be014a5fde1465ff677e878a9842 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Tue, 23 Jul 2024 17:18:37 +0200 Subject: [PATCH 02/11] #4: QueryResultRow does not instantiate the row data * WIP --- .../db/balta/classes/QueryResultRow.scala | 12 ++++++++++- .../db/balta/classes/fields/RowField.scala | 21 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 balta/src/main/scala/za/co/absa/db/balta/classes/fields/RowField.scala diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala index ab474a4..6f2ff58 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala @@ -22,13 +22,21 @@ import za.co.absa.db.balta.classes.simple.JsonBString import java.sql.{Date, ResultSet, Time} import java.time.{Instant, OffsetDateTime} import java.util.UUID +import java.sql.Types /** * This is a row of a query result. It allows to safely extract values from the row by column name. * * @param resultSet - the JDBC result of a query */ -class QueryResultRow private[classes](val resultSet: ResultSet) extends AnyVal { +class QueryResultRow private[classes](val resultSet: ResultSet) /*extends AnyVal*/ { + + + private val md = resultSet.getMetaData() + + + + md.getColumnType(1) // this is not stable as resultSet mutates, but good enough for now private def safe[T](fnc: => T): Option[T] = { val result = fnc @@ -39,6 +47,8 @@ class QueryResultRow private[classes](val resultSet: ResultSet) extends AnyVal { } } + def rowNumber: Int = resultSet.getRow + def getBoolean(columnLabel: String): Option[Boolean] = safe(resultSet.getBoolean(columnLabel)) def getChar(columnLabel: String): Option[Char] = { diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/fields/RowField.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/fields/RowField.scala new file mode 100644 index 0000000..d5defe7 --- /dev/null +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/fields/RowField.scala @@ -0,0 +1,21 @@ +/* + * Copyright 2023 ABSA Group Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package za.co.absa.db.balta.classes.fields + +trait RowField { + +} From 70a5cc02006d10a0ac764c7becc3c1aaa486631b Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Fri, 16 Aug 2024 15:16:35 +0200 Subject: [PATCH 03/11] #4: QueryResultRow does not instantiate the row data * `QueryResultRow` is now instantiated, so can be used even after connection close * `JsonBString` deprecated, replaced by `SimpleJsonString` * setup for separation of unit and integration tests * integration tests database setup * github build action enhanced to support integration tests --- .github/workflows/build.yml | 22 +- .github/workflows/test_filenames_check.yml | 41 ++++ .sbtrc | 27 +++ README.md | 5 + .../za/co/absa/db/balta/DBTestSuite.scala | 2 +- .../za/co/absa/db/balta/classes/DBTable.scala | 2 - .../absa/db/balta/classes/QueryResult.scala | 36 ++- .../db/balta/classes/QueryResultRow.scala | 159 +++++++++---- .../db/balta/classes/simple/JsonBString.scala | 1 + .../SimpleJsonString.scala} | 10 +- .../co/absa/db/balta/implicits/Postgres.scala | 41 ++++ balta/src/test/resources/database.properties | 8 + .../src/test/resources/db/postgres/01_db.ddl | 6 + .../test/resources/db/postgres/02_users.ddl | 9 + .../db/postgres/03_schema_testing.ddl | 2 + .../db/postgres/04_testing.base_types.ddl | 22 ++ .../postgres/05_testing._base_types_data.sql | 13 ++ .../db/postgres/06_testing.pg_types.ddl | 11 + .../db/postgres/07_testing_pg_types_data.sql | 16 ++ .../QueryResultRowIntegrationTests.scala | 221 ++++++++++++++++++ .../PostgresRowIntegrationTests.scala | 78 +++++++ .../testing/classes/DBTestingConnection.scala | 40 ++++ 22 files changed, 703 insertions(+), 69 deletions(-) create mode 100644 .github/workflows/test_filenames_check.yml create mode 100644 .sbtrc rename balta/src/main/scala/za/co/absa/db/balta/classes/{fields/RowField.scala => simple/SimpleJsonString.scala} (68%) create mode 100644 balta/src/main/scala/za/co/absa/db/balta/implicits/Postgres.scala create mode 100644 balta/src/test/resources/database.properties create mode 100644 balta/src/test/resources/db/postgres/01_db.ddl create mode 100644 balta/src/test/resources/db/postgres/02_users.ddl create mode 100644 balta/src/test/resources/db/postgres/03_schema_testing.ddl create mode 100644 balta/src/test/resources/db/postgres/04_testing.base_types.ddl create mode 100644 balta/src/test/resources/db/postgres/05_testing._base_types_data.sql create mode 100644 balta/src/test/resources/db/postgres/06_testing.pg_types.ddl create mode 100644 balta/src/test/resources/db/postgres/07_testing_pg_types_data.sql create mode 100644 balta/src/test/scala/za/co/absa/db/balta/classes/QueryResultRowIntegrationTests.scala create mode 100644 balta/src/test/scala/za/co/absa/db/balta/implicits/PostgresRowIntegrationTests.scala create mode 100644 balta/src/test/scala/za/co/absa/db/balta/testing/classes/DBTestingConnection.scala diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 256a7e7..46f9eee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,13 +33,33 @@ jobs: name: Scala ${{matrix.scala}} + services: + postgres: + image: postgres:15 + env: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: mag_db + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + steps: - name: Checkout code uses: actions/checkout@v4 + - uses: coursier/cache-action@v5 + + - name: Setup database + run: psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/02_users.ddl + - name: Setup Scala uses: olafurpg/setup-scala@v10 with: java-version: "adopt@1.8" - - name: Build and run tests + + - name: Build and run unit tests run: sbt ++${{matrix.scala}} test doc diff --git a/.github/workflows/test_filenames_check.yml b/.github/workflows/test_filenames_check.yml new file mode 100644 index 0000000..aeaa01d --- /dev/null +++ b/.github/workflows/test_filenames_check.yml @@ -0,0 +1,41 @@ +# +# Copyright 2021 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: Test Filenames Check + +on: + pull_request: + branches: [ master ] + types: [ opened, synchronize, reopened ] + +jobs: + test_filenames_check: + name: Test Filenames Check + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Filename Inspector + id: scan-test-files + uses: AbsaOSS/filename-inspector@master + with: + name-patterns: '*UnitTests.*,*IntegrationTests.*' + paths: '**/src/test/scala/**' + report-format: 'console' + excludes: 'server/src/test/scala/za/co/absa/atum/server/api/TestData.scala,server/src/test/scala/za/co/absa/atum/server/api/TestTransactorProvider.scala,server/src/test/scala/za/co/absa/atum/server/ConfigProviderTest.scala' + verbose-logging: 'false' + fail-on-violation: 'true' diff --git a/.sbtrc b/.sbtrc new file mode 100644 index 0000000..5f69dcb --- /dev/null +++ b/.sbtrc @@ -0,0 +1,27 @@ +# +# Copyright 2023 ABSA Group Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Aliases in this file expected usage of test file naming conventions: +# - "UnitTests" suffix for test files and Suites which define unit tests +# - "IntegrationTests" suffix for test files and Suites which define integration tests + +# CPS QA types aliases +# * Unit tests +alias test=; testOnly *UnitTests + +# * Integration tests +alias testIT=; testOnly *IntegrationTests + diff --git a/README.md b/README.md index ac42f3b..07f393c 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,8 @@ in a known state before and after each test. ## How to Release Please see [this file](RELEASE.md) for more details. + +## Known Issues + +### Postgres +* `TIMESTAMP WITH TIME ZONE[]`, `TIME WITH TIME ZONE[]`, generally arrays of time related types are not translated to appropriate time zone aware Scala/Java types diff --git a/balta/src/main/scala/za/co/absa/db/balta/DBTestSuite.scala b/balta/src/main/scala/za/co/absa/db/balta/DBTestSuite.scala index 15e7eb8..ba310ed 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/DBTestSuite.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/DBTestSuite.scala @@ -146,7 +146,7 @@ abstract class DBTestSuite extends AnyFunSuite { } // private functions - private def readConnectionInfoFromConfig = { + private def readConnectionInfoFromConfig: ConnectionInfo = { val properties = new Properties() properties.load(getClass.getResourceAsStream("/database.properties")) diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/DBTable.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/DBTable.scala index 13a2574..9281125 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/classes/DBTable.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/DBTable.scala @@ -18,8 +18,6 @@ package za.co.absa.db.balta.classes import za.co.absa.db.balta.classes.setter.{AllowedParamTypes, Params, SetterFnc} import za.co.absa.db.balta.classes.setter.Params.NamedParams -import za.co.absa.db.balta.classes.setter.Params.NamedParams -import za.co.absa.db.balta.classes.setter.{AllowedParamTypes, Params} /** * This class represents a database table. It allows to perform INSERT, SELECT and COUNT operations on the table easily. diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResult.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResult.scala index a429a6a..81623a3 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResult.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResult.scala @@ -16,7 +16,9 @@ package za.co.absa.db.balta.classes -import java.sql.ResultSet +import za.co.absa.db.balta.classes.QueryResultRow.{Extractors, FieldNames} + +import java.sql.{ResultSet, ResultSetMetaData, SQLException} /** * This is an iterator over the result of a query. @@ -24,24 +26,34 @@ import java.sql.ResultSet * @param resultSet - the JDBC result of a query */ class QueryResult(resultSet: ResultSet) extends Iterator[QueryResultRow] { - private [this] var resultSetHasNext: Option[Boolean] = Some(resultSet.next()) + val resultSetMetaData: ResultSetMetaData = resultSet.getMetaData + val columnCount: Int = resultSetMetaData.getColumnCount + + private [this] var nextRow: Option[QueryResultRow] = None + + private [this] implicit val fieldNames: FieldNames = QueryResultRow.fieldNamesFromMetdata(resultSetMetaData) + private [this] implicit val extractors: Extractors = QueryResultRow.createExtractors(resultSetMetaData) override def hasNext: Boolean = { - resultSetHasNext.getOrElse { - val result = resultSet.next() - resultSetHasNext = Some(result) - result + if (nextRow.isEmpty) { + try { + if (resultSet.next()) { + nextRow = Some(QueryResultRow(resultSet)) + } + } catch { + case e: SQLException => throw e // TODO Do nothing + } } + nextRow.nonEmpty } override def next(): QueryResultRow = { - if (resultSetHasNext.isEmpty) { - resultSet.next() - new QueryResultRow(resultSet) + if (hasNext) { + val row = nextRow.get + nextRow = None + row } else { - resultSetHasNext = None - new QueryResultRow(resultSet) - + throw new NoSuchElementException("No more rows in the result set") } } } diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala index 6f2ff58..6af8b3e 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala @@ -16,93 +16,152 @@ package za.co.absa.db.balta.classes +import QueryResultRow._ +import org.postgresql.jdbc.PgArray import org.postgresql.util.PGobject -import za.co.absa.db.balta.classes.simple.JsonBString -import java.sql.{Date, ResultSet, Time} -import java.time.{Instant, OffsetDateTime} +import java.sql +import java.sql.{Date, ResultSet, ResultSetMetaData, SQLException, Time, Types} +import java.time.{Instant, LocalDateTime, LocalTime, OffsetDateTime, OffsetTime} import java.util.UUID -import java.sql.Types +import scala.util.Try /** * This is a row of a query result. It allows to safely extract values from the row by column name. * - * @param resultSet - the JDBC result of a query + * @param rowNumber - the number of the row in the result set + * @param fields - the values of the row + * @param columnNames - the names of the columns */ -class QueryResultRow private[classes](val resultSet: ResultSet) /*extends AnyVal*/ { +class QueryResultRow private[classes](val rowNumber: Int, + private val fields: Vector[Option[Object]], + private val columnNames: FieldNames) { + def columnCount: Int = fields.length + def columnNumber(columnLabel: String): Int = columnNames(columnLabel.toLowerCase) - private val md = resultSet.getMetaData() + def apply(column: Int): Option[Object] = fields(column - 1) + def apply(columnLabel: String): Option[Object] = apply(columnNumber(columnLabel)) + def getAs[T](column: Int, transformer: TransformerFnc[T]): Option[T] = apply(column).map(transformer) + def getAs[T](column: Int): Option[T] = apply(column)map(_.asInstanceOf[T]) + def getAs[T](columnLabel: String, transformer: TransformerFnc[T]): Option[T] = getAs(columnNumber(columnLabel), transformer) + def getAs[T](columnLabel: String): Option[T] = apply(columnNumber(columnLabel)).map(_.asInstanceOf[T]) - md.getColumnType(1) - // this is not stable as resultSet mutates, but good enough for now - private def safe[T](fnc: => T): Option[T] = { - val result = fnc - if (resultSet.wasNull()) { - None - } else { - Some(result) - } - } - - def rowNumber: Int = resultSet.getRow - - def getBoolean(columnLabel: String): Option[Boolean] = safe(resultSet.getBoolean(columnLabel)) + def getBoolean(column: Int): Option[Boolean] = getAs(column: Int, _.asInstanceOf[Boolean]) + def getBoolean(columnLabel: String): Option[Boolean] = getBoolean(columnNumber(columnLabel)) - def getChar(columnLabel: String): Option[Char] = { - getString(columnLabel) match { + def getChar(column: Int): Option[Char] = { + getString(column) match { case Some(value) => if (value.isEmpty) None else Some(value.charAt(0)) case None => None } } + def getChar(columnLabel: String): Option[Char] = getChar(columnNumber(columnLabel)) - def getString(columnLabel: String): Option[String] = safe(resultSet.getString(columnLabel)) - def getInt(columnLabel: String): Option[Int] = safe(resultSet.getInt(columnLabel)) + def getString(column: Int): Option[String] = getAs(column: Int, _.asInstanceOf[String]) + def getString(columnLabel: String): Option[String] = getString(columnNumber(columnLabel)) - def getLong(columnLabel: String): Option[Long] = safe(resultSet.getLong(columnLabel)) + def getInt(column: Int): Option[Int] = getAs(column: Int, _.asInstanceOf[Int]) + def getInt(columnLabel: String): Option[Int] = getInt(columnNumber(columnLabel)) - def getDouble(columnLabel: String): Option[Double] = safe(resultSet.getDouble(columnLabel)) + def getLong(column: Int): Option[Long] = getAs(column: Int, _.asInstanceOf[Long]) + def getLong(columnLabel: String): Option[Long] = getLong(columnNumber(columnLabel)) - def getFloat(columnLabel: String): Option[Float] = safe(resultSet.getFloat(columnLabel)) + def getDouble(column: Int): Option[Double] = getAs(column: Int, _.asInstanceOf[Double]) + def getDouble(columnLabel: String): Option[Double] = getDouble(columnNumber(columnLabel)) - def getBigDecimal(columnLabel: String): Option[BigDecimal] = safe(resultSet.getBigDecimal(columnLabel)) + def getFloat(column: Int): Option[Float] = getAs(column: Int, _.asInstanceOf[Float]) + def getFloat(columnLabel: String): Option[Float] = getFloat(columnNumber(columnLabel)) - def getUUID(columnLabel: String): Option[UUID] = Option(resultSet.getObject(columnLabel).asInstanceOf[UUID]) + def getBigDecimal(column: Int): Option[BigDecimal] = getAs(column: Int, _.asInstanceOf[java.math.BigDecimal]).map(scala.math.BigDecimal(_)) + def getBigDecimal(columnLabel: String): Option[BigDecimal] = getBigDecimal(columnNumber(columnLabel)) - def getOffsetDateTime(columnLabel: String): Option[OffsetDateTime] = Option(resultSet.getObject(columnLabel, classOf[OffsetDateTime])) + def getTime(column: Int): Option[Time] = getAs(column: Int, _.asInstanceOf[Time]) + def getTime(columnLabel: String): Option[Time] = getTime(columnNumber(columnLabel)) - def getInstant(columnLabel: String): Option[Instant] = getOffsetDateTime(columnLabel).map(_.toInstant) + def getDate(column: Int): Option[Date] = getAs(column: Int, _.asInstanceOf[Date]) + def getDate(columnLabel: String): Option[Date] = getDate(columnNumber(columnLabel)) - def getTime(columnLabel: String): Option[Time] = safe(resultSet.getTime(columnLabel)) + def getLocalDateTime(column: Int): Option[LocalDateTime] = getAs(column: Int, _.asInstanceOf[LocalDateTime]) + def getLocalDateTime(columnLabel: String): Option[LocalDateTime] = getLocalDateTime(columnNumber(columnLabel)) - def getDate(columnLabel: String): Option[Date] = safe(resultSet.getDate(columnLabel)) + def getOffsetDateTime(column: Int): Option[OffsetDateTime] = getAs(column: Int, _.asInstanceOf[OffsetDateTime]) + def getOffsetDateTime(columnLabel: String): Option[OffsetDateTime] = getOffsetDateTime(columnNumber(columnLabel)) - def getJsonB(columnLabel: String): Option[JsonBString] = { - Option(resultSet.getObject(columnLabel).asInstanceOf[PGobject])map(pgo => JsonBString(pgo.toString)) - } + def getInstant(column: Int): Option[Instant] = getOffsetDateTime(column).map(_.toInstant) + def getInstant(columnLabel: String): Option[Instant] = getOffsetDateTime(columnLabel).map(_.toInstant) - def getArray[T](columnLabel: String): Option[Array[T]] = { - val array = resultSet.getArray(columnLabel) - if (resultSet.wasNull()) { - None - } else { - Option(array.getArray.asInstanceOf[Array[T]]) + def getUUID(column: Int): Option[UUID] = getAs(column: Int, _.asInstanceOf[UUID]) + def getUUID(columnLabel: String): Option[UUID] = getUUID(columnNumber(columnLabel)) + + def getArray[T](column: Int): Option[Vector[T]] = { + def transformerFnc(obj: Object): Vector[T] = { + obj.asInstanceOf[sql.Array].getArray().asInstanceOf[Array[T]].toVector } + getAs(column: Int, transformerFnc _) //TODO } - def getAs[T](columnLabel: String): Option[T] = { - val result = resultSet.getObject(columnLabel) - if (resultSet.wasNull()) { - None - } else { - val resultTyped = result.asInstanceOf[T] - Option(resultTyped) + def getArray[T](columnLabel: String): Option[Vector[T]] = getArray[T](columnNumber(columnLabel)) + + def getArray[T](column: Int, itemTransformerFnc: TransformerFnc[T]): Option[Vector[T]] = { + def transformerFnc(obj: Object): Vector[T] = { + obj + .asInstanceOf[sql.Array] + .getArray() + .asInstanceOf[Array[Object]] + .toVector + .map(itemTransformerFnc) } + + getAs(column: Int, transformerFnc _) } } + +object QueryResultRow { + + type FieldNames = Map[String, Int] + type TransformerFnc[T] = Object => T + type Extractors = Vector[ResultSet => Option[Object]] + + def apply(resultSet: ResultSet)(implicit fieldNames: FieldNames, extractors: Extractors): QueryResultRow = { + val fields = extractors.map(_(resultSet)) + new QueryResultRow(resultSet.getRow, fields, fieldNames) + } + + def fieldNamesFromMetdata(metaData: ResultSetMetaData): FieldNames = { + Range.inclusive(1, metaData.getColumnCount).map(i => metaData.getColumnName(i) -> i).toMap + } + + def createExtractors(metaData: ResultSetMetaData): Extractors = { + def generalExtractor(resultSet: ResultSet, column: Int): Option[Object] = Option(resultSet.getObject(column)) + + def timeTzExtractor(resultSet: ResultSet, column: Int): Option[Object] = Option(resultSet.getObject(column, classOf[OffsetTime])) + + def timestampExtractor(resultSet: ResultSet, column: Int): Option[Object] = Option(resultSet.getObject(column, classOf[LocalDateTime])) + + def timestampTzExtractor(resultSet: ResultSet, column: Int): Option[Object] = Option(resultSet.getObject(column, classOf[OffsetDateTime])) + + def arrayExtractor(resultSet: ResultSet, column: Int): Option[Object] = { + val array: sql.Array = resultSet.getArray(column) + Option(array) + } + + def columnTypeName(column: Int): String = metaData.getColumnTypeName(column).toLowerCase() + + Range.inclusive(1, metaData.getColumnCount).map { column => + metaData.getColumnType(column) match { + case Types.TIME if columnTypeName(column) == "timetz" => timeTzExtractor(_, column) + case Types.TIMESTAMP if columnTypeName(column) == "timestamptz" => timestampTzExtractor(_, column) + case Types.TIMESTAMP => timestampExtractor(_, column) + case Types.ARRAY => arrayExtractor(_, column) + case _ => generalExtractor(_, column) + } + }.toVector + } +} diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/simple/JsonBString.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/simple/JsonBString.scala index 13a8b4a..b2340b8 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/classes/simple/JsonBString.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/simple/JsonBString.scala @@ -16,4 +16,5 @@ package za.co.absa.db.balta.classes.simple +@deprecated("Use SimpleJsonString instead", "0.2.0") case class JsonBString(value: String) extends AnyVal diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/fields/RowField.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/simple/SimpleJsonString.scala similarity index 68% rename from balta/src/main/scala/za/co/absa/db/balta/classes/fields/RowField.scala rename to balta/src/main/scala/za/co/absa/db/balta/classes/simple/SimpleJsonString.scala index d5defe7..d712c49 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/classes/fields/RowField.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/simple/SimpleJsonString.scala @@ -14,8 +14,12 @@ * limitations under the License. */ -package za.co.absa.db.balta.classes.fields +package za.co.absa.db.balta.classes.simple -trait RowField { +/** + * A simple class to signal a JSON string is expected. No validation is performed. No extended comparison functionality + * is provided. + * @param value A JSON string. + */ +case class SimpleJsonString(value: String) extends AnyVal -} diff --git a/balta/src/main/scala/za/co/absa/db/balta/implicits/Postgres.scala b/balta/src/main/scala/za/co/absa/db/balta/implicits/Postgres.scala new file mode 100644 index 0000000..5b19ab0 --- /dev/null +++ b/balta/src/main/scala/za/co/absa/db/balta/implicits/Postgres.scala @@ -0,0 +1,41 @@ +/* + * Copyright 2023 ABSA Group Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package za.co.absa.db.balta.implicits + +import org.postgresql.util.PGobject +import za.co.absa.db.balta.classes.QueryResultRow +import za.co.absa.db.balta.classes.simple.{JsonBString, SimpleJsonString} + +object Postgres { + implicit class PostgresRow(val row: QueryResultRow) extends AnyVal { + private def jsonBStringTransformer(obj: Object): JsonBString = JsonBString(obj.asInstanceOf[PGobject].toString) + private def simpleJsonStringTransformer(obj: Object): SimpleJsonString = SimpleJsonString(obj.asInstanceOf[PGobject].toString) + + @deprecated("Use getSimpleJsonString instead", "0.2.0") + def getJsonB(column: Int): Option[JsonBString] = row.getAs[JsonBString](column: Int, jsonBStringTransformer _) + @deprecated("Use getSimpleJsonString instead", "0.2.0") + def getJsonB(columnLabel: String): Option[JsonBString] = getJsonB(row.columnNumber(columnLabel)) + + def getSimpleJsonString(column: Int): Option[SimpleJsonString] = row.getAs[SimpleJsonString](column: Int, simpleJsonStringTransformer _) + def getSimpleJsonString(columnLabel: String): Option[SimpleJsonString] = getSimpleJsonString(row.columnNumber(columnLabel)) + + def getSJSArray(column: Int): Option[Vector[SimpleJsonString]] = + row.getArray(column: Int, item => SimpleJsonString(item.asInstanceOf[String])) + def getSJSArray(columnLabel: String): Option[Vector[SimpleJsonString]] = getSJSArray(row.columnNumber(columnLabel)) + + } +} diff --git a/balta/src/test/resources/database.properties b/balta/src/test/resources/database.properties new file mode 100644 index 0000000..6927c67 --- /dev/null +++ b/balta/src/test/resources/database.properties @@ -0,0 +1,8 @@ +# jdbc settings +test.jdbc.url=jdbc:postgresql://localhost:5432/mag_db + +test.jdbc.username=mag_owner +test.jdbc.password=changeme + +# debug - Hint: Use `.only` to run one selected test only. Test are not designed for parallel run. +test.persist.db=false diff --git a/balta/src/test/resources/db/postgres/01_db.ddl b/balta/src/test/resources/db/postgres/01_db.ddl new file mode 100644 index 0000000..f0ac329 --- /dev/null +++ b/balta/src/test/resources/db/postgres/01_db.ddl @@ -0,0 +1,6 @@ +CREATE DATABASE mag_db + WITH + OWNER = postgres + ENCODING = 'UTF8' + TABLESPACE = pg_default + CONNECTION LIMIT = -1; \ No newline at end of file diff --git a/balta/src/test/resources/db/postgres/02_users.ddl b/balta/src/test/resources/db/postgres/02_users.ddl new file mode 100644 index 0000000..7ab9180 --- /dev/null +++ b/balta/src/test/resources/db/postgres/02_users.ddl @@ -0,0 +1,9 @@ +CREATE ROLE mag_owner WITH + LOGIN + NOSUPERUSER + NOCREATEDB + NOCREATEROLE + INHERIT + NOREPLICATION + CONNECTION LIMIT -1 + PASSWORD 'changeme'; \ No newline at end of file diff --git a/balta/src/test/resources/db/postgres/03_schema_testing.ddl b/balta/src/test/resources/db/postgres/03_schema_testing.ddl new file mode 100644 index 0000000..24da2d3 --- /dev/null +++ b/balta/src/test/resources/db/postgres/03_schema_testing.ddl @@ -0,0 +1,2 @@ +CREATE SCHEMA IF NOT EXISTS testing + AUTHORIZATION mag_owner; \ No newline at end of file diff --git a/balta/src/test/resources/db/postgres/04_testing.base_types.ddl b/balta/src/test/resources/db/postgres/04_testing.base_types.ddl new file mode 100644 index 0000000..d12c348 --- /dev/null +++ b/balta/src/test/resources/db/postgres/04_testing.base_types.ddl @@ -0,0 +1,22 @@ +DROP TABLE IF EXISTS testing.base_types; + +CREATE TABLE IF NOT EXISTS testing.base_types +( + long_type bigint, + boolean_type boolean, + char_type "char", + string_type text COLLATE pg_catalog."default", + int_type integer, + double_type double precision, + float_type real, + bigdecimal_type numeric(25,10), + date_type date, + time_type time without time zone, + timestamp_type timestamp without time zone, + timestamptz_type timestamp with time zone, + uuid_type uuid, + array_int_type integer[] +); + +ALTER TABLE IF EXISTS testing.base_types + OWNER to mag_owner; \ No newline at end of file diff --git a/balta/src/test/resources/db/postgres/05_testing._base_types_data.sql b/balta/src/test/resources/db/postgres/05_testing._base_types_data.sql new file mode 100644 index 0000000..192ca6d --- /dev/null +++ b/balta/src/test/resources/db/postgres/05_testing._base_types_data.sql @@ -0,0 +1,13 @@ +DELETE FROM testing.base_types; + +INSERT INTO testing.base_types( + long_type, boolean_type, char_type, string_type, int_type, double_type, float_type, bigdecimal_type, date_type, + time_type, timestamp_type, timestamptz_type, + uuid_type, array_int_type) +VALUES (1, true, 'a', 'hello world', 257, 3.14, 2.71, 123456789.0123456789, '2022-08-09'::date, + '10:12:15'::time, '2020-06-02 01:00:00'::timestamp without time zone, '2021-04-03 11:00:00 CET'::timestamp with time zone, + '090416f8-7da0-4598-844b-63659334e5b6'::UUID, '{1,2,3}'::INTEGER[]); + +INSERT INTO testing.base_types( + long_type) +VALUES (NULL); \ No newline at end of file diff --git a/balta/src/test/resources/db/postgres/06_testing.pg_types.ddl b/balta/src/test/resources/db/postgres/06_testing.pg_types.ddl new file mode 100644 index 0000000..062d571 --- /dev/null +++ b/balta/src/test/resources/db/postgres/06_testing.pg_types.ddl @@ -0,0 +1,11 @@ +CREATE TABLE testing.pg_types +( + id bigint NOT NULL, + json_type json, + jsonb_type jsonb, + array_of_json_type json[], + PRIMARY KEY (id) +); + +ALTER TABLE IF EXISTS testing.pg_types + OWNER to mag_owner; \ No newline at end of file diff --git a/balta/src/test/resources/db/postgres/07_testing_pg_types_data.sql b/balta/src/test/resources/db/postgres/07_testing_pg_types_data.sql new file mode 100644 index 0000000..51e0a8e --- /dev/null +++ b/balta/src/test/resources/db/postgres/07_testing_pg_types_data.sql @@ -0,0 +1,16 @@ +DELETE FROM + testing.pg_types; + + +INSERT INTO testing.pg_types( + id, json_type, jsonb_type, array_of_json_type) +VALUES ( + 1, + '{"a": 1, "b": "Hello"}'::JSON, + '{"Hello" : "World"}'::JSONB, + array['{"a": 2, "body": "Hold the line!"}', '{"a": 3, "body": ""}', '{"a": 4}']::json[] + ); + + +INSERT INTO testing.pg_types(id) +VALUES (2); diff --git a/balta/src/test/scala/za/co/absa/db/balta/classes/QueryResultRowIntegrationTests.scala b/balta/src/test/scala/za/co/absa/db/balta/classes/QueryResultRowIntegrationTests.scala new file mode 100644 index 0000000..907ae1a --- /dev/null +++ b/balta/src/test/scala/za/co/absa/db/balta/classes/QueryResultRowIntegrationTests.scala @@ -0,0 +1,221 @@ +/* + * Copyright 2023 ABSA Group Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package za.co.absa.db.balta.classes + +import org.scalatest.funsuite.AnyFunSuiteLike +import za.co.absa.db.balta.testing.classes.DBTestingConnection + +import java.sql.{Date, ResultSetMetaData, Time} +import java.text.SimpleDateFormat +import java.time.{Instant, LocalDateTime, OffsetDateTime} +import java.util.UUID + +class QueryResultRowIntegrationTests extends AnyFunSuiteLike with DBTestingConnection{ + private val (tableRows: List[QueryResultRow], metadata: ResultSetMetaData) = DBTable("testing.base_types").all("long_type") { q => + (q.toList, q.resultSetMetaData) + } + + test("fieldNamesFromMetada") { + val result = QueryResultRow.fieldNamesFromMetdata(metadata) + + val expecedResult = Seq( + "long_type", + "boolean_type", + "char_type", + "string_type", + "int_type", + "double_type", + "float_type", + "bigdecimal_type", + "date_type", + "time_type", + "timestamp_type", + "timestamptz_type", + "uuid_type", + "array_int_type") + .zipWithIndex + .map(x => (x._1, x._2 + 1)) + .toMap + assert(result.size == 14) + assert(result == expecedResult) + } + + test("getLong") { + //first row + assert(tableRows.head.getLong(1).contains(1)) + assert(tableRows.head.getLong("long_type").contains(1)) + assert(tableRows.head.getAs[Long](1).contains(1)) + assert(tableRows.head.getAs[Long]("long_type").contains(1)) + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getLong(1).isEmpty) + assert(secondRow.getLong("long_type").isEmpty) + } + + test("getBoolean") { + //first row + assert(tableRows.head.getBoolean(2).contains(true)) + assert(tableRows.head.getBoolean("boolean_type").contains(true)) + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getBoolean(2).isEmpty) + assert(secondRow.getBoolean("boolean_type").isEmpty) + } + + test("getChar") { + //first row + assert(tableRows.head.getChar(3).contains('a')) + assert(tableRows.head.getChar("char_type").contains('a')) + assert(tableRows.head.getChar(4).contains('h')) + assert(tableRows.head.getChar("string_type").contains('h')) + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getChar(3).isEmpty) + assert(secondRow.getChar("char_type").isEmpty) + } + + test("getString") { + //first row + assert(tableRows.head.getString(4).contains("hello world")) + assert(tableRows.head.getString("string_type").contains("hello world")) + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getString(2).isEmpty) + assert(secondRow.getString("string_type").isEmpty) + } + + test("getInt") { + //first row + assert(tableRows.head.getInt(5).contains(257)) + assert(tableRows.head.getInt("int_type").contains(257)) + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getInt(5).isEmpty) + assert(secondRow.getInt("int_type").isEmpty) + } + + test("getDouble") { + //first row + assert(tableRows.head.getDouble(6).contains(3.14)) + assert(tableRows.head.getDouble("double_type").contains(3.14)) + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getDouble(6).isEmpty) + assert(secondRow.getDouble("double_type").isEmpty) + } + + test("getFloat") { + //first row + assert(tableRows.head.getFloat(7).contains(2.71F)) + assert(tableRows.head.getFloat("float_type").contains(2.71F)) + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getFloat(7).isEmpty) + assert(secondRow.getFloat("float_type").isEmpty) + } + + test("getBigDecimal") { + //first row + assert(tableRows.head.getBigDecimal(8).contains(BigDecimal("123456789.0123456789"))) + assert(tableRows.head.getBigDecimal("bigdecimal_type").contains(BigDecimal("123456789.0123456789"))) + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getBigDecimal(8).isEmpty) + assert(secondRow.getBigDecimal("bigdecimal_type").isEmpty) + } + + test("getDate") { + //first row + val df = new SimpleDateFormat("yyyy-MM-dd"); + val expected = new Date(df.parse("2022-08-09").getTime) + assert(tableRows.head.getDate(9).contains(expected)) + assert(tableRows.head.getDate("date_type").contains(expected)) + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getDate(9).isEmpty) + assert(secondRow.getDate("date_type").isEmpty) + } + + test("getTime") { + //first row + val df = new SimpleDateFormat("hh:mm:ss") + val expected = new Time(df.parse("10:12:15").getTime) + assert(tableRows.head.getTime(10).contains(expected)) + assert(tableRows.head.getTime("time_type").contains(expected)) + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getTime(10).isEmpty) + assert(secondRow.getTime("time_type").isEmpty) + } + + + test("getLocalDateTime") { + //first row + val timestampString = "2020-06-02T01:00:00" + val expectedLocalDateTime = LocalDateTime.parse(s"$timestampString") + + assert(tableRows.head.getLocalDateTime(11).contains(expectedLocalDateTime)) + assert(tableRows.head.getLocalDateTime("timestamp_type").contains(expectedLocalDateTime)) + + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getLocalDateTime(11).isEmpty) + assert(secondRow.getLocalDateTime("timestamp_type").isEmpty) + } + + test("getOffsetDateTime and getInstant") { + //first row + val expectedOffsetDateTime = OffsetDateTime.parse("2021-04-03T11:00:00+01:00") + val expectedInstant = Instant.parse("2021-04-03T10:00:00.00Z") + + val resultOfColumn = tableRows.head.getOffsetDateTime(12).get + val resultOfColumnLabel = tableRows.head.getOffsetDateTime("timestamptz_type").get + assert(resultOfColumn.isEqual(expectedOffsetDateTime)) + assert(resultOfColumnLabel.isEqual(expectedOffsetDateTime)) + assert(tableRows.head.getInstant(12).contains(expectedInstant)) + assert(tableRows.head.getInstant("timestamptz_type").contains(expectedInstant)) + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getOffsetDateTime(12).isEmpty) + assert(secondRow.getOffsetDateTime("timestamptz_type").isEmpty) + assert(secondRow.getInstant(12).isEmpty) + assert(secondRow.getInstant("timestamptz_type").isEmpty) + } + + test("getUUID") { + //first row + val expected = UUID.fromString("090416f8-7da0-4598-844b-63659334e5b6") + assert(tableRows.head.getUUID(13).contains(expected)) + assert(tableRows.head.getUUID("uuid_type").contains(expected)) + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getUUID(13).isEmpty) + assert(secondRow.getUUID("uuid_type").isEmpty) + } + + test("getVector[Int]") { + //first row + val expected = Vector(1, 2, 3) + assert(tableRows.head.getArray[Int](14).contains(expected)) + assert(tableRows.head.getArray[Int]("array_int_type").contains(expected)) + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getArray[Int](14).isEmpty) + assert(secondRow.getArray[Int]("array_int_type").isEmpty) + } + +} diff --git a/balta/src/test/scala/za/co/absa/db/balta/implicits/PostgresRowIntegrationTests.scala b/balta/src/test/scala/za/co/absa/db/balta/implicits/PostgresRowIntegrationTests.scala new file mode 100644 index 0000000..c52c034 --- /dev/null +++ b/balta/src/test/scala/za/co/absa/db/balta/implicits/PostgresRowIntegrationTests.scala @@ -0,0 +1,78 @@ +/* + * Copyright 2023 ABSA Group Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package za.co.absa.db.balta.implicits + +import org.scalatest.funsuite.AnyFunSuiteLike +import za.co.absa.db.balta.classes.{DBTable, QueryResultRow} +import za.co.absa.db.balta.testing.classes.DBTestingConnection + +import java.sql.ResultSetMetaData +import Postgres.PostgresRow +import za.co.absa.db.balta.classes.simple.SimpleJsonString + +class PostgresRowIntegrationTests extends AnyFunSuiteLike with DBTestingConnection{ + private val (tableRows: List[QueryResultRow], metadata: ResultSetMetaData) = DBTable("testing.pg_types").all("id") { q => + (q.toList, q.resultSetMetaData) + } + + test("fieldNamesFromMetada") { + val result = QueryResultRow.fieldNamesFromMetdata(metadata) + + val expecedResult = Seq( + "id", + "json_type", + "jsonb_type", + "array_of_json_type" + ) + .zipWithIndex + .map(x => (x._1, x._2 + 1)) + .toMap + assert(result.size == 4) + assert(result == expecedResult) + } + + test("getSimpleJson") { + //first row + val expectedJson = SimpleJsonString("""{"a": 1, "b": "Hello"}""") + val expectedJsonB = SimpleJsonString("""{"Hello": "World"}""") + assert(tableRows.head.getSimpleJsonString(2).contains(expectedJson)) + assert(tableRows.head.getSimpleJsonString("json_type").contains(expectedJson)) + assert(tableRows.head.getSimpleJsonString(3).contains(expectedJsonB)) + assert(tableRows.head.getSimpleJsonString("jsonb_type").contains(expectedJsonB)) + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getSimpleJsonString(2).isEmpty) + assert(secondRow.getSimpleJsonString("json_type").isEmpty) + assert(secondRow.getSimpleJsonString(3).isEmpty) + assert(secondRow.getSimpleJsonString("jsonb_type").isEmpty) + } + + test("getArrayJson") { + //first row + val expected = Vector( + SimpleJsonString("""{"a": 2, "body": "Hold the line!"}"""), + SimpleJsonString("""{"a": 3, "body": ""}"""), + SimpleJsonString("""{"a": 4}""") + ) + assert(tableRows.head.getSJSArray(4).get == (expected)) + assert(tableRows.head.getSJSArray("array_of_json_type").contains(expected)) + //second row + val secondRow = tableRows.tail.head + assert(secondRow.getSJSArray(4).isEmpty) + assert(secondRow.getSJSArray("array_of_json_type").isEmpty) + } +} diff --git a/balta/src/test/scala/za/co/absa/db/balta/testing/classes/DBTestingConnection.scala b/balta/src/test/scala/za/co/absa/db/balta/testing/classes/DBTestingConnection.scala new file mode 100644 index 0000000..f604639 --- /dev/null +++ b/balta/src/test/scala/za/co/absa/db/balta/testing/classes/DBTestingConnection.scala @@ -0,0 +1,40 @@ +/* + * Copyright 2023 ABSA Group Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package za.co.absa.db.balta.testing.classes + +import za.co.absa.db.balta.classes.DBConnection +import za.co.absa.db.balta.classes.simple.ConnectionInfo + +import java.util.Properties + +trait DBTestingConnection { + lazy implicit val connection: DBConnection = DBConnection(connectionInfo) + + protected lazy val connectionInfo: ConnectionInfo = readConnectionInfoFromConfig + + private def readConnectionInfoFromConfig: ConnectionInfo = { + val properties = new Properties() + properties.load(getClass.getResourceAsStream("/database.properties")) + + ConnectionInfo( + dbUrl = properties.getProperty("test.jdbc.url"), + username = properties.getProperty("test.jdbc.username"), + password = properties.getProperty("test.jdbc.password"), + persistData = properties.getProperty("test.persist.db", "false").toBoolean + ) + } +} From 5d671573dc5c29685028425f9dea202ea3058350 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Fri, 16 Aug 2024 18:16:24 +0200 Subject: [PATCH 04/11] * fixed filename exclusions * running integration tests in workflow * full DB setup --- .github/workflows/build.yml | 11 ++++++++++- .github/workflows/test_filenames_check.yml | 2 +- .../za/co/absa/db/balta/classes/QueryResultRow.scala | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 46f9eee..cb08508 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,7 +54,13 @@ jobs: - uses: coursier/cache-action@v5 - name: Setup database - run: psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/02_users.ddl + run: | + psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/02_users.ddl + psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/03_schema_testing.ddl + psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/04_testing.base_types.ddl + psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/05_testing._base_types_data.sql + psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/06_testing.pg_types.ddl + psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/07_testing_pg_types_data.sql - name: Setup Scala uses: olafurpg/setup-scala@v10 @@ -63,3 +69,6 @@ jobs: - name: Build and run unit tests run: sbt ++${{matrix.scala}} test doc + + - name: Build and run integration tests + run: sbt ++${{matrix.scala}} testIT diff --git a/.github/workflows/test_filenames_check.yml b/.github/workflows/test_filenames_check.yml index aeaa01d..e94aa77 100644 --- a/.github/workflows/test_filenames_check.yml +++ b/.github/workflows/test_filenames_check.yml @@ -36,6 +36,6 @@ jobs: name-patterns: '*UnitTests.*,*IntegrationTests.*' paths: '**/src/test/scala/**' report-format: 'console' - excludes: 'server/src/test/scala/za/co/absa/atum/server/api/TestData.scala,server/src/test/scala/za/co/absa/atum/server/api/TestTransactorProvider.scala,server/src/test/scala/za/co/absa/atum/server/ConfigProviderTest.scala' + excludes: 'balta/src/test/scala/za/co/absa/db/balta/testing/*' verbose-logging: 'false' fail-on-violation: 'true' diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala index 4a20ddb..7c771ad 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala @@ -49,7 +49,7 @@ class QueryResultRow private[classes](val rowNumber: Int, def getAs[T](columnLabel: String, transformer: TransformerFnc[T]): Option[T] = getAs(columnNumber(columnLabel), transformer) def getAs[T](columnLabel: String): Option[T] = apply(columnNumber(columnLabel)).map(_.asInstanceOf[T]) - def getBoolean(column: Int): Option[Boolean] = getAs(column: Int, _.asInstanceOf[Boolean]) + def getBoolean(column: Int): Option[Boolean] = getAs(column: Int, item => item.asInstanceOf[Boolean]) def getBoolean(columnLabel: String): Option[Boolean] = getBoolean(columnNumber(columnLabel)) def getChar(column: Int): Option[Char] = { From ac194942e70a0d66f73c2db86e23881f8438caa2 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Fri, 16 Aug 2024 18:32:04 +0200 Subject: [PATCH 05/11] Fix --- .../main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala index 7c771ad..b18bb2b 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala @@ -49,7 +49,7 @@ class QueryResultRow private[classes](val rowNumber: Int, def getAs[T](columnLabel: String, transformer: TransformerFnc[T]): Option[T] = getAs(columnNumber(columnLabel), transformer) def getAs[T](columnLabel: String): Option[T] = apply(columnNumber(columnLabel)).map(_.asInstanceOf[T]) - def getBoolean(column: Int): Option[Boolean] = getAs(column: Int, item => item.asInstanceOf[Boolean]) + def getBoolean(column: Int): Option[Boolean] = getAs(column: Int, {item: Object => item.asInstanceOf[Boolean]}) def getBoolean(columnLabel: String): Option[Boolean] = getBoolean(columnNumber(columnLabel)) def getChar(column: Int): Option[Char] = { From d199874c3891d3c525079237c3b4236342af2f30 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Fri, 16 Aug 2024 18:39:23 +0200 Subject: [PATCH 06/11] * fixes spread --- .../absa/db/balta/classes/QueryResult.scala | 2 +- .../db/balta/classes/QueryResultRow.scala | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResult.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResult.scala index 81623a3..e24d88f 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResult.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResult.scala @@ -41,7 +41,7 @@ class QueryResult(resultSet: ResultSet) extends Iterator[QueryResultRow] { nextRow = Some(QueryResultRow(resultSet)) } } catch { - case e: SQLException => throw e // TODO Do nothing + case _: SQLException => // TODO Do nothing } } nextRow.nonEmpty diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala index b18bb2b..c82fafa 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala @@ -63,47 +63,47 @@ class QueryResultRow private[classes](val rowNumber: Int, def getChar(columnLabel: String): Option[Char] = getChar(columnNumber(columnLabel)) - def getString(column: Int): Option[String] = getAs(column: Int, _.asInstanceOf[String]) + def getString(column: Int): Option[String] = getAs(column: Int, {item: Object => item.asInstanceOf[String]}) def getString(columnLabel: String): Option[String] = getString(columnNumber(columnLabel)) - def getInt(column: Int): Option[Int] = getAs(column: Int, _.asInstanceOf[Int]) + def getInt(column: Int): Option[Int] = getAs(column: Int, {item: Object => item.asInstanceOf[Int]}) def getInt(columnLabel: String): Option[Int] = getInt(columnNumber(columnLabel)) - def getLong(column: Int): Option[Long] = getAs(column: Int, _.asInstanceOf[Long]) + def getLong(column: Int): Option[Long] = getAs(column: Int, {item: Object => item.asInstanceOf[Long]}) def getLong(columnLabel: String): Option[Long] = getLong(columnNumber(columnLabel)) - def getDouble(column: Int): Option[Double] = getAs(column: Int, _.asInstanceOf[Double]) + def getDouble(column: Int): Option[Double] = getAs(column: Int, {item: Object => item.asInstanceOf[Double]}) def getDouble(columnLabel: String): Option[Double] = getDouble(columnNumber(columnLabel)) - def getFloat(column: Int): Option[Float] = getAs(column: Int, _.asInstanceOf[Float]) + def getFloat(column: Int): Option[Float] = getAs(column: Int, {item: Object => item.asInstanceOf[Float]}) def getFloat(columnLabel: String): Option[Float] = getFloat(columnNumber(columnLabel)) def getBigDecimal(column: Int): Option[BigDecimal] = getAs(column: Int, _.asInstanceOf[java.math.BigDecimal]).map(scala.math.BigDecimal(_)) def getBigDecimal(columnLabel: String): Option[BigDecimal] = getBigDecimal(columnNumber(columnLabel)) - def getTime(column: Int): Option[Time] = getAs(column: Int, _.asInstanceOf[Time]) + def getTime(column: Int): Option[Time] = getAs(column: Int, {item: Object => item.asInstanceOf[Time]}) def getTime(columnLabel: String): Option[Time] = getTime(columnNumber(columnLabel)) - def getDate(column: Int): Option[Date] = getAs(column: Int, _.asInstanceOf[Date]) + def getDate(column: Int): Option[Date] = getAs(column: Int, {item: Object => item.asInstanceOf[Date]}) def getDate(columnLabel: String): Option[Date] = getDate(columnNumber(columnLabel)) - def getLocalDateTime(column: Int): Option[LocalDateTime] = getAs(column: Int, _.asInstanceOf[LocalDateTime]) + def getLocalDateTime(column: Int): Option[LocalDateTime] = getAs(column: Int, {item: Object => item.asInstanceOf[LocalDateTime]}) def getLocalDateTime(columnLabel: String): Option[LocalDateTime] = getLocalDateTime(columnNumber(columnLabel)) - def getOffsetDateTime(column: Int): Option[OffsetDateTime] = getAs(column: Int, _.asInstanceOf[OffsetDateTime]) + def getOffsetDateTime(column: Int): Option[OffsetDateTime] = getAs(column: Int, {item: Object => item.asInstanceOf[OffsetDateTime]}) def getOffsetDateTime(columnLabel: String): Option[OffsetDateTime] = getOffsetDateTime(columnNumber(columnLabel)) def getInstant(column: Int): Option[Instant] = getOffsetDateTime(column).map(_.toInstant) def getInstant(columnLabel: String): Option[Instant] = getOffsetDateTime(columnLabel).map(_.toInstant) - def getUUID(column: Int): Option[UUID] = getAs(column: Int, _.asInstanceOf[UUID]) + def getUUID(column: Int): Option[UUID] = getAs(column: Int, {item: Object => item.asInstanceOf[UUID]}) def getUUID(columnLabel: String): Option[UUID] = getUUID(columnNumber(columnLabel)) def getArray[T](column: Int): Option[Vector[T]] = { def transformerFnc(obj: Object): Vector[T] = { obj.asInstanceOf[sql.Array].getArray().asInstanceOf[Array[T]].toVector } - getAs(column: Int, transformerFnc _) //TODO + getAs(column: Int, transformerFnc _) } def getArray[T](columnLabel: String): Option[Vector[T]] = getArray[T](columnNumber(columnLabel)) From 6a890d46fbe763368cf12e9b5dcec2151698f22d Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Fri, 16 Aug 2024 20:53:31 +0200 Subject: [PATCH 07/11] * fixes enhanced --- .../za/co/absa/db/balta/classes/QueryResultRow.scala | 12 ++++++++---- build.sbt | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala index c82fafa..60ccebb 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala @@ -78,7 +78,9 @@ class QueryResultRow private[classes](val rowNumber: Int, def getFloat(column: Int): Option[Float] = getAs(column: Int, {item: Object => item.asInstanceOf[Float]}) def getFloat(columnLabel: String): Option[Float] = getFloat(columnNumber(columnLabel)) - def getBigDecimal(column: Int): Option[BigDecimal] = getAs(column: Int, _.asInstanceOf[java.math.BigDecimal]).map(scala.math.BigDecimal(_)) + def getBigDecimal(column: Int): Option[BigDecimal] = + getAs(column: Int, {item: Object => item.asInstanceOf[java.math.BigDecimal]}) + .map(scala.math.BigDecimal(_)) def getBigDecimal(columnLabel: String): Option[BigDecimal] = getBigDecimal(columnNumber(columnLabel)) def getTime(column: Int): Option[Time] = getAs(column: Int, {item: Object => item.asInstanceOf[Time]}) @@ -127,7 +129,8 @@ object QueryResultRow { type FieldNames = Map[String, Int] type TransformerFnc[T] = Object => T - type Extractors = Vector[ResultSet => Option[Object]] + type Extractor = ResultSet => Option[Object] + type Extractors = Vector[Extractor] def apply(resultSet: ResultSet)(implicit fieldNames: FieldNames, extractors: Extractors): QueryResultRow = { val fields = extractors.map(_(resultSet)) @@ -155,13 +158,14 @@ object QueryResultRow { def columnTypeName(column: Int): String = metaData.getColumnTypeName(column).toLowerCase() Range.inclusive(1, metaData.getColumnCount).map { column => - metaData.getColumnType(column) match { - case Types.TIME if columnTypeName(column) == "timetz" => timeTzExtractor(_, column) + val extractor: Extractor = metaData.getColumnType(column) match { + case Types.TIME if columnTypeName(column) == "timetz" => timeTzExtractor(_, column) case Types.TIMESTAMP if columnTypeName(column) == "timestamptz" => timestampTzExtractor(_, column) case Types.TIMESTAMP => timestampExtractor(_, column) case Types.ARRAY => arrayExtractor(_, column) case _ => generalExtractor(_, column) } + extractor }.toVector } diff --git a/build.sbt b/build.sbt index 52093c3..7e9bcfe 100644 --- a/build.sbt +++ b/build.sbt @@ -22,6 +22,8 @@ lazy val scala213 = "2.13.11" lazy val supportedScalaVersions: Seq[String] = Seq(scala211, scala212 , scala213) +name := "balta" + ThisBuild / scalaVersion := scala212 ThisBuild / versionScheme := Some("early-semver") From 746b4614fb75344198623b26519cd99faaf237b1 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Fri, 16 Aug 2024 21:02:49 +0200 Subject: [PATCH 08/11] * removed unused imports --- .../scala/za/co/absa/db/balta/classes/DBFunction.scala | 1 - .../scala/za/co/absa/db/balta/classes/QueryResultRow.scala | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/DBFunction.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/DBFunction.scala index 8da05f6..8f39e50 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/classes/DBFunction.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/DBFunction.scala @@ -18,7 +18,6 @@ package za.co.absa.db.balta.classes import DBFunction.{DBFunctionWithNamedParamsToo, DBFunctionWithPositionedParamsOnly, ParamsMap} import za.co.absa.db.balta.classes.setter.{AllowedParamTypes, SetterFnc} -import za.co.absa.db.balta.classes.setter.{AllowedParamTypes, SetterFnc} import scala.collection.immutable.ListMap diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala index 60ccebb..d4dcc5d 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala @@ -17,14 +17,11 @@ package za.co.absa.db.balta.classes import QueryResultRow._ -import org.postgresql.jdbc.PgArray -import org.postgresql.util.PGobject import java.sql -import java.sql.{Date, ResultSet, ResultSetMetaData, SQLException, Time, Types} -import java.time.{Instant, LocalDateTime, LocalTime, OffsetDateTime, OffsetTime} +import java.sql.{Date, ResultSet, ResultSetMetaData, Time, Types} +import java.time.{Instant, LocalDateTime, OffsetDateTime, OffsetTime} import java.util.UUID -import scala.util.Try /** * This is a row of a query result. It allows to safely extract values from the row by column name. From 8a01daea51673708227cc293ff15936b43fc9d5f Mon Sep 17 00:00:00 2001 From: David Benedeki <14905969+benedeki@users.noreply.github.com> Date: Mon, 19 Aug 2024 13:07:13 +0200 Subject: [PATCH 09/11] Apply suggestions from code review Co-authored-by: Ladislav Sulak --- .github/workflows/test_filenames_check.yml | 2 +- .../za/co/absa/db/balta/classes/QueryResultRow.scala | 2 +- .../db/balta/implicits/PostgresRowIntegrationTests.scala | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test_filenames_check.yml b/.github/workflows/test_filenames_check.yml index e94aa77..7af9871 100644 --- a/.github/workflows/test_filenames_check.yml +++ b/.github/workflows/test_filenames_check.yml @@ -1,5 +1,5 @@ # -# Copyright 2021 ABSA Group Limited +# Copyright 2023 ABSA Group Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala index d4dcc5d..337aceb 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResultRow.scala @@ -134,7 +134,7 @@ object QueryResultRow { new QueryResultRow(resultSet.getRow, fields, fieldNames) } - def fieldNamesFromMetdata(metaData: ResultSetMetaData): FieldNames = { + def fieldNamesFromMetadata(metaData: ResultSetMetaData): FieldNames = { Range.inclusive(1, metaData.getColumnCount).map(i => metaData.getColumnName(i) -> i).toMap } diff --git a/balta/src/test/scala/za/co/absa/db/balta/implicits/PostgresRowIntegrationTests.scala b/balta/src/test/scala/za/co/absa/db/balta/implicits/PostgresRowIntegrationTests.scala index c52c034..e280616 100644 --- a/balta/src/test/scala/za/co/absa/db/balta/implicits/PostgresRowIntegrationTests.scala +++ b/balta/src/test/scala/za/co/absa/db/balta/implicits/PostgresRowIntegrationTests.scala @@ -24,7 +24,7 @@ import java.sql.ResultSetMetaData import Postgres.PostgresRow import za.co.absa.db.balta.classes.simple.SimpleJsonString -class PostgresRowIntegrationTests extends AnyFunSuiteLike with DBTestingConnection{ +class PostgresRowIntegrationTests extends AnyFunSuiteLike with DBTestingConnection{ private val (tableRows: List[QueryResultRow], metadata: ResultSetMetaData) = DBTable("testing.pg_types").all("id") { q => (q.toList, q.resultSetMetaData) } @@ -32,7 +32,7 @@ class PostgresRowIntegrationTests extends AnyFunSuiteLike with DBTestingConnect test("fieldNamesFromMetada") { val result = QueryResultRow.fieldNamesFromMetdata(metadata) - val expecedResult = Seq( + val expectedResult = Seq( "id", "json_type", "jsonb_type", @@ -42,7 +42,7 @@ class PostgresRowIntegrationTests extends AnyFunSuiteLike with DBTestingConnect .map(x => (x._1, x._2 + 1)) .toMap assert(result.size == 4) - assert(result == expecedResult) + assert(result == expectedResult) } test("getSimpleJson") { @@ -68,7 +68,7 @@ class PostgresRowIntegrationTests extends AnyFunSuiteLike with DBTestingConnect SimpleJsonString("""{"a": 3, "body": ""}"""), SimpleJsonString("""{"a": 4}""") ) - assert(tableRows.head.getSJSArray(4).get == (expected)) + assert(tableRows.head.getSJSArray(4).get == expected) assert(tableRows.head.getSJSArray("array_of_json_type").contains(expected)) //second row val secondRow = tableRows.tail.head From a6237b698d8b0daf613b9601124bc7f9f051f551 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Mon, 19 Aug 2024 13:30:17 +0200 Subject: [PATCH 10/11] * Addressed PR comments --- .../main/scala/za/co/absa/db/balta/classes/QueryResult.scala | 4 ++-- balta/src/test/resources/db/postgres/01_db.ddl | 2 +- balta/src/test/resources/db/postgres/02_users.ddl | 2 +- balta/src/test/resources/db/postgres/03_schema_testing.ddl | 2 +- .../src/test/resources/db/postgres/04_testing.base_types.ddl | 2 +- .../resources/db/postgres/05_testing._base_types_data.sql | 4 ++-- balta/src/test/resources/db/postgres/06_testing.pg_types.ddl | 2 +- .../test/resources/db/postgres/07_testing_pg_types_data.sql | 3 +-- .../db/balta/classes/QueryResultRowIntegrationTests.scala | 2 +- .../absa/db/balta/implicits/PostgresRowIntegrationTests.scala | 2 +- 10 files changed, 12 insertions(+), 13 deletions(-) diff --git a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResult.scala b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResult.scala index e24d88f..aa3a987 100644 --- a/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResult.scala +++ b/balta/src/main/scala/za/co/absa/db/balta/classes/QueryResult.scala @@ -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.fieldNamesFromMetdata(resultSetMetaData) + private [this] implicit val fieldNames: FieldNames = QueryResultRow.fieldNamesFromMetadata(resultSetMetaData) private [this] implicit val extractors: Extractors = QueryResultRow.createExtractors(resultSetMetaData) override def hasNext: Boolean = { @@ -41,7 +41,7 @@ class QueryResult(resultSet: ResultSet) extends Iterator[QueryResultRow] { nextRow = Some(QueryResultRow(resultSet)) } } catch { - case _: SQLException => // TODO Do nothing + case _: SQLException => // Do nothing } } nextRow.nonEmpty diff --git a/balta/src/test/resources/db/postgres/01_db.ddl b/balta/src/test/resources/db/postgres/01_db.ddl index f0ac329..b3f3aba 100644 --- a/balta/src/test/resources/db/postgres/01_db.ddl +++ b/balta/src/test/resources/db/postgres/01_db.ddl @@ -3,4 +3,4 @@ CREATE DATABASE mag_db OWNER = postgres ENCODING = 'UTF8' TABLESPACE = pg_default - CONNECTION LIMIT = -1; \ No newline at end of file + CONNECTION LIMIT = -1; diff --git a/balta/src/test/resources/db/postgres/02_users.ddl b/balta/src/test/resources/db/postgres/02_users.ddl index 7ab9180..56d8354 100644 --- a/balta/src/test/resources/db/postgres/02_users.ddl +++ b/balta/src/test/resources/db/postgres/02_users.ddl @@ -6,4 +6,4 @@ CREATE ROLE mag_owner WITH INHERIT NOREPLICATION CONNECTION LIMIT -1 - PASSWORD 'changeme'; \ No newline at end of file + PASSWORD 'changeme'; diff --git a/balta/src/test/resources/db/postgres/03_schema_testing.ddl b/balta/src/test/resources/db/postgres/03_schema_testing.ddl index 24da2d3..d01b823 100644 --- a/balta/src/test/resources/db/postgres/03_schema_testing.ddl +++ b/balta/src/test/resources/db/postgres/03_schema_testing.ddl @@ -1,2 +1,2 @@ CREATE SCHEMA IF NOT EXISTS testing - AUTHORIZATION mag_owner; \ No newline at end of file + AUTHORIZATION mag_owner; diff --git a/balta/src/test/resources/db/postgres/04_testing.base_types.ddl b/balta/src/test/resources/db/postgres/04_testing.base_types.ddl index d12c348..a78ad5c 100644 --- a/balta/src/test/resources/db/postgres/04_testing.base_types.ddl +++ b/balta/src/test/resources/db/postgres/04_testing.base_types.ddl @@ -19,4 +19,4 @@ CREATE TABLE IF NOT EXISTS testing.base_types ); ALTER TABLE IF EXISTS testing.base_types - OWNER to mag_owner; \ No newline at end of file + OWNER to mag_owner; diff --git a/balta/src/test/resources/db/postgres/05_testing._base_types_data.sql b/balta/src/test/resources/db/postgres/05_testing._base_types_data.sql index 192ca6d..5db1daf 100644 --- a/balta/src/test/resources/db/postgres/05_testing._base_types_data.sql +++ b/balta/src/test/resources/db/postgres/05_testing._base_types_data.sql @@ -1,4 +1,4 @@ -DELETE FROM testing.base_types; +TRUNCATE testing.base_types; INSERT INTO testing.base_types( long_type, boolean_type, char_type, string_type, int_type, double_type, float_type, bigdecimal_type, date_type, @@ -10,4 +10,4 @@ VALUES (1, true, 'a', 'hello world', 257, 3.14, 2.71, 123456789.0123456789, '202 INSERT INTO testing.base_types( long_type) -VALUES (NULL); \ No newline at end of file +VALUES (NULL); diff --git a/balta/src/test/resources/db/postgres/06_testing.pg_types.ddl b/balta/src/test/resources/db/postgres/06_testing.pg_types.ddl index 062d571..22e57d0 100644 --- a/balta/src/test/resources/db/postgres/06_testing.pg_types.ddl +++ b/balta/src/test/resources/db/postgres/06_testing.pg_types.ddl @@ -8,4 +8,4 @@ CREATE TABLE testing.pg_types ); ALTER TABLE IF EXISTS testing.pg_types - OWNER to mag_owner; \ No newline at end of file + OWNER to mag_owner; diff --git a/balta/src/test/resources/db/postgres/07_testing_pg_types_data.sql b/balta/src/test/resources/db/postgres/07_testing_pg_types_data.sql index 51e0a8e..7dfe29f 100644 --- a/balta/src/test/resources/db/postgres/07_testing_pg_types_data.sql +++ b/balta/src/test/resources/db/postgres/07_testing_pg_types_data.sql @@ -1,5 +1,4 @@ -DELETE FROM - testing.pg_types; +TRUNCATE testing.pg_types; INSERT INTO testing.pg_types( diff --git a/balta/src/test/scala/za/co/absa/db/balta/classes/QueryResultRowIntegrationTests.scala b/balta/src/test/scala/za/co/absa/db/balta/classes/QueryResultRowIntegrationTests.scala index 907ae1a..9424a74 100644 --- a/balta/src/test/scala/za/co/absa/db/balta/classes/QueryResultRowIntegrationTests.scala +++ b/balta/src/test/scala/za/co/absa/db/balta/classes/QueryResultRowIntegrationTests.scala @@ -30,7 +30,7 @@ class QueryResultRowIntegrationTests extends AnyFunSuiteLike with DBTestingConne } test("fieldNamesFromMetada") { - val result = QueryResultRow.fieldNamesFromMetdata(metadata) + val result = QueryResultRow.fieldNamesFromMetadata(metadata) val expecedResult = Seq( "long_type", diff --git a/balta/src/test/scala/za/co/absa/db/balta/implicits/PostgresRowIntegrationTests.scala b/balta/src/test/scala/za/co/absa/db/balta/implicits/PostgresRowIntegrationTests.scala index e280616..ad97888 100644 --- a/balta/src/test/scala/za/co/absa/db/balta/implicits/PostgresRowIntegrationTests.scala +++ b/balta/src/test/scala/za/co/absa/db/balta/implicits/PostgresRowIntegrationTests.scala @@ -30,7 +30,7 @@ class PostgresRowIntegrationTests extends AnyFunSuiteLike with DBTestingConnecti } test("fieldNamesFromMetada") { - val result = QueryResultRow.fieldNamesFromMetdata(metadata) + val result = QueryResultRow.fieldNamesFromMetadata(metadata) val expectedResult = Seq( "id", From 30c6b070a88435d0448f9295b57dbb7e13b6a2a1 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Fri, 23 Aug 2024 13:56:10 +0200 Subject: [PATCH 11/11] * addressed further PR comments --- .github/workflows/build.yml | 16 ++++++++-------- .github/workflows/test_filenames_check.yml | 2 +- README.md | 12 ++++++++++++ .../classes/QueryResultRowIntegrationTests.scala | 2 +- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cb08508..f2982b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,6 +53,14 @@ jobs: - uses: coursier/cache-action@v5 + - name: Setup Scala + uses: olafurpg/setup-scala@v10 + with: + java-version: "adopt@1.8" + + - name: Build and run unit tests + run: sbt ++${{matrix.scala}} test doc + - name: Setup database run: | psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/02_users.ddl @@ -62,13 +70,5 @@ jobs: psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/06_testing.pg_types.ddl psql postgresql://postgres:postgres@localhost:5432/mag_db -f balta/src/test/resources/db/postgres/07_testing_pg_types_data.sql - - name: Setup Scala - uses: olafurpg/setup-scala@v10 - with: - java-version: "adopt@1.8" - - - name: Build and run unit tests - run: sbt ++${{matrix.scala}} test doc - - name: Build and run integration tests run: sbt ++${{matrix.scala}} testIT diff --git a/.github/workflows/test_filenames_check.yml b/.github/workflows/test_filenames_check.yml index 7af9871..318662b 100644 --- a/.github/workflows/test_filenames_check.yml +++ b/.github/workflows/test_filenames_check.yml @@ -31,7 +31,7 @@ jobs: - name: Filename Inspector id: scan-test-files - uses: AbsaOSS/filename-inspector@master + uses: AbsaOSS/filename-inspector@v0.1.0 with: name-patterns: '*UnitTests.*,*IntegrationTests.*' paths: '**/src/test/scala/**' diff --git a/README.md b/README.md index 28adad2..e7ec7a4 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,18 @@ It's a natural complement to the use of [Fa-Db library](https://github.com/AbsaO Advantages of this approach is that the tests repeateble, they are isolated from each other and the database is always in a known state before and after each test. +## How to Test +There are integration tests part of the package that can be run with the following command: + +```bash +sbt testIT +``` + +The tests to finish successfully, a Postgres database must be running and populated. +* by default the database is expected to be running on `localhost:5432` +* if you wish to run against a different server modify the `src/test/resources/database.properties` file +* to populate the database run the scripts in the `src/test/resources/db/postgres` folder + ## How to Release Please see [this file](RELEASE.md) for more details. diff --git a/balta/src/test/scala/za/co/absa/db/balta/classes/QueryResultRowIntegrationTests.scala b/balta/src/test/scala/za/co/absa/db/balta/classes/QueryResultRowIntegrationTests.scala index 9424a74..bace35c 100644 --- a/balta/src/test/scala/za/co/absa/db/balta/classes/QueryResultRowIntegrationTests.scala +++ b/balta/src/test/scala/za/co/absa/db/balta/classes/QueryResultRowIntegrationTests.scala @@ -207,7 +207,7 @@ class QueryResultRowIntegrationTests extends AnyFunSuiteLike with DBTestingConne assert(secondRow.getUUID("uuid_type").isEmpty) } - test("getVector[Int]") { + test("getArray[Int]") { //first row val expected = Vector(1, 2, 3) assert(tableRows.head.getArray[Int](14).contains(expected))