From 384948d428cf227561ba2d39625046560548083c Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Wed, 31 May 2023 22:28:52 +0200 Subject: [PATCH 01/17] #35: Refactor the core for more logical structure * massive classes refactoring --- .../main/scala/za/co/absa/fadb/DBEngine.scala | 44 ++++++++ .../scala/za/co/absa/fadb/DBFunction.scala | 53 +++++++--- .../main/scala/za/co/absa/fadb/DBSchema.scala | 24 +---- .../fadb/statushandling/StatusHandling.scala | 14 ++- .../UserDefinedStatusHandling.scala | 31 ++++++ .../examples/enceladus/DatasetSchema.scala | 100 ++++++++++-------- .../za/co/absa/fadb/slick/SlickPgEngine.scala | 41 +++++++ .../co/absa/fadb/slick/SlickPgFunction.scala | 41 ++++--- .../SlickPgFunctionWithStatusSupport.scala | 40 +++++++ .../za/co/absa/fadb/slick/SlickQuery.scala | 25 +++++ 10 files changed, 313 insertions(+), 100 deletions(-) create mode 100644 core/src/main/scala/za/co/absa/fadb/DBEngine.scala create mode 100644 core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.scala create mode 100644 slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala create mode 100644 slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala create mode 100644 slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala diff --git a/core/src/main/scala/za/co/absa/fadb/DBEngine.scala b/core/src/main/scala/za/co/absa/fadb/DBEngine.scala new file mode 100644 index 00000000..58685bdb --- /dev/null +++ b/core/src/main/scala/za/co/absa/fadb/DBEngine.scala @@ -0,0 +1,44 @@ +/* + * Copyright 2022 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.fadb + +import za.co.absa.fadb.DBEngine.Query + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future + +trait DBEngine { + + type QueryType[_] + + // in future implementation the convertor might not be needed (ideally) + protected def run[R](query: QueryType[R]): Future[Seq[R]] + + def execute[R](query: QueryType[R]): Future[Seq[R]] = run(query) + + def unique[QR, R](query: QueryType, converter: QR => R): Future[R] = { + run(query, converter).map(_.head) + } + + def option[QR, R](query: QueryType, converter: QR => R): Future[Option[R]] = { + run(query, converter).map(_.headOption) + } +} + +object DBEngine { + trait Query +} diff --git a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala index d6432de3..0998fcc8 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala @@ -16,7 +16,11 @@ package za.co.absa.fadb +import za.co.absa.fadb.DBEngine.Query +import za.co.absa.fadb.naming_conventions.NamingConvention + import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global /** * The most general abstraction of database function representation @@ -29,7 +33,9 @@ import scala.concurrent.Future * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function */ -abstract class DBFunction[E, T, R](schema: DBSchema[E], functionNameOverride: Option[String] = None) extends DBFunctionFabric { +abstract class DBFunction[T, R, Q <: Query](schema: DBSchema[Q], functionNameOverride: Option[String] = None) extends DBFunctionFabric { + type QR + val functionName: String = { val fn = functionNameOverride.getOrElse(schema.objectNameFromClassName(getClass)) if (schema.schemaName.isEmpty) { @@ -39,6 +45,13 @@ abstract class DBFunction[E, T, R](schema: DBSchema[E], functionNameOverride: Op } } + def namingConvention: NamingConvention = schema.namingConvention + + override protected def fieldsToSelect: Seq[String] = super.fieldsToSelect //TODO should get the names from R #6 + + protected def query(values: T): Q + + protected def converter: QR => R /** * For the given output it returns a function to execute the SQL query and interpret the results. * Basically it should create a function which contains a query to be executable and executed on on the [[DBExecutor]] @@ -46,7 +59,19 @@ abstract class DBFunction[E, T, R](schema: DBSchema[E], functionNameOverride: Op * @param values - the input values of the DB function (stored procedure) * @return - the query function that when provided an executor will return the result of the DB function call */ - protected def queryFunction(values: T): QueryFunction[E, R] + + protected def execute(values: T): Future[Seq[R]] = { + schema.dBEngine.execute(query(values), converter) + } + + protected def unique(values: T): Future[R] = { + schema.dBEngine.unique(query(values), converter) + } + + protected def option(values: T): Future[Option[R]] = { + schema.dBEngine.option(query(values), converter) + } + } object DBFunction { @@ -60,11 +85,9 @@ object DBFunction { * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function */ - abstract class DBSeqFunction[E, T, R](schema: DBSchema[E], functionNameOverride: Option[String] = None) - extends DBFunction[E, T, R](schema, functionNameOverride) { - def apply(values: T): Future[Seq[R]] = { - schema.execute(queryFunction(values)) - } + abstract class DBSeqFunction[T, R, Q <: Query, QR](schema: DBSchema[Q], functionNameOverride: Option[String] = None) + extends DBFunction[T, R, Q, QR](schema, functionNameOverride) { + def apply(values: T): Future[Seq[R]] = execute(values) } /** @@ -77,11 +100,9 @@ object DBFunction { * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function */ - abstract class DBUniqueFunction[E, T, R](schema: DBSchema[E], functionNameOverride: Option[String] = None) - extends DBFunction[E, T, R](schema, functionNameOverride) { - def apply(values: T): Future[R] = { - schema.unique(queryFunction(values)) - } + abstract class DBUniqueFunction[T, R, Q <: Query, QR](schema: DBSchema[Q], functionNameOverride: Option[String] = None) + extends DBFunction[T, R, Q, QR](schema, functionNameOverride) { + def apply(values: T): Future[R] = unique(values) } /** @@ -94,10 +115,8 @@ object DBFunction { * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function */ - abstract class DBOptionFunction[E, T, R](schema: DBSchema[E], functionNameOverride: Option[String] = None) - extends DBFunction[E, T, R](schema, functionNameOverride) { - def apply(values: T): Future[Option[R]] = { - schema.option(queryFunction(values)) - } + abstract class DBOptionFunction[T, R, Q <: Query, QR](schema: DBSchema[Q], functionNameOverride: Option[String] = None) + extends DBFunction[T, R, Q, QR](schema, functionNameOverride) { + def apply(values: T): Future[Option[R]] = option(values) } } diff --git a/core/src/main/scala/za/co/absa/fadb/DBSchema.scala b/core/src/main/scala/za/co/absa/fadb/DBSchema.scala index e372c107..4d813097 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBSchema.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBSchema.scala @@ -16,11 +16,9 @@ package za.co.absa.fadb +import za.co.absa.fadb.DBEngine.Query import za.co.absa.fadb.naming_conventions.NamingConvention -import scala.concurrent.Future -import scala.concurrent.ExecutionContext.Implicits.global - /** * An abstract class, an ancestor to represent a database schema (each database function should be placed in a schema) * The database name of the schema is derives from the class name based on the provided naming convention @@ -31,9 +29,10 @@ import scala.concurrent.ExecutionContext.Implicits.global * @param namingConvention - the [[za.co.absa.fadb.naming_conventions.NamingConvention]](NamingConvention) prescribing how to convert a class name into a db object name * @tparam E - the engine of the executor type, e.g. Slick Database */ -abstract class DBSchema[E](val executor: DBExecutor[E], schemaNameOverride: Option[String] = None) - (implicit namingConvention: NamingConvention) { +abstract class DBSchema[Q <: Query](val dBEngine: DBEngine[Q], schemaNameOverride: Option[String] = None) + (implicit val namingConvention: NamingConvention) { + type QueryType[R] = dBEngine.QueryType[R] def objectNameFromClassName(c: Class[_]): String = { namingConvention.fromClassNamePerConvention(c) @@ -41,19 +40,4 @@ abstract class DBSchema[E](val executor: DBExecutor[E], schemaNameOverride: Opti val schemaName: String = schemaNameOverride.getOrElse(objectNameFromClassName(getClass)) - def execute[R](query: QueryFunction[E, R]): Future[Seq[R]] = { - executor.run(query) - } - - def unique[R](query: QueryFunction[E, R]): Future[R] = { - for { - all <- execute(query) - } yield all.head - } - - def option[R](query: QueryFunction[E, R]): Future[Option[R]] = { - for { - all <- execute(query) - } yield all.headOption - } } diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala index 06bb4720..36e3cc56 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala @@ -17,26 +17,30 @@ package za.co.absa.fadb.statushandling import za.co.absa.fadb.DBFunctionFabric +import za.co.absa.fadb.naming_conventions.NamingConvention import za.co.absa.fadb.statushandling.StatusHandling.{defaultStatusFieldName, defaultStatusTextFieldName} import scala.util.Try /** - * A basis for mix-in traits for [[DBFunction]] that support `status` and `status_text` for easier handling + * A basis for mix-in traits for [[DBFunction]] that support `status` and `status text` for easier handling */ trait StatusHandling extends DBFunctionFabric{ - def statusFieldName: String = defaultStatusFieldName - def statusTextFieldName: String = defaultStatusTextFieldName + def namingConvention: NamingConvention + + def checkStatus(status: Integer, statusText: String): Try[Unit] + + def statusFieldName: String = namingConvention.stringPerConvention(defaultStatusFieldName) + def statusTextFieldName: String = namingConvention.stringPerConvention(defaultStatusTextFieldName) override protected def fieldsToSelect: Seq[String] = { Seq(statusFieldName, statusTextFieldName) ++ super.fieldsToSelect } - protected def checkStatus(status: Integer, statusTex: String): Try[Unit] } object StatusHandling { val defaultStatusFieldName = "status" - val defaultStatusTextFieldName = "status_test" + val defaultStatusTextFieldName = "statusText" } diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.scala new file mode 100644 index 00000000..9ceebc11 --- /dev/null +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.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.fadb.statushandling + +import scala.util.{Failure, Success, Try} + +trait UserDefinedStatusHandling extends StatusHandling{ + def OKStatuses: Set[Integer] + + def checkStatus(status: Integer, statusText: String): Try[Unit] = { + if (OKStatuses.contains(status)) { + Success(Unit) + } else { + Failure(new StatusException(status, statusText)) + } + } +} diff --git a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala index 90e3fb75..9d6660a3 100644 --- a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala +++ b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala @@ -16,21 +16,22 @@ package za.co.absa.fadb.examples.enceladus -import za.co.absa.fadb.{DBSchema} -import za.co.absa.fadb.slick.{SlickPgExecutor, SlickPgFunction} +import za.co.absa.fadb.DBSchema +import za.co.absa.fadb.slick.{SlickPgEngine, SlickPgFunction, SlickPgFunctionWithStatusSupport, SlickQuery} +import za.co.absa.fadb.slick.SlickPgFunctionWithStatusSupport._ +import za.co.absa.fadb.slick.SlickPgFunction.{DBSeqFunction => DBSeqFunctionNoStatus} import za.co.absa.fadb.naming_conventions.SnakeCaseNaming.Implicits.namingConvention import slick.jdbc.{GetResult, SQLActionBuilder} -import za.co.absa.fadb.DBFunction._ import slick.jdbc.PostgresProfile.api._ import java.sql.Timestamp import scala.concurrent.Future import DatasetSchema._ -import za.co.absa.fadb.statushandling.StatusException +import za.co.absa.fadb.statushandling.{StatusException, UserDefinedStatusHandling} -class DatasetSchema(executor: SlickPgExecutor) extends DBSchema(executor) { +class DatasetSchema(engine: SlickPgEngine) extends DBSchema(engine) { - private implicit val schema: DBSchema[ExecutorEngineType] = this + private implicit val schema: DBSchema[SlickQuery] = this val addSchema = new AddSchema val getSchema = new GetSchema val listSchemas = new ListSchemas @@ -72,32 +73,35 @@ object DatasetSchema { Schema(r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<) }) - final class AddSchema(implicit schema: DBSchema[ExecutorEngineType]) - extends DBUniqueFunction[ExecutorEngineType, SchemaInput, Long](schema) - with SlickPgFunction[SchemaInput, Long] { - - - override protected def sqlToCallFunction(values: SchemaInput): SQLActionBuilder = { - sql"""SELECT A.status, A.status_text, A.id_schema_version - FROM #$functionName(${values.schemaName}, ${values.schemaVersion}, ${values.schemaDescription}, - ${values.fields}::JSONB, ${values.userName} - ) A;""" - } - - override protected def resultConverter: GetResult[Long] = { - val gr:GetResult[Long] = GetResult(r => { - val status: Int = r.<< - val statusText: String = r.<< - if (status != 201) throw new StatusException(status, statusText) - r.<< - }) - gr - } + final class AddSchema(implicit schema: DBSchema[SlickQuery]) + extends DBUniqueFunction[SchemaInput, Long](schema) + with UserDefinedStatusHandling { + + +// TODO --- +// override protected def sqlToCallFunction(values: SchemaInput): SQLActionBuilder = { +// sql"""SELECT A.status, A.status_text, A.id_schema_version +// FROM #$functionName(${values.schemaName}, ${values.schemaVersion}, ${values.schemaDescription}, +// ${values.fields}::JSONB, ${values.userName} +// ) A;""" +// } +// +// override protected def resultConverter: GetResult[Long] = { +// val gr:GetResult[Long] = GetResult(r => { +// val status: Int = r.<< +// val statusText: String = r.<< +// if (status != 201) throw new StatusException(status, statusText) +// r.<< +// }) +// gr +// } + + override def OKStatuses: Set[Integer] = Set(201) } - final class GetSchema(implicit schema: DBSchema[ExecutorEngineType]) - extends DBUniqueFunction[ExecutorEngineType, (String, Option[Int]), Schema](schema) - with SlickPgFunction[(String, Option[Int]), Schema] { + final class GetSchema(implicit schema: DBSchema[SlickQuery]) + extends DBUniqueFunction[(String, Option[Int]), Schema](schema) + with UserDefinedStatusHandling { def apply(id: Long): Future[Schema] = { val sql = @@ -107,25 +111,35 @@ object DatasetSchema { schema.unique(makeQueryFunction(sql)(resultConverter)) } - override protected def sqlToCallFunction(values: (String, Option[Int])): SQLActionBuilder = { - sql"""SELECT A.* - FROM #$functionName(${values._1}, ${values._2}) A;""" - } - - override protected def resultConverter: GetResult[Schema] = DatasetSchema.GetSchemaImplicit +// TODO --- +// override protected def sqlToCallFunction(values: (String, Option[Int])): SQLActionBuilder = { +// sql"""SELECT A.* +// FROM #$functionName(${values._1}, ${values._2}) A;""" +// } +// +// override protected def resultConverter: GetResult[Schema] = DatasetSchema.GetSchemaImplicit } - final class ListSchemas(implicit schema: DBSchema[ExecutorEngineType]) - extends DBSeqFunction[ExecutorEngineType, Boolean, SchemaHeader](schema) - with SlickPgFunction[Boolean, SchemaHeader] { + final class ListSchemas(implicit schema: DBSchema[SlickQuery]) + extends DBSeqFunctionNoStatus[Boolean, SchemaHeader](schema) { override def apply(values: Boolean = false): Future[Seq[SchemaHeader]] = super.apply(values) - override protected def sqlToCallFunction(values: Boolean): SQLActionBuilder = { - sql"""SELECT A.schema_name, A.schema_latest_version - FROM #$functionName($values) as A;""" + override def fieldsToSelect: Seq[String] = { + super.fieldsToSelect } - override protected def resultConverter: GetResult[SchemaHeader] = DatasetSchema.SchemaHeaderImplicit + protected def query(values: T): SlickQuery = { + + } + +// TODO --- +// override protected def sqlToCallFunction(values: Boolean): SQLActionBuilder = { +// sql"""SELECT A.schema_name, A.schema_latest_version +// FROM #$functionName($values) as A;""" +// } +// +// override protected def resultConverter: GetResult[SchemaHeader] = DatasetSchema.SchemaHeaderImplicit + } } diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala new file mode 100644 index 00000000..42d84755 --- /dev/null +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala @@ -0,0 +1,41 @@ +/* + * Copyright 2022 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.fadb.slick + + +import slick.jdbc.{GetResult, PositionedResult} +import za.co.absa.fadb.DBEngine + +import scala.concurrent.Future +import slick.jdbc.PostgresProfile.api._ + +class SlickPgEngine(val db: Database) extends DBEngine { + + type QueryType[R] = SlickQuery[R] + + override protected def run[QR >: PositionedResult, R](query: SlickQuery, converter: QR => R): Future[Seq[R]] = { + // It can be expected that a GetResult will be passed into the run function as converter. + // Unfortunately it has to be recreated to be used by Slick + val getResult: GetResult[R] = GetResult{ r => + val pr: PositionedResult = r + converter(pr) + } + val slickAction = query.sql.as[R](getResult) + db.run(slickAction) + } + +} diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala index c6fbaa0f..cf5b44c6 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala @@ -16,25 +16,36 @@ package za.co.absa.fadb.slick -import slick.jdbc.{GetResult, SQLActionBuilder} -import slick.jdbc.PostgresProfile.api._ -import za.co.absa.fadb.{DBFunctionFabric, QueryFunction} +import slick.jdbc.{GetResult, PositionedResult} +import za.co.absa.fadb.{DBFunction, DBFunctionFabric} trait SlickPgFunction[T, R] extends DBFunctionFabric { + protected val alias = "A" - protected def sqlToCallFunction(values: T): SQLActionBuilder + protected def selectEntry: String = { + val fieldsSeq = fieldsToSelect + if (fieldsSeq.isEmpty) { + "*" + } else { + val aliasToUse = if (alias.isEmpty) { + "" + } else { + s"$alias." + } + fieldsToSelect.map(aliasToUse + _).mkString(",") + } + } - protected def resultConverter: GetResult[R] + protected def query(values: T): SlickQuery - protected def makeQueryFunction(sql: SQLActionBuilder)(implicit rconv: GetResult[R]): QueryFunction[Database, R] = { - val query = sql.as[R] - val resultFnc = {db: Database => db.run(query)} - resultFnc - } + protected def slickConverter: GetResult[R] - protected def queryFunction(values: T): QueryFunction[Database, R] = { - val converter = resultConverter - val functionSql = sqlToCallFunction(values) - makeQueryFunction(functionSql)(converter) - } + protected def converter: PositionedResult => R = slickConverter } + +object SlickPgFunction { + type DBSeqFunction[T, R] = DBFunction.DBSeqFunction[T, R, SlickQuery, PositionedResult] with SlickPgFunction[T, R] + type DBUniqueFunction[T, R] = DBFunction.DBUniqueFunction[T, R, SlickQuery, PositionedResult] with SlickPgFunction[T, R] + type DBOptionFunction[T, R] = DBFunction.DBOptionFunction[T, R, SlickQuery, PositionedResult] with SlickPgFunction[T, R] +} + diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala new file mode 100644 index 00000000..05f96411 --- /dev/null +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.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.fadb.slick + +import slick.jdbc.PositionedResult +import za.co.absa.fadb.DBFunction + +import scala.util.Try + +trait SlickPgFunctionWithStatusSupport[T, R] extends SlickPgFunction[T, R] { + + protected def checkStatus(status: Integer, statusText: String): Try[Unit] + + override protected def converter: PositionedResult => R = { queryResult => + val status: Int = queryResult.<< + val statusText: String = queryResult.<< + checkStatus(status, statusText).get //throw exception if status was off + slickConverter(queryResult) + } +} + +object SlickPgFunctionWithStatusSupport { + type DBSeqFunction[T, R] = DBFunction.DBSeqFunction[T, R, SlickQuery, PositionedResult] with SlickPgFunctionWithStatusSupport[T, R] + type DBUniqueFunction[T, R] = DBFunction.DBUniqueFunction[T, R, SlickQuery, PositionedResult] with SlickPgFunctionWithStatusSupport[T, R] + type DBOptionFunction[T, R] = DBFunction.DBOptionFunction[T, R, SlickQuery, PositionedResult] with SlickPgFunctionWithStatusSupport[T, R] +} diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala new file mode 100644 index 00000000..72eb7930 --- /dev/null +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala @@ -0,0 +1,25 @@ +/* + * Copyright 2022 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.fadb.slick + +import slick.jdbc.{GetResult, SQLActionBuilder} +import za.co.absa.fadb.DBEngine.Query + +case class SlickQuery[R](sql: SQLActionBuilder, getResult: GetResult[R]) extends Query { + +} + From 6af747a7fa932427357dacbfcfccbfab25ac2305 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Thu, 1 Jun 2023 12:48:29 +0200 Subject: [PATCH 02/17] * work in progress --- .../main/scala/za/co/absa/fadb/DBEngine.scala | 15 +-- .../scala/za/co/absa/fadb/DBFunction.scala | 27 +++--- .../za/co/absa/fadb/DBFunctionFabric.scala | 9 ++ .../main/scala/za/co/absa/fadb/DBSchema.scala | 5 +- .../fadb/statushandling/StatusHandling.scala | 4 +- .../UserDefinedStatusHandling.scala | 2 +- .../za/co/absa/fadb/DBFunctionSuite.scala | 25 +++-- .../scala/za/co/absa/fadb/DBSchemaSuite.scala | 50 ++-------- .../statushandling/StatusHandlingTest.scala | 43 +++++++++ .../StandardStatusHandlingTest.scala | 4 +- .../examples/enceladus/DatasetSchema.scala | 91 ++++++++----------- .../za/co/absa/fadb/slick/SlickPgEngine.scala | 12 +-- .../co/absa/fadb/slick/SlickPgFunction.scala | 21 ++--- .../SlickPgFunctionWithStatusSupport.scala | 15 ++- .../za/co/absa/fadb/slick/SlickQuery.scala | 6 +- 15 files changed, 162 insertions(+), 167 deletions(-) create mode 100644 core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingTest.scala diff --git a/core/src/main/scala/za/co/absa/fadb/DBEngine.scala b/core/src/main/scala/za/co/absa/fadb/DBEngine.scala index 58685bdb..15022aea 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBEngine.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBEngine.scala @@ -16,29 +16,30 @@ package za.co.absa.fadb -import za.co.absa.fadb.DBEngine.Query +import za.co.absa.fadb.DBFunctionFabric.Query import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future +import scala.language.higherKinds trait DBEngine { - type QueryType[_] + type QueryType[R] <: Query[R] // in future implementation the convertor might not be needed (ideally) protected def run[R](query: QueryType[R]): Future[Seq[R]] def execute[R](query: QueryType[R]): Future[Seq[R]] = run(query) - def unique[QR, R](query: QueryType, converter: QR => R): Future[R] = { - run(query, converter).map(_.head) + def unique[R](query: QueryType[R]): Future[R] = { + run(query).map(_.head) } - def option[QR, R](query: QueryType, converter: QR => R): Future[Option[R]] = { - run(query, converter).map(_.headOption) + def option[R](query: QueryType[R]): Future[Option[R]] = { + run(query).map(_.headOption) } } object DBEngine { - trait Query + // trait Query[R] TODO --- } diff --git a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala index 0998fcc8..a9ce8111 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala @@ -16,7 +16,6 @@ package za.co.absa.fadb -import za.co.absa.fadb.DBEngine.Query import za.co.absa.fadb.naming_conventions.NamingConvention import scala.concurrent.Future @@ -33,8 +32,9 @@ import scala.concurrent.ExecutionContext.Implicits.global * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function */ -abstract class DBFunction[T, R, Q <: Query](schema: DBSchema[Q], functionNameOverride: Option[String] = None) extends DBFunctionFabric { - type QR +abstract class DBFunction[T, R](val schema: DBSchema, functionNameOverride: Option[String] = None) extends DBFunctionFabric[T,R] { + + override type QueryType[X] = schema.dBEngine.QueryType[X] val functionName: String = { val fn = functionNameOverride.getOrElse(schema.objectNameFromClassName(getClass)) @@ -49,9 +49,8 @@ abstract class DBFunction[T, R, Q <: Query](schema: DBSchema[Q], functionNameOve override protected def fieldsToSelect: Seq[String] = super.fieldsToSelect //TODO should get the names from R #6 - protected def query(values: T): Q + //protected def query[Q >: QueryType[R]](values: T): Q - protected def converter: QR => R /** * For the given output it returns a function to execute the SQL query and interpret the results. * Basically it should create a function which contains a query to be executable and executed on on the [[DBExecutor]] @@ -61,15 +60,15 @@ abstract class DBFunction[T, R, Q <: Query](schema: DBSchema[Q], functionNameOve */ protected def execute(values: T): Future[Seq[R]] = { - schema.dBEngine.execute(query(values), converter) + schema.dBEngine.execute(query(values)) } protected def unique(values: T): Future[R] = { - schema.dBEngine.unique(query(values), converter) + schema.dBEngine.unique(query(values)) } protected def option(values: T): Future[Option[R]] = { - schema.dBEngine.option(query(values), converter) + schema.dBEngine.option(query(values)) } } @@ -85,8 +84,8 @@ object DBFunction { * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function */ - abstract class DBSeqFunction[T, R, Q <: Query, QR](schema: DBSchema[Q], functionNameOverride: Option[String] = None) - extends DBFunction[T, R, Q, QR](schema, functionNameOverride) { + abstract class DBSeqFunction[T, R](schema: DBSchema, functionNameOverride: Option[String] = None) + extends DBFunction[T, R](schema, functionNameOverride) { def apply(values: T): Future[Seq[R]] = execute(values) } @@ -100,8 +99,8 @@ object DBFunction { * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function */ - abstract class DBUniqueFunction[T, R, Q <: Query, QR](schema: DBSchema[Q], functionNameOverride: Option[String] = None) - extends DBFunction[T, R, Q, QR](schema, functionNameOverride) { + abstract class DBUniqueFunction[T, R](schema: DBSchema, functionNameOverride: Option[String] = None) + extends DBFunction[T, R](schema, functionNameOverride) { def apply(values: T): Future[R] = unique(values) } @@ -115,8 +114,8 @@ object DBFunction { * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function */ - abstract class DBOptionFunction[T, R, Q <: Query, QR](schema: DBSchema[Q], functionNameOverride: Option[String] = None) - extends DBFunction[T, R, Q, QR](schema, functionNameOverride) { + abstract class DBOptionFunction[T, R](schema: DBSchema, functionNameOverride: Option[String] = None) + extends DBFunction[T, R](schema, functionNameOverride) { def apply(values: T): Future[Option[R]] = option(values) } } diff --git a/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala b/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala index c2fb5276..0aaf65f7 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala @@ -16,12 +16,21 @@ package za.co.absa.fadb +import za.co.absa.fadb.DBFunctionFabric.Query + +import scala.language.higherKinds + /** * This trait serves the purpose of introducing functions that are common to all DB Function objects and mix-in traits * that offer certain implementations. This trait should help with the inheritance of all of these */ trait DBFunctionFabric { + def functionName: String protected def fieldsToSelect: Seq[String] = Seq.empty } + +object DBFunctionFabric { + trait Query[R] +} diff --git a/core/src/main/scala/za/co/absa/fadb/DBSchema.scala b/core/src/main/scala/za/co/absa/fadb/DBSchema.scala index 4d813097..373d68b7 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBSchema.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBSchema.scala @@ -16,7 +16,6 @@ package za.co.absa.fadb -import za.co.absa.fadb.DBEngine.Query import za.co.absa.fadb.naming_conventions.NamingConvention /** @@ -29,10 +28,10 @@ import za.co.absa.fadb.naming_conventions.NamingConvention * @param namingConvention - the [[za.co.absa.fadb.naming_conventions.NamingConvention]](NamingConvention) prescribing how to convert a class name into a db object name * @tparam E - the engine of the executor type, e.g. Slick Database */ -abstract class DBSchema[Q <: Query](val dBEngine: DBEngine[Q], schemaNameOverride: Option[String] = None) +abstract class DBSchema(val dBEngine: DBEngine, schemaNameOverride: Option[String] = None) (implicit val namingConvention: NamingConvention) { - type QueryType[R] = dBEngine.QueryType[R] + //type QueryType[R] = dBEngine.QueryType[R] def objectNameFromClassName(c: Class[_]): String = { namingConvention.fromClassNamePerConvention(c) diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala index 36e3cc56..5e159622 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala @@ -25,11 +25,11 @@ import scala.util.Try /** * A basis for mix-in traits for [[DBFunction]] that support `status` and `status text` for easier handling */ -trait StatusHandling extends DBFunctionFabric{ +trait StatusHandling extends DBFunctionFabric { def namingConvention: NamingConvention - def checkStatus(status: Integer, statusText: String): Try[Unit] + protected def checkStatus(status: Integer, statusText: String): Try[Unit] def statusFieldName: String = namingConvention.stringPerConvention(defaultStatusFieldName) def statusTextFieldName: String = namingConvention.stringPerConvention(defaultStatusTextFieldName) diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.scala index 9ceebc11..9f22896f 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.scala @@ -18,7 +18,7 @@ package za.co.absa.fadb.statushandling import scala.util.{Failure, Success, Try} -trait UserDefinedStatusHandling extends StatusHandling{ +trait UserDefinedStatusHandling extends StatusHandling { def OKStatuses: Set[Integer] def checkStatus(status: Integer, statusText: String): Try[Unit] = { diff --git a/core/src/test/scala/za/co/absa/fadb/DBFunctionSuite.scala b/core/src/test/scala/za/co/absa/fadb/DBFunctionSuite.scala index f50332de..093673ff 100644 --- a/core/src/test/scala/za/co/absa/fadb/DBFunctionSuite.scala +++ b/core/src/test/scala/za/co/absa/fadb/DBFunctionSuite.scala @@ -22,22 +22,20 @@ import scala.concurrent.Future import za.co.absa.fadb.naming_conventions.SnakeCaseNaming.Implicits.namingConvention class DBFunctionSuite extends AnyFunSuite { - private type Engine = String // just an engine type, not relevant Here - - private object ExecutorThrow extends DBExecutor[String] { - override def run[R](fnc: QueryFunction[Engine, R]): Future[Seq[R]] = { + private object EngineThrow extends DBEngine { + override def run[R](query: QueryType[R]): Future[Seq[R]] = { throw new Exception("Should never get here") } } - private object FooNamed extends DBSchema(ExecutorThrow) - private object FooNameless extends DBSchema(ExecutorThrow, Some("")) + private object FooNamed extends DBSchema(EngineThrow) + private object FooNameless extends DBSchema(EngineThrow, Some("")) test("Function name check"){ - case class MyFunction(schema: DBSchema[Engine]) extends DBFunction(schema) { - override protected def queryFunction(values: Nothing): QueryFunction[Engine, Nothing] = { - throw new Exception("Should never get here") - } + case class MyFunction(override val schema: DBSchema) extends DBFunction(schema) { + //override protected def query(values: Nothing): schema.dBEngine.QueryType[Nothing] = throw new Exception("Should never get here") + + override protected def query[Q >: schema.dBEngine.QueryType[Nothing]](values: Nothing): Q = ??? //TODO } val fnc1 = MyFunction(FooNamed) @@ -48,10 +46,9 @@ class DBFunctionSuite extends AnyFunSuite { } test("Function name override check"){ - case class MyFunction(schema: DBSchema[Engine]) extends DBFunction(schema, Some("bar")) { - override protected def queryFunction(values: Nothing): QueryFunction[Engine, Nothing] = { - throw new Exception("Should never get here") - } + case class MyFunction(override val schema: DBSchema) extends DBFunction(schema, Some("bar")) { +// override protected def query(values: Nothing): schema.dBEngine.QueryType[Nothing] = throw new Exception("Should never get here") + override protected def query[Q >: schema.dBEngine.QueryType[Nothing]](values: Nothing): Q = ??? //TODO } val fnc1 = MyFunction(FooNamed) diff --git a/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala b/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala index cae060cd..529ae609 100644 --- a/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala +++ b/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala @@ -18,66 +18,28 @@ package za.co.absa.fadb import org.scalatest.funsuite.AnyFunSuite import za.co.absa.fadb.naming_conventions.SnakeCaseNaming.Implicits.namingConvention -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration.Duration import scala.concurrent.{Await, Future} class DBSchemaSuite extends AnyFunSuite { - private val awaitTime = Duration("1 sec") - private val executorThrow = new DBExecutor[String] { - override def run[R](fnc: QueryFunction[String, R]): Future[Seq[R]] = { + private object EngineThrow extends DBEngine { + override def run[R](query: QueryType[R]): Future[Seq[R]] = { throw new Exception("Should never get here") } } - private val executorResend = new DBExecutor[String] { - override def run[Int](fnc: QueryFunction[String, Int]): Future[Seq[Int]] = { - fnc("") - } - } - test("schema name default") { - class Foo(executor: DBExecutor[String]) extends DBSchema(executor) - val schema = new Foo(executorThrow) + class Foo extends DBSchema(EngineThrow) + val schema = new Foo assert(schema.schemaName == "foo") } test("schema name overridden") { - class Foo(executor: DBExecutor[String]) extends DBSchema(executor, Some("bar")) + class Foo extends DBSchema(EngineThrow, Some("bar")) - val schema = new Foo(executorThrow) + val schema = new Foo assert(schema.schemaName == "bar") } - test("Test run call over") { - def queryFncSeq(s: String): Future[Seq[Int]] = { - Future { - Seq(1, 2, 3) - } - } - def queryFncEmpty(s: String): Future[Seq[Int]] = { - Future { - Seq.empty - } - } - - class Foo(executor: DBExecutor[String]) extends DBSchema(executor) - val schema = new Foo(executorResend) - - val resultExecuteSeq = Await.result(schema.execute(queryFncSeq), awaitTime) - assert(resultExecuteSeq == Seq(1, 2, 3)) - val resultUniqueSeq= Await.result(schema.unique(queryFncSeq), awaitTime) - assert(resultUniqueSeq == 1) - val resultOptionSeq = Await.result(schema.option(queryFncSeq), awaitTime) - assert(resultOptionSeq.contains(1)) - - val resultExecuteEmpty = Await.result(schema.execute(queryFncEmpty), awaitTime) - assert(resultExecuteEmpty.isEmpty) - val resultOptionEmpty = Await.result(schema.option(queryFncEmpty), awaitTime) - assert(resultOptionEmpty.isEmpty) - - } - } diff --git a/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingTest.scala b/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingTest.scala new file mode 100644 index 00000000..4345e99c --- /dev/null +++ b/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingTest.scala @@ -0,0 +1,43 @@ +/* + * Copyright 2022 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.fadb.statushandling + +import org.scalatest.funsuite.AnyFunSuite +import za.co.absa.fadb.DBFunctionFabric +import za.co.absa.fadb.naming_conventions.{NamingConvention, SnakeCaseNaming} + +import scala.util.Try + +class StatusHandlingTest extends AnyFunSuite { + test("Fields to select filled with default values") { + trait FooDBFunction extends DBFunctionFabric { + override def fieldsToSelect: Seq[String] = Seq("alpha", "beta") + } + + class StatusHandlingForTest extends FooDBFunction with StatusHandling { + override def functionName: String = "Never needed" + override def namingConvention: NamingConvention = SnakeCaseNaming.Implicits.namingConvention + + override protected def checkStatus(status: Integer, statusText: String): Try[Unit] = ??? + override def fieldsToSelect: Seq[String] = super.fieldsToSelect + } + + val statusHandling = new StatusHandlingForTest + assert(statusHandling.fieldsToSelect == Seq("status", "status_text", "alpha", "beta")) + + } +} diff --git a/core/src/test/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandlingTest.scala b/core/src/test/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandlingTest.scala index a371b0b9..7d421362 100644 --- a/core/src/test/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandlingTest.scala +++ b/core/src/test/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandlingTest.scala @@ -17,17 +17,19 @@ package za.co.absa.fadb.statushandling.fadbstandard import org.scalatest.funsuite.AnyFunSuite +import za.co.absa.fadb.naming_conventions.{NamingConvention, SnakeCaseNaming} import za.co.absa.fadb.statushandling.StatusException import za.co.absa.fadb.statushandling.StatusException._ import scala.reflect.ClassTag -import scala.util.{Failure, Try} +import scala.util.Try class StandardStatusHandlingTest extends AnyFunSuite { test("Verify checkStatus error mapping") { class StandardStatusHandlingForTest extends StandardStatusHandling { override def checkStatus(status: Integer, statusText: String): Try[Unit] = super.checkStatus(status, statusText) override def functionName: String = "Never needed" + override def namingConvention: NamingConvention = SnakeCaseNaming.Implicits.namingConvention } def assertCheckStatusFailure[F <: StatusException](status: Int, statusText: String) diff --git a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala index 9d6660a3..958c6c9f 100644 --- a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala +++ b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala @@ -18,8 +18,6 @@ package za.co.absa.fadb.examples.enceladus import za.co.absa.fadb.DBSchema import za.co.absa.fadb.slick.{SlickPgEngine, SlickPgFunction, SlickPgFunctionWithStatusSupport, SlickQuery} -import za.co.absa.fadb.slick.SlickPgFunctionWithStatusSupport._ -import za.co.absa.fadb.slick.SlickPgFunction.{DBSeqFunction => DBSeqFunctionNoStatus} import za.co.absa.fadb.naming_conventions.SnakeCaseNaming.Implicits.namingConvention import slick.jdbc.{GetResult, SQLActionBuilder} import slick.jdbc.PostgresProfile.api._ @@ -27,11 +25,14 @@ import slick.jdbc.PostgresProfile.api._ import java.sql.Timestamp import scala.concurrent.Future import DatasetSchema._ +import za.co.absa.fadb.DBFunction.{DBSeqFunction, DBUniqueFunction} import za.co.absa.fadb.statushandling.{StatusException, UserDefinedStatusHandling} class DatasetSchema(engine: SlickPgEngine) extends DBSchema(engine) { - private implicit val schema: DBSchema[SlickQuery] = this + + + private implicit val schema: DBSchema = this val addSchema = new AddSchema val getSchema = new GetSchema val listSchemas = new ListSchemas @@ -39,7 +40,6 @@ class DatasetSchema(engine: SlickPgEngine) extends DBSchema(engine) { object DatasetSchema { - type ExecutorEngineType = Database case class SchemaInput(schemaName: String, schemaVersion: Int, @@ -73,73 +73,62 @@ object DatasetSchema { Schema(r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<) }) - final class AddSchema(implicit schema: DBSchema[SlickQuery]) + final class AddSchema(implicit schema: DBSchema) extends DBUniqueFunction[SchemaInput, Long](schema) + with SlickPgFunction[SchemaInput, Long] with UserDefinedStatusHandling { + override protected def sql(values: SchemaInput): SQLActionBuilder = { + sql"""SELECT A.status, A.status_text, A.id_schema_version + FROM #$functionName(${values.schemaName}, ${values.schemaVersion}, ${values.schemaDescription}, + ${values.fields}::JSONB, ${values.userName} + ) A;""" + } -// TODO --- -// override protected def sqlToCallFunction(values: SchemaInput): SQLActionBuilder = { -// sql"""SELECT A.status, A.status_text, A.id_schema_version -// FROM #$functionName(${values.schemaName}, ${values.schemaVersion}, ${values.schemaDescription}, -// ${values.fields}::JSONB, ${values.userName} -// ) A;""" -// } -// -// override protected def resultConverter: GetResult[Long] = { -// val gr:GetResult[Long] = GetResult(r => { -// val status: Int = r.<< -// val statusText: String = r.<< -// if (status != 201) throw new StatusException(status, statusText) -// r.<< -// }) -// gr -// } + override protected def slickConverter: GetResult[Long] = GetResult.GetLong override def OKStatuses: Set[Integer] = Set(201) + } - final class GetSchema(implicit schema: DBSchema[SlickQuery]) + final class GetSchema(implicit schema: DBSchema) extends DBUniqueFunction[(String, Option[Int]), Schema](schema) + with SlickPgFunctionWithStatusSupport[(String, Option[Int]), Schema] with UserDefinedStatusHandling { - def apply(id: Long): Future[Schema] = { - val sql = - sql"""SELECT A.* - FROM #$functionName($id) A;""" +// TODO +++ +// def apply(id: Long): Future[Schema] = { +// val sql = +// sql"""SELECT A.* +// FROM #$functionName($id) A;""" +// +// val slickQuery: schema.QueryType[Schema] = SlickQuery(sql, slickConverter) +// schema.dBEngine.unique[Schema](slickQuery) +// } - schema.unique(makeQueryFunction(sql)(resultConverter)) + override protected def sql(values: (String, Option[Int])): SQLActionBuilder = { + sql"""SELECT A.* + FROM #$functionName(${values._1}, ${values._2}) A;""" } -// TODO --- -// override protected def sqlToCallFunction(values: (String, Option[Int])): SQLActionBuilder = { -// sql"""SELECT A.* -// FROM #$functionName(${values._1}, ${values._2}) A;""" -// } -// -// override protected def resultConverter: GetResult[Schema] = DatasetSchema.GetSchemaImplicit + override protected val slickConverter: GetResult[Schema] = GetResult{r => + Schema(r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<) + } + + override val OKStatuses: Set[Integer] = Set(200) } - final class ListSchemas(implicit schema: DBSchema[SlickQuery]) - extends DBSeqFunctionNoStatus[Boolean, SchemaHeader](schema) { + final class ListSchemas(implicit schema: DBSchema) + extends DBSeqFunction[Boolean, SchemaHeader](schema) + with SlickPgFunction[Boolean, SchemaHeader] { override def apply(values: Boolean = false): Future[Seq[SchemaHeader]] = super.apply(values) - override def fieldsToSelect: Seq[String] = { - super.fieldsToSelect + override protected def sql(values: Boolean): SQLActionBuilder = { + sql"""SELECT A.schema_name, A.schema_latest_version + FROM #$functionName($values) as A;""" } - protected def query(values: T): SlickQuery = { - - } - -// TODO --- -// override protected def sqlToCallFunction(values: Boolean): SQLActionBuilder = { -// sql"""SELECT A.schema_name, A.schema_latest_version -// FROM #$functionName($values) as A;""" -// } -// -// override protected def resultConverter: GetResult[SchemaHeader] = DatasetSchema.SchemaHeaderImplicit - + override protected val slickConverter: GetResult[SchemaHeader] = GetResult(r => {SchemaHeader(r.<<, r.<<)}) } } diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala index 42d84755..7a05e655 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala @@ -23,18 +23,16 @@ import za.co.absa.fadb.DBEngine import scala.concurrent.Future import slick.jdbc.PostgresProfile.api._ +import scala.language.higherKinds + class SlickPgEngine(val db: Database) extends DBEngine { - type QueryType[R] = SlickQuery[R] + override type QueryType[R] = SlickQuery[R] - override protected def run[QR >: PositionedResult, R](query: SlickQuery, converter: QR => R): Future[Seq[R]] = { + override protected def run[R](query: QueryType[R]): Future[Seq[R]] = { // It can be expected that a GetResult will be passed into the run function as converter. // Unfortunately it has to be recreated to be used by Slick - val getResult: GetResult[R] = GetResult{ r => - val pr: PositionedResult = r - converter(pr) - } - val slickAction = query.sql.as[R](getResult) + val slickAction = query.sql.as[R](query.getResult) db.run(slickAction) } diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala index cf5b44c6..69670a06 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala @@ -16,10 +16,13 @@ package za.co.absa.fadb.slick -import slick.jdbc.{GetResult, PositionedResult} -import za.co.absa.fadb.{DBFunction, DBFunctionFabric} +import slick.jdbc.{GetResult, PositionedResult, SQLActionBuilder} +import za.co.absa.fadb.{DBFunction, DBFunctionFabric, DBSchema} trait SlickPgFunction[T, R] extends DBFunctionFabric { + + val schema: DBSchema + protected val alias = "A" protected def selectEntry: String = { @@ -36,16 +39,12 @@ trait SlickPgFunction[T, R] extends DBFunctionFabric { } } - protected def query(values: T): SlickQuery + protected def query[Q >: schema.dBEngine.QueryType[R]](values: T): Q = { + val + new QueryType(sql(values), slickConverter) + } + protected def sql(values: T): SQLActionBuilder protected def slickConverter: GetResult[R] - protected def converter: PositionedResult => R = slickConverter } - -object SlickPgFunction { - type DBSeqFunction[T, R] = DBFunction.DBSeqFunction[T, R, SlickQuery, PositionedResult] with SlickPgFunction[T, R] - type DBUniqueFunction[T, R] = DBFunction.DBUniqueFunction[T, R, SlickQuery, PositionedResult] with SlickPgFunction[T, R] - type DBOptionFunction[T, R] = DBFunction.DBOptionFunction[T, R, SlickQuery, PositionedResult] with SlickPgFunction[T, R] -} - diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala index 05f96411..58f3e982 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala @@ -16,7 +16,7 @@ package za.co.absa.fadb.slick -import slick.jdbc.PositionedResult +import slick.jdbc.{GetResult, PositionedResult} import za.co.absa.fadb.DBFunction import scala.util.Try @@ -25,16 +25,15 @@ trait SlickPgFunctionWithStatusSupport[T, R] extends SlickPgFunction[T, R] { protected def checkStatus(status: Integer, statusText: String): Try[Unit] - override protected def converter: PositionedResult => R = { queryResult => + private def converterWithStatus(queryResult: PositionedResult, actualConverter: GetResult[R]): R = { val status: Int = queryResult.<< val statusText: String = queryResult.<< checkStatus(status, statusText).get //throw exception if status was off - slickConverter(queryResult) + actualConverter(queryResult) } -} -object SlickPgFunctionWithStatusSupport { - type DBSeqFunction[T, R] = DBFunction.DBSeqFunction[T, R, SlickQuery, PositionedResult] with SlickPgFunctionWithStatusSupport[T, R] - type DBUniqueFunction[T, R] = DBFunction.DBUniqueFunction[T, R, SlickQuery, PositionedResult] with SlickPgFunctionWithStatusSupport[T, R] - type DBOptionFunction[T, R] = DBFunction.DBOptionFunction[T, R, SlickQuery, PositionedResult] with SlickPgFunctionWithStatusSupport[T, R] + override protected def query[Q >: QueryType[R]](values: T): Q = { + val original = super.query(values) + new QueryType(original.sql, GetResult{converterWithStatus(_, original.getResult)}) + } } diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala index 72eb7930..f67c1a80 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala @@ -17,9 +17,7 @@ package za.co.absa.fadb.slick import slick.jdbc.{GetResult, SQLActionBuilder} -import za.co.absa.fadb.DBEngine.Query +import za.co.absa.fadb.DBFunctionFabric.Query -case class SlickQuery[R](sql: SQLActionBuilder, getResult: GetResult[R]) extends Query { - -} +class SlickQuery[R](val sql: SQLActionBuilder, val getResult: GetResult[R]) extends Query[R] From e033a35a5d027dda1dc27a90775f026f7553f2b1 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Thu, 1 Jun 2023 15:09:24 +0200 Subject: [PATCH 03/17] * Still work in progress, already builds, just one step remains, but maybe it's a step from different galaxy :-D --- .../main/scala/za/co/absa/fadb/DBEngine.scala | 6 - .../scala/za/co/absa/fadb/DBFunction.scala | 6 +- .../za/co/absa/fadb/DBFunctionFabric.scala | 6 - .../main/scala/za/co/absa/fadb/Query.scala | 21 ++ .../za/co/absa/fadb/DBFunctionSuite.scala | 77 +++---- .../enceladus/DatasetSchemaSuite.scala | 199 +++++++++--------- .../co/absa/fadb/slick/SlickPgFunction.scala | 37 +++- .../SlickPgFunctionWithStatusSupport.scala | 6 +- .../za/co/absa/fadb/slick/SlickQuery.scala | 2 +- 9 files changed, 198 insertions(+), 162 deletions(-) create mode 100644 core/src/main/scala/za/co/absa/fadb/Query.scala diff --git a/core/src/main/scala/za/co/absa/fadb/DBEngine.scala b/core/src/main/scala/za/co/absa/fadb/DBEngine.scala index 15022aea..bbd02d1b 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBEngine.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBEngine.scala @@ -16,8 +16,6 @@ package za.co.absa.fadb -import za.co.absa.fadb.DBFunctionFabric.Query - import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scala.language.higherKinds @@ -39,7 +37,3 @@ trait DBEngine { run(query).map(_.headOption) } } - -object DBEngine { - // trait Query[R] TODO --- -} diff --git a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala index a9ce8111..9e74482e 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala @@ -32,9 +32,7 @@ import scala.concurrent.ExecutionContext.Implicits.global * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function */ -abstract class DBFunction[T, R](val schema: DBSchema, functionNameOverride: Option[String] = None) extends DBFunctionFabric[T,R] { - - override type QueryType[X] = schema.dBEngine.QueryType[X] +abstract class DBFunction[T, R](val schema: DBSchema, functionNameOverride: Option[String] = None) extends DBFunctionFabric { val functionName: String = { val fn = functionNameOverride.getOrElse(schema.objectNameFromClassName(getClass)) @@ -49,7 +47,7 @@ abstract class DBFunction[T, R](val schema: DBSchema, functionNameOverride: Opti override protected def fieldsToSelect: Seq[String] = super.fieldsToSelect //TODO should get the names from R #6 - //protected def query[Q >: QueryType[R]](values: T): Q + protected def query[Q <: schema.dBEngine.QueryType[R]](values: T): Q /** * For the given output it returns a function to execute the SQL query and interpret the results. diff --git a/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala b/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala index 0aaf65f7..dffc8185 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala @@ -16,8 +16,6 @@ package za.co.absa.fadb -import za.co.absa.fadb.DBFunctionFabric.Query - import scala.language.higherKinds /** @@ -30,7 +28,3 @@ trait DBFunctionFabric { protected def fieldsToSelect: Seq[String] = Seq.empty } - -object DBFunctionFabric { - trait Query[R] -} diff --git a/core/src/main/scala/za/co/absa/fadb/Query.scala b/core/src/main/scala/za/co/absa/fadb/Query.scala new file mode 100644 index 00000000..f944eb03 --- /dev/null +++ b/core/src/main/scala/za/co/absa/fadb/Query.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.fadb + +class Query[R] { + val foo = "bar" //TODO --- +} diff --git a/core/src/test/scala/za/co/absa/fadb/DBFunctionSuite.scala b/core/src/test/scala/za/co/absa/fadb/DBFunctionSuite.scala index 093673ff..ef2cab1a 100644 --- a/core/src/test/scala/za/co/absa/fadb/DBFunctionSuite.scala +++ b/core/src/test/scala/za/co/absa/fadb/DBFunctionSuite.scala @@ -21,41 +21,42 @@ import org.scalatest.funsuite.AnyFunSuite import scala.concurrent.Future import za.co.absa.fadb.naming_conventions.SnakeCaseNaming.Implicits.namingConvention -class DBFunctionSuite extends AnyFunSuite { - private object EngineThrow extends DBEngine { - override def run[R](query: QueryType[R]): Future[Seq[R]] = { - throw new Exception("Should never get here") - } - } - - private object FooNamed extends DBSchema(EngineThrow) - private object FooNameless extends DBSchema(EngineThrow, Some("")) - - test("Function name check"){ - case class MyFunction(override val schema: DBSchema) extends DBFunction(schema) { - //override protected def query(values: Nothing): schema.dBEngine.QueryType[Nothing] = throw new Exception("Should never get here") - - override protected def query[Q >: schema.dBEngine.QueryType[Nothing]](values: Nothing): Q = ??? //TODO - } - - val fnc1 = MyFunction(FooNamed) - val fnc2 = MyFunction(FooNameless) - - assert(fnc1.functionName == "foo_named.my_function") - assert(fnc2.functionName == "my_function") - } - - test("Function name override check"){ - case class MyFunction(override val schema: DBSchema) extends DBFunction(schema, Some("bar")) { -// override protected def query(values: Nothing): schema.dBEngine.QueryType[Nothing] = throw new Exception("Should never get here") - override protected def query[Q >: schema.dBEngine.QueryType[Nothing]](values: Nothing): Q = ??? //TODO - } - - val fnc1 = MyFunction(FooNamed) - val fnc2 = MyFunction(FooNameless) - - assert(fnc1.functionName == "foo_named.bar") - assert(fnc2.functionName == "bar") - } - -} +// +//class DBFunctionSuite extends AnyFunSuite { +// private object EngineThrow extends DBEngine { +// override def run[R](query: QueryType[R]): Future[Seq[R]] = { +// throw new Exception("Should never get here") +// } +// } +// +// private object FooNamed extends DBSchema(EngineThrow) +// private object FooNameless extends DBSchema(EngineThrow, Some("")) +// +// test("Function name check"){ +// case class MyFunction(override val schema: DBSchema) extends DBFunction(schema) { +// //override protected def query(values: Nothing): schema.dBEngine.QueryType[Nothing] = throw new Exception("Should never get here") +// +// override protected def query[Q >: schema.dBEngine.QueryType[Nothing]](values: Nothing): Q = ??? //TODO +// } +// +// val fnc1 = MyFunction(FooNamed) +// val fnc2 = MyFunction(FooNameless) +// +// assert(fnc1.functionName == "foo_named.my_function") +// assert(fnc2.functionName == "my_function") +// } +// +// test("Function name override check"){ +// case class MyFunction(override val schema: DBSchema) extends DBFunction(schema, Some("bar")) { +//// override protected def query(values: Nothing): schema.dBEngine.QueryType[Nothing] = throw new Exception("Should never get here") +// override protected def query[Q >: schema.dBEngine.QueryType[Nothing]](values: Nothing): Q = ??? //TODO +// } +// +// val fnc1 = MyFunction(FooNamed) +// val fnc2 = MyFunction(FooNameless) +// +// assert(fnc1.functionName == "foo_named.bar") +// assert(fnc2.functionName == "bar") +// } +// +//} diff --git a/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala b/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala index 905fc232..6de19a1c 100644 --- a/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala +++ b/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala @@ -27,102 +27,103 @@ import za.co.absa.fadb.statushandling.StatusException import scala.concurrent.Await import scala.concurrent.duration.Duration -class DatasetSchemaSuite extends AnyWordSpec with Matchers { - private val db = Database.forConfig("menasdb") - private val executor = new SlickPgExecutor(db) - private val schemas = new DatasetSchema(executor) - - private def checkException(exception: StatusException): Unit = { - println(s"Requested failed with: ${exception.status} - ${exception.statusText}") - } - - // test cases are set to be ignored now, as they are not idempotent and require other project's (Enceladus) data structures - - "listSchemas" should { - "list the schemas" ignore { - val ls = schemas.listSchemas() - val result = Await.result(ls, Duration.Inf) - result.foreach(println) - } - } - - "getSchema" should { - "return the particular schema" when { - "given name and version" ignore { - val ls = schemas.getSchema(("aaa", Option(1))) - val result = Await.result(ls, Duration.Inf) - println(result) - } - "given id" ignore { - val gs = schemas.getSchema(1000000000000051L) - val result = Await.result(gs, Duration.Inf) - println(result) - } - } - "return the latest schema version" when { - "only the schema name is given" ignore { - val ls = schemas.getSchema(("aaa", None)) - val result = Await.result(ls, Duration.Inf) - println(result) - } - } - "fail" when { - "schema does not exist" ignore { - val exception = intercept[StatusException] { - val gs = schemas.getSchema(("xxx", None)) - Await.result(gs, Duration.Inf) - } - checkException(exception) - } - "requested schema version does not exist" ignore { - val exception = intercept[StatusException] { - val gs = schemas.getSchema(("aaa", Some(1000))) - Await.result(gs, Duration.Inf) - } - checkException(exception) - } - } - } - - "addSchema" should { - "add a schema" ignore { - val schemaInput = SchemaInput( - schemaName = "bbe", - schemaVersion = 1, - schemaDescription = Option("Hello World"), - fields = Option("""{"lorem": "ipsum"}"""), - userName = "david" - ) - val result = Await.result(schemas.addSchema(schemaInput), Duration.Inf) - println(result) - } - "fail" when { - "Schema already exists" ignore { - val schemaInput = SchemaInput( - schemaName = "aaa", - schemaVersion = 2, - schemaDescription = Option("Updates"), - fields = Option("""{"foo": "bar"}"""), - userName = "david" - ) - val exception = intercept[StatusException] { - Await.result(schemas.addSchema(schemaInput), Duration.Inf) - } - checkException(exception) - } - "Schema version wrong" ignore { - val schemaInput = SchemaInput( - schemaName = "aaa", - schemaVersion = 1000, - schemaDescription = Option("Will fail"), - fields = Option("""{"not_getting_in": "1"}"""), - userName = "david" - ) - val exception = intercept[StatusException] { - Await.result(schemas.addSchema(schemaInput), Duration.Inf) - } - checkException(exception) - } - } - } -} +// TODO +//class DatasetSchemaSuite extends AnyWordSpec with Matchers { +// private val db = Database.forConfig("menasdb") +// private val executor = new SlickPgExecutor(db) +// private val schemas = new DatasetSchema(executor) +// +// private def checkException(exception: StatusException): Unit = { +// println(s"Requested failed with: ${exception.status} - ${exception.statusText}") +// } +// +// // test cases are set to be ignored now, as they are not idempotent and require other project's (Enceladus) data structures +// +// "listSchemas" should { +// "list the schemas" ignore { +// val ls = schemas.listSchemas() +// val result = Await.result(ls, Duration.Inf) +// result.foreach(println) +// } +// } +// +// "getSchema" should { +// "return the particular schema" when { +// "given name and version" ignore { +// val ls = schemas.getSchema(("aaa", Option(1))) +// val result = Await.result(ls, Duration.Inf) +// println(result) +// } +// "given id" ignore { +// val gs = schemas.getSchema(1000000000000051L) +// val result = Await.result(gs, Duration.Inf) +// println(result) +// } +// } +// "return the latest schema version" when { +// "only the schema name is given" ignore { +// val ls = schemas.getSchema(("aaa", None)) +// val result = Await.result(ls, Duration.Inf) +// println(result) +// } +// } +// "fail" when { +// "schema does not exist" ignore { +// val exception = intercept[StatusException] { +// val gs = schemas.getSchema(("xxx", None)) +// Await.result(gs, Duration.Inf) +// } +// checkException(exception) +// } +// "requested schema version does not exist" ignore { +// val exception = intercept[StatusException] { +// val gs = schemas.getSchema(("aaa", Some(1000))) +// Await.result(gs, Duration.Inf) +// } +// checkException(exception) +// } +// } +// } +// +// "addSchema" should { +// "add a schema" ignore { +// val schemaInput = SchemaInput( +// schemaName = "bbe", +// schemaVersion = 1, +// schemaDescription = Option("Hello World"), +// fields = Option("""{"lorem": "ipsum"}"""), +// userName = "david" +// ) +// val result = Await.result(schemas.addSchema(schemaInput), Duration.Inf) +// println(result) +// } +// "fail" when { +// "Schema already exists" ignore { +// val schemaInput = SchemaInput( +// schemaName = "aaa", +// schemaVersion = 2, +// schemaDescription = Option("Updates"), +// fields = Option("""{"foo": "bar"}"""), +// userName = "david" +// ) +// val exception = intercept[StatusException] { +// Await.result(schemas.addSchema(schemaInput), Duration.Inf) +// } +// checkException(exception) +// } +// "Schema version wrong" ignore { +// val schemaInput = SchemaInput( +// schemaName = "aaa", +// schemaVersion = 1000, +// schemaDescription = Option("Will fail"), +// fields = Option("""{"not_getting_in": "1"}"""), +// userName = "david" +// ) +// val exception = intercept[StatusException] { +// Await.result(schemas.addSchema(schemaInput), Duration.Inf) +// } +// checkException(exception) +// } +// } +// } +//} diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala index 69670a06..4b63b8f8 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala @@ -17,14 +17,30 @@ package za.co.absa.fadb.slick import slick.jdbc.{GetResult, PositionedResult, SQLActionBuilder} -import za.co.absa.fadb.{DBFunction, DBFunctionFabric, DBSchema} +import za.co.absa.fadb.{DBFunction, DBFunctionFabric, DBSchema, Query} + +import scala.language.higherKinds trait SlickPgFunction[T, R] extends DBFunctionFabric { val schema: DBSchema + + trait Foo[A] {} //Query + + type MyFunction[A] = Foo[A] + + class MyFunction2[A1] extends MyFunction[A1] //SlickQuery + + def bar[Q[String] >: MyFunction[String] ]: Q[String] = { + new MyFunction2[String] + } + + protected val alias = "A" + val set: Set[String] = Set("a") + protected def selectEntry: String = { val fieldsSeq = fieldsToSelect if (fieldsSeq.isEmpty) { @@ -39,11 +55,24 @@ trait SlickPgFunction[T, R] extends DBFunctionFabric { } } - protected def query[Q >: schema.dBEngine.QueryType[R]](values: T): Q = { - val - new QueryType(sql(values), slickConverter) +// type MyQueryType[A] = Query[A] +// protected def query[Q[String] >: MyQueryType[String]](values: T): Q[String] = { + protected def query[Q <: schema.dBEngine.QueryType[R]](values: T): Q = { +// val q: Q = new SlickQuery(sql(values), slickConverter) +// val q2: Q = ??? +//// q2.foo +//// val q: (Q <: Query[R]) = new Query[R] {} +// new Q{} //TODO + ??? } +// protected def query(values: T): schema.dBEngine.QueryType[R] = { +// new SlickQuery(sql(values), slickConverter) +// ??? +// //new Query[R] {} +// } + + protected def sql(values: T): SQLActionBuilder protected def slickConverter: GetResult[R] diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala index 58f3e982..bcf910ad 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala @@ -32,8 +32,6 @@ trait SlickPgFunctionWithStatusSupport[T, R] extends SlickPgFunction[T, R] { actualConverter(queryResult) } - override protected def query[Q >: QueryType[R]](values: T): Q = { - val original = super.query(values) - new QueryType(original.sql, GetResult{converterWithStatus(_, original.getResult)}) - } + //TODO missing query function + } diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala index f67c1a80..ea7dea79 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala @@ -17,7 +17,7 @@ package za.co.absa.fadb.slick import slick.jdbc.{GetResult, SQLActionBuilder} -import za.co.absa.fadb.DBFunctionFabric.Query +import za.co.absa.fadb.Query class SlickQuery[R](val sql: SQLActionBuilder, val getResult: GetResult[R]) extends Query[R] From 8304276b5fb6279720d86da438e9d7c58ae71225 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Thu, 1 Jun 2023 16:56:08 +0200 Subject: [PATCH 04/17] * reshaped to shape of Shapeless --- .../main/scala/za/co/absa/fadb/DBEngine.scala | 18 ++++---- .../scala/za/co/absa/fadb/DBFunction.scala | 15 +++++- .../main/scala/za/co/absa/fadb/Query.scala | 16 ++++++- .../scala/za/co/absa/fadb/DBSchemaSuite.scala | 46 +++++++++---------- .../examples/enceladus/DatasetSchema.scala | 19 ++++---- .../za/co/absa/fadb/slick/SlickPgEngine.scala | 8 ++-- .../co/absa/fadb/slick/SlickPgFunction.scala | 38 +++++++-------- .../za/co/absa/fadb/slick/SlickQuery.scala | 3 +- 8 files changed, 95 insertions(+), 68 deletions(-) diff --git a/core/src/main/scala/za/co/absa/fadb/DBEngine.scala b/core/src/main/scala/za/co/absa/fadb/DBEngine.scala index bbd02d1b..499f17f4 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBEngine.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBEngine.scala @@ -22,18 +22,20 @@ import scala.language.higherKinds trait DBEngine { - type QueryType[R] <: Query[R] - // in future implementation the convertor might not be needed (ideally) - protected def run[R](query: QueryType[R]): Future[Seq[R]] + protected def run[R, Q <: Query.Aux[R]](query: Q): Future[Seq[R]] - def execute[R](query: QueryType[R]): Future[Seq[R]] = run(query) + def execute[R, Q <: Query.Aux[R]](query: Q): Future[Seq[query.RESULT]] = run[R, Q](query) - def unique[R](query: QueryType[R]): Future[R] = { - run(query).map(_.head) + def unique[R, Q <: Query.Aux[R]](query: Q): Future[query.RESULT] = { + run[R, Q](query).map(_.head) } - def option[R](query: QueryType[R]): Future[Option[R]] = { - run(query).map(_.headOption) + def option[R, Q <: Query.Aux[R]](query: Q): Future[Option[query.RESULT]] = { + run[R, Q](query).map(_.headOption) } } + +object DBEngine { + type Aux[Q, R] +} diff --git a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala index 9e74482e..d87b4a61 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala @@ -21,6 +21,15 @@ import za.co.absa.fadb.naming_conventions.NamingConvention import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global + +/* + +overriding method query in class DBFunction of type [Q >: CreateQuestion.this.QueryType[Unit]](values: za.co.absa.fadb.examples.aul.Questions.QuestionInput)Q; + method query in trait SlickPgFunctionWithStatusSupport of type [Q >: CreateQuestion.this.QueryType[Unit]](values: za.co.absa.fadb.examples.aul.Questions.QuestionInput)Q has incompatible type; + other members with override errors are: QueryType + case class CreateQuestion(implicit override val schema: DBSchema) + */ + /** * The most general abstraction of database function representation * The database name of the function is derives from the class name based on the provided naming convention (in schema) @@ -34,6 +43,8 @@ import scala.concurrent.ExecutionContext.Implicits.global */ abstract class DBFunction[T, R](val schema: DBSchema, functionNameOverride: Option[String] = None) extends DBFunctionFabric { + //type Q <: Query.Aux[R] + val functionName: String = { val fn = functionNameOverride.getOrElse(schema.objectNameFromClassName(getClass)) if (schema.schemaName.isEmpty) { @@ -47,7 +58,7 @@ abstract class DBFunction[T, R](val schema: DBSchema, functionNameOverride: Opti override protected def fieldsToSelect: Seq[String] = super.fieldsToSelect //TODO should get the names from R #6 - protected def query[Q <: schema.dBEngine.QueryType[R]](values: T): Q + protected def query(values: T): Query.Aux[R] /** * For the given output it returns a function to execute the SQL query and interpret the results. @@ -58,7 +69,7 @@ abstract class DBFunction[T, R](val schema: DBSchema, functionNameOverride: Opti */ protected def execute(values: T): Future[Seq[R]] = { - schema.dBEngine.execute(query(values)) + schema.dBEngine.execute[R, Query.Aux[R]](query(values)) } protected def unique(values: T): Future[R] = { diff --git a/core/src/main/scala/za/co/absa/fadb/Query.scala b/core/src/main/scala/za/co/absa/fadb/Query.scala index f944eb03..2d7b8d69 100644 --- a/core/src/main/scala/za/co/absa/fadb/Query.scala +++ b/core/src/main/scala/za/co/absa/fadb/Query.scala @@ -16,6 +16,18 @@ package za.co.absa.fadb -class Query[R] { - val foo = "bar" //TODO --- +trait Query { + type RESULT +// type SQL +// def sql: SQL +} + +object Query { +// type Aux[R, S] = Query { +// type RESULT = R +// type SQL = S +// } + type Aux[R] = Query { + type RESULT = R + } } diff --git a/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala b/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala index 529ae609..9a7e83ce 100644 --- a/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala +++ b/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala @@ -20,26 +20,26 @@ import za.co.absa.fadb.naming_conventions.SnakeCaseNaming.Implicits.namingConven import scala.concurrent.{Await, Future} -class DBSchemaSuite extends AnyFunSuite { - - private object EngineThrow extends DBEngine { - override def run[R](query: QueryType[R]): Future[Seq[R]] = { - throw new Exception("Should never get here") - } - } - - test("schema name default") { - - class Foo extends DBSchema(EngineThrow) - val schema = new Foo - assert(schema.schemaName == "foo") - } - - test("schema name overridden") { - class Foo extends DBSchema(EngineThrow, Some("bar")) - - val schema = new Foo - assert(schema.schemaName == "bar") - } - -} +//class DBSchemaSuite extends AnyFunSuite { +// +// private object EngineThrow extends DBEngine { +// override def run[R](query: QueryType[R]): Future[Seq[R]] = { +// throw new Exception("Should never get here") +// } +// } +// +// test("schema name default") { +// +// class Foo extends DBSchema(EngineThrow) +// val schema = new Foo +// assert(schema.schemaName == "foo") +// } +// +// test("schema name overridden") { +// class Foo extends DBSchema(EngineThrow, Some("bar")) +// +// val schema = new Foo +// assert(schema.schemaName == "bar") +// } +// +//} diff --git a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala index 958c6c9f..9df58976 100644 --- a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala +++ b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala @@ -91,20 +91,21 @@ object DatasetSchema { } - final class GetSchema(implicit schema: DBSchema) + final class GetSchema(implicit override val schema: DBSchema) extends DBUniqueFunction[(String, Option[Int]), Schema](schema) with SlickPgFunctionWithStatusSupport[(String, Option[Int]), Schema] with UserDefinedStatusHandling { -// TODO +++ -// def apply(id: Long): Future[Schema] = { -// val sql = -// sql"""SELECT A.* -// FROM #$functionName($id) A;""" +// def apply(id: Long): Future[Schema] = { +// val sql = +// sql"""SELECT A.* +// FROM #$functionName($id) A;""" // -// val slickQuery: schema.QueryType[Schema] = SlickQuery(sql, slickConverter) -// schema.dBEngine.unique[Schema](slickQuery) -// } +// type X = schema.dBEngine.QueryType[Schema] +// val slickQuery: X = ??? // new SlickQuery(sql, slickConverter) TODO +// val slickQuery2: X = new X() +// schema.dBEngine.unique[Schema](slickQuery) +// } override protected def sql(values: (String, Option[Int])): SQLActionBuilder = { sql"""SELECT A.* diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala index 7a05e655..d76be5a2 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala @@ -18,7 +18,7 @@ package za.co.absa.fadb.slick import slick.jdbc.{GetResult, PositionedResult} -import za.co.absa.fadb.DBEngine +import za.co.absa.fadb.{DBEngine, Query} import scala.concurrent.Future import slick.jdbc.PostgresProfile.api._ @@ -27,12 +27,12 @@ import scala.language.higherKinds class SlickPgEngine(val db: Database) extends DBEngine { - override type QueryType[R] = SlickQuery[R] + //override type QueryType[R] = SlickQuery[R] - override protected def run[R](query: QueryType[R]): Future[Seq[R]] = { + override protected def run[R, Q <: SlickQuery[R]](query: Q): Future[Seq[R]] = { // It can be expected that a GetResult will be passed into the run function as converter. // Unfortunately it has to be recreated to be used by Slick - val slickAction = query.sql.as[R](query.getResult) + val slickAction = query.sql.as[query.RESULT](query.getResult) db.run(slickAction) } diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala index 4b63b8f8..cdddab83 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala @@ -23,18 +23,20 @@ import scala.language.higherKinds trait SlickPgFunction[T, R] extends DBFunctionFabric { - val schema: DBSchema - - - trait Foo[A] {} //Query + type Q = SlickQuery[R] - type MyFunction[A] = Foo[A] + val schema: DBSchema - class MyFunction2[A1] extends MyFunction[A1] //SlickQuery - def bar[Q[String] >: MyFunction[String] ]: Q[String] = { - new MyFunction2[String] - } +// trait Foo[A] {} //Query +// +// type MyFunction[A] = Foo[A] +// +// class MyFunction2[A1] extends MyFunction[A1] //SlickQuery +// +// def bar[Q[String] >: MyFunction[String] ]: Q[String] = { +// new MyFunction2[String] +// } protected val alias = "A" @@ -55,15 +57,15 @@ trait SlickPgFunction[T, R] extends DBFunctionFabric { } } -// type MyQueryType[A] = Query[A] -// protected def query[Q[String] >: MyQueryType[String]](values: T): Q[String] = { - protected def query[Q <: schema.dBEngine.QueryType[R]](values: T): Q = { -// val q: Q = new SlickQuery(sql(values), slickConverter) -// val q2: Q = ??? -//// q2.foo -//// val q: (Q <: Query[R]) = new Query[R] {} -// new Q{} //TODO - ??? + // type MyQueryType[A] = Query[A] + // protected def query[Q[String] >: MyQueryType[String]](values: T): Q[String] = { + protected def query(values: T): Q = { + // val q: Q = new SlickQuery(sql(values), slickConverter) + // val q2: Q = ??? + //// q2.foo + //// val q: (Q <: Query[R]) = new Query[R] {} + // new Q{} //TODO + new SlickQuery(sql(values), slickConverter) } // protected def query(values: T): schema.dBEngine.QueryType[R] = { diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala index ea7dea79..b0f8892e 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala @@ -19,5 +19,4 @@ package za.co.absa.fadb.slick import slick.jdbc.{GetResult, SQLActionBuilder} import za.co.absa.fadb.Query -class SlickQuery[R](val sql: SQLActionBuilder, val getResult: GetResult[R]) extends Query[R] - +class SlickQuery[SLICKRESULT](val sql: SQLActionBuilder, val getResult: GetResult[SLICKRESULT]) extends Query {type RESULT = SLICKRESULT} From 58e045d4a303b748c6167d28886da534c43a0384 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Sun, 4 Jun 2023 09:26:04 +0200 Subject: [PATCH 05/17] * Massive refactoring finally working --- .../main/scala/za/co/absa/fadb/DBEngine.scala | 50 ++++- .../scala/za/co/absa/fadb/DBFunction.scala | 119 +++++++++-- .../za/co/absa/fadb/DBFunctionFabric.scala | 2 - .../main/scala/za/co/absa/fadb/DBSchema.scala | 26 ++- .../main/scala/za/co/absa/fadb/Query.scala | 18 +- .../main/scala/za/co/absa/fadb/package.scala | 29 --- .../FunctionStatus.scala} | 14 +- .../fadb/statushandling/StatusException.scala | 52 ++++- .../fadb/statushandling/StatusHandling.scala | 20 +- .../UserDefinedStatusHandling.scala | 13 +- .../fadbstandard/StandardStatusHandling.scala | 20 +- .../za/co/absa/fadb/DBFunctionSuite.scala | 78 +++---- .../statushandling/StatusExceptionSuite.scala | 45 ++++ ...ngTest.scala => StatusHandlingSuite.scala} | 4 +- .../UserDefinedStatusHandlingSuite.scala | 41 ++++ ...cala => StandardStatusHandlingSuite.scala} | 14 +- .../examples/enceladus/DatasetSchema.scala | 59 +++-- .../enceladus/DatasetSchemaSuite.scala | 202 +++++++++--------- .../za/co/absa/fadb/slick/SlickPgEngine.scala | 7 +- .../co/absa/fadb/slick/SlickPgExecutor.scala | 36 ---- .../co/absa/fadb/slick/SlickPgFunction.scala | 45 +--- .../SlickPgFunctionWithStatusSupport.scala | 15 +- .../za/co/absa/fadb/slick/SlickQuery.scala | 2 +- 23 files changed, 532 insertions(+), 379 deletions(-) delete mode 100644 core/src/main/scala/za/co/absa/fadb/package.scala rename core/src/main/scala/za/co/absa/fadb/{DBExecutor.scala => statushandling/FunctionStatus.scala} (62%) create mode 100644 core/src/test/scala/za/co/absa/fadb/statushandling/StatusExceptionSuite.scala rename core/src/test/scala/za/co/absa/fadb/statushandling/{StatusHandlingTest.scala => StatusHandlingSuite.scala} (90%) create mode 100644 core/src/test/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandlingSuite.scala rename core/src/test/scala/za/co/absa/fadb/statushandling/fadbstandard/{StandardStatusHandlingTest.scala => StandardStatusHandlingSuite.scala} (82%) delete mode 100644 slick/src/main/scala/za/co/absa/fadb/slick/SlickPgExecutor.scala diff --git a/core/src/main/scala/za/co/absa/fadb/DBEngine.scala b/core/src/main/scala/za/co/absa/fadb/DBEngine.scala index 499f17f4..931bb92e 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBEngine.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBEngine.scala @@ -20,22 +20,52 @@ import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scala.language.higherKinds +/** + * A basis to represent a database executor + */ trait DBEngine { - // in future implementation the convertor might not be needed (ideally) - protected def run[R, Q <: Query.Aux[R]](query: Q): Future[Seq[R]] + /** + * A type repesenting the (SQL) query within the engine + * @tparam X - the return type of the query + */ + type QueryType[X] <: Query[X] - def execute[R, Q <: Query.Aux[R]](query: Q): Future[Seq[query.RESULT]] = run[R, Q](query) + /** + * The actual query executioner of the queries of the engine + * @param query - the query to execute + * @tparam R - return the of the query + * @return - sequence of the results of database query + */ + protected def run[R](query: QueryType[R]): Future[Seq[R]] - def unique[R, Q <: Query.Aux[R]](query: Q): Future[query.RESULT] = { - run[R, Q](query).map(_.head) + /** + * Public method to execute when query is expected to return multiple results + * @param query - the query to execute + * @tparam R - return the of the query + * @return - sequence of the results of database query + */ + def execute[R](query: QueryType[R]): Future[Seq[R]] = run(query) + + /** + * Public method to execute when query is expected to return exactly one row + * @param query - the query to execute + * @tparam R - return the of the query + * @return - sequence of the results of database query + */ + def unique[R](query: QueryType[R]): Future[R] = { + run(query).map(_.head) } - def option[R, Q <: Query.Aux[R]](query: Q): Future[Option[query.RESULT]] = { - run[R, Q](query).map(_.headOption) + /** + * Public method to execute when query is expected to return one or no results + * @param query - the query to execute + * @tparam R - return the of the query + * @return - sequence of the results of database query + */ + + def option[R](query: QueryType[R]): Future[Option[R]] = { + run(query).map(_.headOption) } } -object DBEngine { - type Aux[Q, R] -} diff --git a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala index d87b4a61..3d625617 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala @@ -19,8 +19,6 @@ package za.co.absa.fadb import za.co.absa.fadb.naming_conventions.NamingConvention import scala.concurrent.Future -import scala.concurrent.ExecutionContext.Implicits.global - /* @@ -37,13 +35,41 @@ overriding method query in class DBFunction of type [Q * @param schema - the schema the function belongs into * @param functionNameOverride - in case the class name would not match the database function name, this gives the * possibility of override - * @tparam E - the type of the [[DBExecutor]] engine + */ + +/** + * + * @param functionNameOverride- in case the class name would not match the database function name, this gives the + * possibility of override + * @param schema - the schema the function belongs into + * @param dBEngine - the database engine that is supposed to execute the function (persumable contains + * connection to the database * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function + * @tparam E - the type of the [[DBEngine]] engine */ -abstract class DBFunction[T, R](val schema: DBSchema, functionNameOverride: Option[String] = None) extends DBFunctionFabric { +abstract class DBFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None) + (implicit val schema: DBSchema, val dBEngine: E) extends DBFunctionFabric { - //type Q <: Query.Aux[R] + def this(schema: DBSchema, functionNameOverride: String) + (implicit dBEngine: E) = { + this(Option(functionNameOverride))(schema, dBEngine) + } + + def this(schema: DBSchema) + (implicit dBEngine: E) = { + this(None)(schema, dBEngine) + } + + def this(dBEngine: E, functionNameOverride: String) + (implicit schema: DBSchema) = { + this(Option(functionNameOverride))(schema, dBEngine) + } + + def this(dBEngine: E) + (implicit schema: DBSchema) = { + this(None)(schema, dBEngine) + } val functionName: String = { val fn = functionNameOverride.getOrElse(schema.objectNameFromClassName(getClass)) @@ -58,7 +84,7 @@ abstract class DBFunction[T, R](val schema: DBSchema, functionNameOverride: Opti override protected def fieldsToSelect: Seq[String] = super.fieldsToSelect //TODO should get the names from R #6 - protected def query(values: T): Query.Aux[R] + protected def query(values: T): dBEngine.QueryType[R] /** * For the given output it returns a function to execute the SQL query and interpret the results. @@ -69,15 +95,15 @@ abstract class DBFunction[T, R](val schema: DBSchema, functionNameOverride: Opti */ protected def execute(values: T): Future[Seq[R]] = { - schema.dBEngine.execute[R, Query.Aux[R]](query(values)) + dBEngine.execute[R](query(values)) } protected def unique(values: T): Future[R] = { - schema.dBEngine.unique(query(values)) + dBEngine.unique(query(values)) } protected def option(values: T): Future[Option[R]] = { - schema.dBEngine.option(query(values)) + dBEngine.option(query(values)) } } @@ -93,8 +119,29 @@ object DBFunction { * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function */ - abstract class DBSeqFunction[T, R](schema: DBSchema, functionNameOverride: Option[String] = None) - extends DBFunction[T, R](schema, functionNameOverride) { + abstract class DBSeqFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None)(implicit schema: DBSchema, dBEngine: E) + extends DBFunction[T, R, E](functionNameOverride) { + + def this(schema: DBSchema, functionNameOverride: String) + (implicit dBEngine: E) = { + this(Option(functionNameOverride))(schema, dBEngine) + } + + def this(schema: DBSchema) + (implicit dBEngine: E) = { + this(None)(schema, dBEngine) + } + + def this(dBEngine: E, functionNameOverride: String) + (implicit schema: DBSchema) = { + this(Option(functionNameOverride))(schema, dBEngine) + } + + def this(dBEngine: E) + (implicit schema: DBSchema) = { + this(None)(schema, dBEngine) + } + def apply(values: T): Future[Seq[R]] = execute(values) } @@ -108,8 +155,29 @@ object DBFunction { * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function */ - abstract class DBUniqueFunction[T, R](schema: DBSchema, functionNameOverride: Option[String] = None) - extends DBFunction[T, R](schema, functionNameOverride) { + abstract class DBUniqueFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None)(implicit schema: DBSchema, dBEngine: E) + extends DBFunction[T, R, E](functionNameOverride) { + + def this(schema: DBSchema, functionNameOverride: String) + (implicit dBEngine: E) = { + this(Option(functionNameOverride))(schema, dBEngine) + } + + def this(schema: DBSchema) + (implicit dBEngine: E) = { + this(None)(schema, dBEngine) + } + + def this(dBEngine: E, functionNameOverride: String) + (implicit schema: DBSchema) = { + this(Option(functionNameOverride))(schema, dBEngine) + } + + def this(dBEngine: E) + (implicit schema: DBSchema) = { + this(None)(schema, dBEngine) + } + def apply(values: T): Future[R] = unique(values) } @@ -123,8 +191,29 @@ object DBFunction { * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function */ - abstract class DBOptionFunction[T, R](schema: DBSchema, functionNameOverride: Option[String] = None) - extends DBFunction[T, R](schema, functionNameOverride) { + abstract class DBOptionFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None)(implicit schema: DBSchema, dBEngine: E) + extends DBFunction[T, R, E](functionNameOverride) { + + def this(schema: DBSchema, functionNameOverride: String) + (implicit dBEngine: E) = { + this(Option(functionNameOverride))(schema, dBEngine) + } + + def this(schema: DBSchema) + (implicit dBEngine: E) = { + this(None)(schema, dBEngine) + } + + def this(dBEngine: E, functionNameOverride: String) + (implicit schema: DBSchema) = { + this(Option(functionNameOverride))(schema, dBEngine) + } + + def this(dBEngine: E) + (implicit schema: DBSchema) = { + this(None)(schema, dBEngine) + } + def apply(values: T): Future[Option[R]] = option(values) } } diff --git a/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala b/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala index dffc8185..9ebc1aea 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala @@ -16,8 +16,6 @@ package za.co.absa.fadb -import scala.language.higherKinds - /** * This trait serves the purpose of introducing functions that are common to all DB Function objects and mix-in traits * that offer certain implementations. This trait should help with the inheritance of all of these diff --git a/core/src/main/scala/za/co/absa/fadb/DBSchema.scala b/core/src/main/scala/za/co/absa/fadb/DBSchema.scala index 373d68b7..ae01b05c 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBSchema.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBSchema.scala @@ -28,10 +28,32 @@ import za.co.absa.fadb.naming_conventions.NamingConvention * @param namingConvention - the [[za.co.absa.fadb.naming_conventions.NamingConvention]](NamingConvention) prescribing how to convert a class name into a db object name * @tparam E - the engine of the executor type, e.g. Slick Database */ -abstract class DBSchema(val dBEngine: DBEngine, schemaNameOverride: Option[String] = None) - (implicit val namingConvention: NamingConvention) { +abstract class DBSchema(schemaNameOverride: Option[String] = None) + (implicit dBEngine: DBEngine, implicit val namingConvention: NamingConvention) { + + def this(dBEngine: DBEngine, schemaNameOverride: String) + (implicit namingConvention: NamingConvention) { + this(Option(schemaNameOverride))(dBEngine, namingConvention) + } + + + def this(dBEngine: DBEngine) + (implicit namingConvention: NamingConvention) { + this(None)(dBEngine, namingConvention) + } + + def this(namingConvention: NamingConvention, schemaNameOverride:String) + (implicit dBEngine: DBEngine) { + this(Option(schemaNameOverride))(dBEngine, namingConvention) + } + + def this(namingConvention: NamingConvention) + (implicit dBEngine: DBEngine) { + this(None)(dBEngine, namingConvention) + } //type QueryType[R] = dBEngine.QueryType[R] + protected implicit val schema: DBSchema = this def objectNameFromClassName(c: Class[_]): String = { namingConvention.fromClassNamePerConvention(c) diff --git a/core/src/main/scala/za/co/absa/fadb/Query.scala b/core/src/main/scala/za/co/absa/fadb/Query.scala index 2d7b8d69..39ad9fc0 100644 --- a/core/src/main/scala/za/co/absa/fadb/Query.scala +++ b/core/src/main/scala/za/co/absa/fadb/Query.scala @@ -1,5 +1,5 @@ /* - * Copyright 2023 ABSA Group Limited + * Copyright 2022 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. @@ -16,18 +16,6 @@ package za.co.absa.fadb -trait Query { - type RESULT -// type SQL -// def sql: SQL -} - -object Query { -// type Aux[R, S] = Query { -// type RESULT = R -// type SQL = S -// } - type Aux[R] = Query { - type RESULT = R - } +trait Query[R] { + type RESULT = R } diff --git a/core/src/main/scala/za/co/absa/fadb/package.scala b/core/src/main/scala/za/co/absa/fadb/package.scala deleted file mode 100644 index 14f51ef0..00000000 --- a/core/src/main/scala/za/co/absa/fadb/package.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2022 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 - -import scala.concurrent.Future - -package object fadb { - /** - * Represents a database query call (in the model of Fa-Db a call to a DB stored procedure). When provided a DB - * connection (of type [[DBExecutor]]) it executes the query and transforms it to the desired result type sequence. - * @tparam E - the type of the DB connection to execute on - * @tparam R - the type of result - */ - type QueryFunction[E, R] = E => Future[Seq[R]] -} diff --git a/core/src/main/scala/za/co/absa/fadb/DBExecutor.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/FunctionStatus.scala similarity index 62% rename from core/src/main/scala/za/co/absa/fadb/DBExecutor.scala rename to core/src/main/scala/za/co/absa/fadb/statushandling/FunctionStatus.scala index 24431b22..c0f5baa1 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBExecutor.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/FunctionStatus.scala @@ -14,15 +14,11 @@ * limitations under the License. */ -package za.co.absa.fadb - -import scala.concurrent.Future +package za.co.absa.fadb.statushandling /** - * And abstraction to make it possible to execute queries through regardless of the provided database engine library - * - * @tparam E - the type of the engine, E.g. a Slick Postgres Database + * Class represents the status of calling an fa-db function (if it supports status that is) + * @param status - status code identifying if the function call succeeded or failed and how + * @param statusText - human readable description of the status returned */ -trait DBExecutor[E] { - def run[R](fnc: QueryFunction[E, R]): Future[Seq[R]] -} +case class FunctionStatus(status: Int, statusText: String) diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusException.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusException.scala index aa068500..01647e8f 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusException.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusException.scala @@ -20,22 +20,56 @@ import za.co.absa.fadb.exceptions.DBFailException /** * Exception caused by status signaling a failure in DB function execution - * @param status - the status that caused the error - * @param statusText - the status text explaining the status code + * @param status - represent the status information returned from the function call */ -class StatusException(val status: Int, statusText: String) extends DBFailException(statusText) { - def statusText: String = getMessage +class StatusException(val status:FunctionStatus) extends DBFailException(status.statusText) { + + override def equals(obj: Any): Boolean = { + obj match { + case other: StatusException => (other.status == status) && (getClass == other.getClass) + case _ => false + } + } } object StatusException { - class ServerMisconfigurationException(status: Int, statusText: String) extends StatusException(status, statusText) - class DataConflictException(status: Int, statusText: String) extends StatusException(status, statusText) + def apply(status: FunctionStatus): StatusException = new StatusException(status) + def apply(status: Int, statusText: String): StatusException = new StatusException(FunctionStatus(status, statusText)) + + class ServerMisconfigurationException(status:FunctionStatus) extends StatusException(status) + + class DataConflictException(status:FunctionStatus) extends StatusException(status) + + class DataNotFoundException(status:FunctionStatus) extends StatusException(status) + + class ErrorInDataException(status:FunctionStatus) extends StatusException(status) + + class OtherStatusException(status:FunctionStatus) extends StatusException(status) + + object ServerMisconfigurationException { + def apply(status: FunctionStatus): ServerMisconfigurationException = new ServerMisconfigurationException(status) + def apply(status: Int, statusText: String): ServerMisconfigurationException = new ServerMisconfigurationException(FunctionStatus(status, statusText)) + } + + object DataConflictException { + def apply(status: FunctionStatus): DataConflictException = new DataConflictException(status) + def apply(status: Int, statusText: String): DataConflictException = new DataConflictException(FunctionStatus(status, statusText)) + } - class DataNotFoundException(status: Int, statusText: String) extends StatusException(status, statusText) + object DataNotFoundException { + def apply(status: FunctionStatus): DataNotFoundException = new DataNotFoundException(status) + def apply(status: Int, statusText: String): DataNotFoundException = new DataNotFoundException(FunctionStatus(status, statusText)) + } - class ErrorInDataException(status: Int, statusText: String) extends StatusException(status, statusText) + object ErrorInDataException { + def apply(status: FunctionStatus): ErrorInDataException = new ErrorInDataException(status) + def apply(status: Int, statusText: String): ErrorInDataException = new ErrorInDataException(FunctionStatus(status, statusText)) + } - class OtherStatusException(status: Int, statusText: String) extends StatusException(status, statusText) + object OtherStatusException { + def apply(status: FunctionStatus): OtherStatusException = new OtherStatusException(status) + def apply(status: Int, statusText: String): OtherStatusException = new OtherStatusException(FunctionStatus(status, statusText)) + } } diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala index 5e159622..51a194a2 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala @@ -27,15 +27,31 @@ import scala.util.Try */ trait StatusHandling extends DBFunctionFabric { + /** + * @return - the naming convention to use when convertin the internal status and status text fields to DB fields + */ def namingConvention: NamingConvention - protected def checkStatus(status: Integer, statusText: String): Try[Unit] + /** + * Verifies if the give status means success or failure + * @param status - the staus to check + * @return - Success or failure the status means + */ + protected def checkStatus(status: FunctionStatus): Try[FunctionStatus] + protected def checkStatus(status: Integer, statusText: String): Try[FunctionStatus] = checkStatus((FunctionStatus(status, statusText))) def statusFieldName: String = namingConvention.stringPerConvention(defaultStatusFieldName) def statusTextFieldName: String = namingConvention.stringPerConvention(defaultStatusTextFieldName) + /** + * A mixin to add the status fields into the SELECT statement + * @return a sequence of fields to use in SELECT + */ override protected def fieldsToSelect: Seq[String] = { - Seq(statusFieldName, statusTextFieldName) ++ super.fieldsToSelect + Seq( + namingConvention.stringPerConvention(statusFieldName), + namingConvention.stringPerConvention(statusTextFieldName) + ) ++ super.fieldsToSelect } } diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.scala index 9f22896f..01c2bd6e 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.scala @@ -1,5 +1,5 @@ /* - * Copyright 2023 ABSA Group Limited + * Copyright 2022 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. @@ -18,14 +18,17 @@ package za.co.absa.fadb.statushandling import scala.util.{Failure, Success, Try} +/** + * + */ trait UserDefinedStatusHandling extends StatusHandling { def OKStatuses: Set[Integer] - def checkStatus(status: Integer, statusText: String): Try[Unit] = { - if (OKStatuses.contains(status)) { - Success(Unit) + def checkStatus(status: FunctionStatus): Try[FunctionStatus] = { + if (OKStatuses.contains(status.status)) { + Success(status) } else { - Failure(new StatusException(status, statusText)) + Failure(StatusException(status)) } } } diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala index 147be3fc..848bf622 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala @@ -17,7 +17,7 @@ package za.co.absa.fadb.statushandling.fadbstandard import za.co.absa.fadb.exceptions.DBFailException -import za.co.absa.fadb.statushandling.StatusHandling +import za.co.absa.fadb.statushandling.{FunctionStatus, StatusHandling} import za.co.absa.fadb.statushandling.StatusException._ import scala.util.{Failure, Success, Try} @@ -26,15 +26,15 @@ import scala.util.{Failure, Success, Try} * A mix in trait for [[DBFunction]] for standard handling of `status` and `status_text` fields. */ trait StandardStatusHandling extends StatusHandling { - override protected def checkStatus(status: Integer, statusText: String): Try[Unit] = { - status / 10 match { - case 1 => Success(Unit) - case 2 => Failure(new ServerMisconfigurationException(status, statusText)) - case 3 => Failure(new DataConflictException(status, statusText)) - case 4 => Failure(new DataNotFoundException(status, statusText)) - case 5 | 6 | 7 | 8 => Failure(new ErrorInDataException(status, statusText)) - case 9 => Failure(new OtherStatusException(status, statusText)) - case _ => Failure(DBFailException(s"Status out of range - with status: $status and status text: '$statusText'")) + override protected def checkStatus(status: FunctionStatus): Try[FunctionStatus] = { + status.status / 10 match { + case 1 => Success(status) + case 2 => Failure(ServerMisconfigurationException(status)) + case 3 => Failure(DataConflictException(status)) + case 4 => Failure(DataNotFoundException(status)) + case 5 | 6 | 7 | 8 => Failure(ErrorInDataException(status)) + case 9 => Failure(OtherStatusException(status)) + case _ => Failure(DBFailException(s"Status out of range - with status: $status and status text: '${status.statusText}'")) } } } diff --git a/core/src/test/scala/za/co/absa/fadb/DBFunctionSuite.scala b/core/src/test/scala/za/co/absa/fadb/DBFunctionSuite.scala index ef2cab1a..b55d0bbf 100644 --- a/core/src/test/scala/za/co/absa/fadb/DBFunctionSuite.scala +++ b/core/src/test/scala/za/co/absa/fadb/DBFunctionSuite.scala @@ -21,42 +21,42 @@ import org.scalatest.funsuite.AnyFunSuite import scala.concurrent.Future import za.co.absa.fadb.naming_conventions.SnakeCaseNaming.Implicits.namingConvention -// -//class DBFunctionSuite extends AnyFunSuite { -// private object EngineThrow extends DBEngine { -// override def run[R](query: QueryType[R]): Future[Seq[R]] = { -// throw new Exception("Should never get here") -// } -// } -// -// private object FooNamed extends DBSchema(EngineThrow) -// private object FooNameless extends DBSchema(EngineThrow, Some("")) -// -// test("Function name check"){ -// case class MyFunction(override val schema: DBSchema) extends DBFunction(schema) { -// //override protected def query(values: Nothing): schema.dBEngine.QueryType[Nothing] = throw new Exception("Should never get here") -// -// override protected def query[Q >: schema.dBEngine.QueryType[Nothing]](values: Nothing): Q = ??? //TODO -// } -// -// val fnc1 = MyFunction(FooNamed) -// val fnc2 = MyFunction(FooNameless) -// -// assert(fnc1.functionName == "foo_named.my_function") -// assert(fnc2.functionName == "my_function") -// } -// -// test("Function name override check"){ -// case class MyFunction(override val schema: DBSchema) extends DBFunction(schema, Some("bar")) { -//// override protected def query(values: Nothing): schema.dBEngine.QueryType[Nothing] = throw new Exception("Should never get here") -// override protected def query[Q >: schema.dBEngine.QueryType[Nothing]](values: Nothing): Q = ??? //TODO -// } -// -// val fnc1 = MyFunction(FooNamed) -// val fnc2 = MyFunction(FooNameless) -// -// assert(fnc1.functionName == "foo_named.bar") -// assert(fnc2.functionName == "bar") -// } -// -//} + +class DBFunctionSuite extends AnyFunSuite { + + private def neverHappens: Nothing = { + throw new Exception("Should never get here") + } + + private implicit object EngineThrow extends DBEngine { + override def run[R](query: QueryType[R]): Future[Seq[R]] = neverHappens + } + + private object FooNamed extends DBSchema(EngineThrow) + private object FooNameless extends DBSchema(EngineThrow, "") + + test("Function name check"){ + case class MyFunction(override val schema: DBSchema) extends DBFunction[Unit, Unit, DBEngine](schema) { + override protected def query(values: Unit): dBEngine.QueryType[Unit] = neverHappens + } + + val fnc1 = MyFunction(FooNamed) + val fnc2 = MyFunction(FooNameless) + + assert(fnc1.functionName == "foo_named.my_function") + assert(fnc2.functionName == "my_function") + } + + test("Function name override check"){ + case class MyFunction(override val schema: DBSchema) extends DBFunction[Unit, Unit, DBEngine](schema, "bar") { + override protected def query(values: Unit): dBEngine.QueryType[Unit] = neverHappens + } + + val fnc1 = MyFunction(FooNamed) + val fnc2 = MyFunction(FooNameless) + + assert(fnc1.functionName == "foo_named.bar") + assert(fnc2.functionName == "bar") + } + +} diff --git a/core/src/test/scala/za/co/absa/fadb/statushandling/StatusExceptionSuite.scala b/core/src/test/scala/za/co/absa/fadb/statushandling/StatusExceptionSuite.scala new file mode 100644 index 00000000..ef7e2f89 --- /dev/null +++ b/core/src/test/scala/za/co/absa/fadb/statushandling/StatusExceptionSuite.scala @@ -0,0 +1,45 @@ +/* + * Copyright 2022ABSA 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.fadb.statushandling + +import org.scalatest.funsuite.AnyFunSuite +import za.co.absa.fadb.statushandling.StatusException._ + +class StatusExceptionSuite extends AnyFunSuite { + test("Test equals - when they are the same") { + val statusException = DataConflictException(FunctionStatus(10, "OK")) + val otherStatusException = DataConflictException(10, "OK") + + assert(statusException == otherStatusException) + } + + test("Test equals - when they are different") { + val statusException = DataNotFoundException(10, "OK") + val otherStatusException = DataNotFoundException(10, "Hello") + val anotherStatusException = DataNotFoundException(11, "OK") + + assert(statusException != otherStatusException) + assert(statusException != anotherStatusException) + } + + test("Test equals - when values are same but classes differ") { + val statusException = StatusException(10, "OK") + val otherStatusException = ServerMisconfigurationException(10, "OK") + + assert(statusException != otherStatusException) + } +} diff --git a/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingTest.scala b/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingSuite.scala similarity index 90% rename from core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingTest.scala rename to core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingSuite.scala index 4345e99c..fa70f328 100644 --- a/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingTest.scala +++ b/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingSuite.scala @@ -22,7 +22,7 @@ import za.co.absa.fadb.naming_conventions.{NamingConvention, SnakeCaseNaming} import scala.util.Try -class StatusHandlingTest extends AnyFunSuite { +class StatusHandlingSuite extends AnyFunSuite { test("Fields to select filled with default values") { trait FooDBFunction extends DBFunctionFabric { override def fieldsToSelect: Seq[String] = Seq("alpha", "beta") @@ -32,7 +32,7 @@ class StatusHandlingTest extends AnyFunSuite { override def functionName: String = "Never needed" override def namingConvention: NamingConvention = SnakeCaseNaming.Implicits.namingConvention - override protected def checkStatus(status: Integer, statusText: String): Try[Unit] = ??? + override protected def checkStatus(status: FunctionStatus) = throw new Exception("Should never get here") override def fieldsToSelect: Seq[String] = super.fieldsToSelect } diff --git a/core/src/test/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandlingSuite.scala b/core/src/test/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandlingSuite.scala new file mode 100644 index 00000000..bbe8fb97 --- /dev/null +++ b/core/src/test/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandlingSuite.scala @@ -0,0 +1,41 @@ +/* + * Copyright 2022 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.fadb.statushandling + +import org.scalatest.funsuite.AnyFunSuite +import za.co.absa.fadb.naming_conventions.{NamingConvention, SnakeCaseNaming} + +import scala.util.{Failure, Success, Try} + +class UserDefinedStatusHandlingSuite extends AnyFunSuite { + test("") { + class UserDefinedStatusHandlingForTest(val OKStatuses: Set[Integer]) extends UserDefinedStatusHandling { + override def checkStatus(status: FunctionStatus): Try[FunctionStatus] = super.checkStatus(status) + override def functionName: String = "Never needed" + override def namingConvention: NamingConvention = SnakeCaseNaming.Implicits.namingConvention + } + + val statusHandling = new UserDefinedStatusHandlingForTest(Set(200, 201)) + + val oK = FunctionStatus(200, "OK") + val alsoOK = FunctionStatus(201, "Also OK") + val notOK = FunctionStatus(500, "Not OK") + assert(statusHandling.checkStatus(oK) == Success(oK)) + assert(statusHandling.checkStatus(alsoOK) == Success(alsoOK)) + assert(statusHandling.checkStatus(notOK) == Failure(new StatusException(notOK))) + } +} diff --git a/core/src/test/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandlingTest.scala b/core/src/test/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandlingSuite.scala similarity index 82% rename from core/src/test/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandlingTest.scala rename to core/src/test/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandlingSuite.scala index 7d421362..3f415fbf 100644 --- a/core/src/test/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandlingTest.scala +++ b/core/src/test/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandlingSuite.scala @@ -18,16 +18,17 @@ package za.co.absa.fadb.statushandling.fadbstandard import org.scalatest.funsuite.AnyFunSuite import za.co.absa.fadb.naming_conventions.{NamingConvention, SnakeCaseNaming} -import za.co.absa.fadb.statushandling.StatusException +import za.co.absa.fadb.statushandling.{FunctionStatus, StatusException} import za.co.absa.fadb.statushandling.StatusException._ import scala.reflect.ClassTag -import scala.util.Try +import scala.util.{Success, Try} -class StandardStatusHandlingTest extends AnyFunSuite { +class StandardStatusHandlingSuite extends AnyFunSuite { test("Verify checkStatus error mapping") { class StandardStatusHandlingForTest extends StandardStatusHandling { - override def checkStatus(status: Integer, statusText: String): Try[Unit] = super.checkStatus(status, statusText) + override def checkStatus(status: FunctionStatus): Try[FunctionStatus] = super.checkStatus(status) + override def checkStatus(status: Integer, statusText: String): Try[FunctionStatus] = super.checkStatus(status, statusText) override def functionName: String = "Never needed" override def namingConvention: NamingConvention = SnakeCaseNaming.Implicits.namingConvention } @@ -37,12 +38,11 @@ class StandardStatusHandlingTest extends AnyFunSuite { val failure = intercept[F] { checker.checkStatus(status, statusText).get } - assert(failure.status == status) - assert(failure.statusText == statusText) + assert(failure.status == FunctionStatus(status, statusText)) } implicit val standardStatusHandling: StandardStatusHandlingForTest = new StandardStatusHandlingForTest - assert(standardStatusHandling.checkStatus(10, "OK").isSuccess) + assert(standardStatusHandling.checkStatus(FunctionStatus(10, "OK")) == Success(FunctionStatus(10, "OK"))) assertCheckStatusFailure[ServerMisconfigurationException](21, "Server is wrongly set up") assertCheckStatusFailure[DataConflictException](31, "Referenced data does not allow execution of the request") assertCheckStatusFailure[DataNotFoundException](42, "Detail record not found") diff --git a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala index 9df58976..7be9be0d 100644 --- a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala +++ b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala @@ -17,7 +17,7 @@ package za.co.absa.fadb.examples.enceladus import za.co.absa.fadb.DBSchema -import za.co.absa.fadb.slick.{SlickPgEngine, SlickPgFunction, SlickPgFunctionWithStatusSupport, SlickQuery} +import za.co.absa.fadb.slick.{SlickPgEngine, SlickPgFunction, SlickPgFunctionWithStatusSupport} import za.co.absa.fadb.naming_conventions.SnakeCaseNaming.Implicits.namingConvention import slick.jdbc.{GetResult, SQLActionBuilder} import slick.jdbc.PostgresProfile.api._ @@ -28,14 +28,11 @@ import DatasetSchema._ import za.co.absa.fadb.DBFunction.{DBSeqFunction, DBUniqueFunction} import za.co.absa.fadb.statushandling.{StatusException, UserDefinedStatusHandling} -class DatasetSchema(engine: SlickPgEngine) extends DBSchema(engine) { +class DatasetSchema(implicit engine: SlickPgEngine) extends DBSchema { - - - private implicit val schema: DBSchema = this val addSchema = new AddSchema val getSchema = new GetSchema - val listSchemas = new ListSchemas + val list = new List } @@ -61,28 +58,20 @@ object DatasetSchema { deletedBy: Option[String], deletedWhen: Option[Timestamp]) - case class SchemaHeader(schemaName: String, schemaLatestVersion: Int) - - private implicit val SchemaHeaderImplicit: GetResult[SchemaHeader] = GetResult(r => {SchemaHeader(r.<<, r.<<)}) - private implicit val GetSchemaImplicit: GetResult[Schema] = GetResult(r => { - val status: Int = r.<< - val statusText: String = r.<< - if (status != 200) { - throw new StatusException(status, statusText) - } - Schema(r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<) - }) + case class SchemaHeader(entityName: String, entityLatestVersion: Int) - final class AddSchema(implicit schema: DBSchema) - extends DBUniqueFunction[SchemaInput, Long](schema) + final class AddSchema(implicit override val schema: DBSchema, override val dbEngine: SlickPgEngine) + extends DBUniqueFunction[SchemaInput, Long, SlickPgEngine] with SlickPgFunction[SchemaInput, Long] with UserDefinedStatusHandling { override protected def sql(values: SchemaInput): SQLActionBuilder = { - sql"""SELECT A.status, A.status_text, A.id_schema_version + val r = sql"""SELECT A.status, A.status_text, A.id_schema_version FROM #$functionName(${values.schemaName}, ${values.schemaVersion}, ${values.schemaDescription}, ${values.fields}::JSONB, ${values.userName} ) A;""" + //println(r) + r } override protected def slickConverter: GetResult[Long] = GetResult.GetLong @@ -91,21 +80,19 @@ object DatasetSchema { } - final class GetSchema(implicit override val schema: DBSchema) - extends DBUniqueFunction[(String, Option[Int]), Schema](schema) + final class GetSchema(implicit override val schema: DBSchema, override val dbEngine: SlickPgEngine) + extends DBUniqueFunction[(String, Option[Int]), Schema, SlickPgEngine] with SlickPgFunctionWithStatusSupport[(String, Option[Int]), Schema] with UserDefinedStatusHandling { -// def apply(id: Long): Future[Schema] = { -// val sql = -// sql"""SELECT A.* -// FROM #$functionName($id) A;""" -// -// type X = schema.dBEngine.QueryType[Schema] -// val slickQuery: X = ??? // new SlickQuery(sql, slickConverter) TODO -// val slickQuery2: X = new X() -// schema.dBEngine.unique[Schema](slickQuery) -// } + def apply(id: Long): Future[Schema] = { + val sql = + sql"""SELECT A.* + FROM #$functionName($id) A;""" + + val slickQuery = new dBEngine.QueryType(sql, slickConverter) + dBEngine.unique[Schema](slickQuery) + } override protected def sql(values: (String, Option[Int])): SQLActionBuilder = { sql"""SELECT A.* @@ -119,15 +106,17 @@ object DatasetSchema { override val OKStatuses: Set[Integer] = Set(200) } - final class ListSchemas(implicit schema: DBSchema) - extends DBSeqFunction[Boolean, SchemaHeader](schema) + final class List(implicit override val schema: DBSchema, override val dbEngine: SlickPgEngine) + extends DBSeqFunction[Boolean, SchemaHeader, SlickPgEngine]() with SlickPgFunction[Boolean, SchemaHeader] { override def apply(values: Boolean = false): Future[Seq[SchemaHeader]] = super.apply(values) override protected def sql(values: Boolean): SQLActionBuilder = { - sql"""SELECT A.schema_name, A.schema_latest_version + val r =sql"""SELECT A.entity_name, A.entity_latest_version FROM #$functionName($values) as A;""" + println(r) + r } override protected val slickConverter: GetResult[SchemaHeader] = GetResult(r => {SchemaHeader(r.<<, r.<<)}) diff --git a/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala b/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala index 6de19a1c..057f1715 100644 --- a/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala +++ b/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala @@ -18,112 +18,110 @@ package za.co.absa.fadb.examples.enceladus import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec -import za.co.absa.fadb.slick.SlickPgExecutor import za.co.absa.fadb.examples.enceladus.DatasetSchema._ -import za.co.absa.fadb.exceptions.DBFailException import slick.jdbc.PostgresProfile.api._ +import za.co.absa.fadb.slick.SlickPgEngine import za.co.absa.fadb.statushandling.StatusException import scala.concurrent.Await import scala.concurrent.duration.Duration -// TODO -//class DatasetSchemaSuite extends AnyWordSpec with Matchers { -// private val db = Database.forConfig("menasdb") -// private val executor = new SlickPgExecutor(db) -// private val schemas = new DatasetSchema(executor) -// -// private def checkException(exception: StatusException): Unit = { -// println(s"Requested failed with: ${exception.status} - ${exception.statusText}") -// } -// -// // test cases are set to be ignored now, as they are not idempotent and require other project's (Enceladus) data structures -// -// "listSchemas" should { -// "list the schemas" ignore { -// val ls = schemas.listSchemas() -// val result = Await.result(ls, Duration.Inf) -// result.foreach(println) -// } -// } -// -// "getSchema" should { -// "return the particular schema" when { -// "given name and version" ignore { -// val ls = schemas.getSchema(("aaa", Option(1))) -// val result = Await.result(ls, Duration.Inf) -// println(result) -// } -// "given id" ignore { -// val gs = schemas.getSchema(1000000000000051L) -// val result = Await.result(gs, Duration.Inf) -// println(result) -// } -// } -// "return the latest schema version" when { -// "only the schema name is given" ignore { -// val ls = schemas.getSchema(("aaa", None)) -// val result = Await.result(ls, Duration.Inf) -// println(result) -// } -// } -// "fail" when { -// "schema does not exist" ignore { -// val exception = intercept[StatusException] { -// val gs = schemas.getSchema(("xxx", None)) -// Await.result(gs, Duration.Inf) -// } -// checkException(exception) -// } -// "requested schema version does not exist" ignore { -// val exception = intercept[StatusException] { -// val gs = schemas.getSchema(("aaa", Some(1000))) -// Await.result(gs, Duration.Inf) -// } -// checkException(exception) -// } -// } -// } -// -// "addSchema" should { -// "add a schema" ignore { -// val schemaInput = SchemaInput( -// schemaName = "bbe", -// schemaVersion = 1, -// schemaDescription = Option("Hello World"), -// fields = Option("""{"lorem": "ipsum"}"""), -// userName = "david" -// ) -// val result = Await.result(schemas.addSchema(schemaInput), Duration.Inf) -// println(result) -// } -// "fail" when { -// "Schema already exists" ignore { -// val schemaInput = SchemaInput( -// schemaName = "aaa", -// schemaVersion = 2, -// schemaDescription = Option("Updates"), -// fields = Option("""{"foo": "bar"}"""), -// userName = "david" -// ) -// val exception = intercept[StatusException] { -// Await.result(schemas.addSchema(schemaInput), Duration.Inf) -// } -// checkException(exception) -// } -// "Schema version wrong" ignore { -// val schemaInput = SchemaInput( -// schemaName = "aaa", -// schemaVersion = 1000, -// schemaDescription = Option("Will fail"), -// fields = Option("""{"not_getting_in": "1"}"""), -// userName = "david" -// ) -// val exception = intercept[StatusException] { -// Await.result(schemas.addSchema(schemaInput), Duration.Inf) -// } -// checkException(exception) -// } -// } -// } -//} +class DatasetSchemaSuite extends AnyWordSpec with Matchers { + private val db = Database.forConfig("menasdb") + private implicit val dbEngine: SlickPgEngine = new SlickPgEngine(db) + private val schemas = new DatasetSchema + + private def checkException(exception: StatusException): Unit = { + println(s"Requested failed with: ${exception.status.status} - ${exception.status.statusText}") + } + + // test cases are set to be ignored now, as they are not idempotent and require other project's (Enceladus) data structures + + "listSchemas" should { + "list the schemas" should { + val ls = schemas.list() + val result = Await.result(ls, Duration.Inf) + result.foreach(println) + } + } + + "getSchema" should { + "return the particular schema" when { + "given name and version" ignore { + val ls = schemas.getSchema(("aaa", Option(1))) + val result = Await.result(ls, Duration.Inf) + println(result) + } + "given id" ignore { + val gs = schemas.getSchema(1000000000000051L) + val result = Await.result(gs, Duration.Inf) + println(result) + } + } + "return the latest schema version" when { + "only the schema name is given" ignore { + val ls = schemas.getSchema(("aaa", None)) + val result = Await.result(ls, Duration.Inf) + println(result) + } + } + "fail" when { + "schema does not exist" ignore { + val exception = intercept[StatusException] { + val gs = schemas.getSchema(("xxx", None)) + Await.result(gs, Duration.Inf) + } + checkException(exception) + } + "requested schema version does not exist" ignore { + val exception = intercept[StatusException] { + val gs = schemas.getSchema(("aaa", Some(1000))) + Await.result(gs, Duration.Inf) + } + checkException(exception) + } + } + } + + "addSchema" should { + "add a schema" ignore { + val schemaInput = SchemaInput( + schemaName = "bbe", + schemaVersion = 1, + schemaDescription = Option("Hello World"), + fields = Option("""{"lorem": "ipsum"}"""), + userName = "david" + ) + val result = Await.result(schemas.addSchema(schemaInput), Duration.Inf) + println(result) + } + "fail" when { + "Schema already exists" ignore { + val schemaInput = SchemaInput( + schemaName = "aaa", + schemaVersion = 2, + schemaDescription = Option("Updates"), + fields = Option("""{"foo": "bar"}"""), + userName = "david" + ) + val exception = intercept[StatusException] { + Await.result(schemas.addSchema(schemaInput), Duration.Inf) + } + checkException(exception) + } + "Schema version wrong" ignore { + val schemaInput = SchemaInput( + schemaName = "aaa", + schemaVersion = 1000, + schemaDescription = Option("Will fail"), + fields = Option("""{"not_getting_in": "1"}"""), + userName = "david" + ) + val exception = intercept[StatusException] { + Await.result(schemas.addSchema(schemaInput), Duration.Inf) + } + checkException(exception) + } + } + } +} diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala index d76be5a2..1edbc4c6 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala @@ -17,8 +17,7 @@ package za.co.absa.fadb.slick -import slick.jdbc.{GetResult, PositionedResult} -import za.co.absa.fadb.{DBEngine, Query} +import za.co.absa.fadb.DBEngine import scala.concurrent.Future import slick.jdbc.PostgresProfile.api._ @@ -27,9 +26,9 @@ import scala.language.higherKinds class SlickPgEngine(val db: Database) extends DBEngine { - //override type QueryType[R] = SlickQuery[R] + type QueryType[X] = SlickQuery[X] - override protected def run[R, Q <: SlickQuery[R]](query: Q): Future[Seq[R]] = { + override protected def run[R](query: QueryType[R]): Future[Seq[R]] = { // It can be expected that a GetResult will be passed into the run function as converter. // Unfortunately it has to be recreated to be used by Slick val slickAction = query.sql.as[query.RESULT](query.getResult) diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgExecutor.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgExecutor.scala deleted file mode 100644 index e9d8f1d7..00000000 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgExecutor.scala +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2022 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.fadb.slick - - -import scala.concurrent.Future -import slick.jdbc.PostgresProfile.api._ -import za.co.absa.fadb.{DBExecutor, QueryFunction} - - -class SlickPgExecutor(db: Database) extends DBExecutor[Database] { - override def run[R](fnc: QueryFunction[Database, R]): Future[Seq[R]] = { - fnc(db) - } -} - -object SlickPgExecutor { - def forConfig(dbConfig: String): SlickPgExecutor = { - val db = Database.forConfig(dbConfig) - new SlickPgExecutor(db) - } -} diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala index cdddab83..3a43e713 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala @@ -16,33 +16,18 @@ package za.co.absa.fadb.slick -import slick.jdbc.{GetResult, PositionedResult, SQLActionBuilder} -import za.co.absa.fadb.{DBFunction, DBFunctionFabric, DBSchema, Query} - -import scala.language.higherKinds +import slick.jdbc.{GetResult, SQLActionBuilder} +import za.co.absa.fadb.DBFunctionFabric trait SlickPgFunction[T, R] extends DBFunctionFabric { - type Q = SlickQuery[R] - - val schema: DBSchema - - -// trait Foo[A] {} //Query -// -// type MyFunction[A] = Foo[A] -// -// class MyFunction2[A1] extends MyFunction[A1] //SlickQuery -// -// def bar[Q[String] >: MyFunction[String] ]: Q[String] = { -// new MyFunction2[String] -// } + implicit val dbEngine: SlickPgEngine + protected def sql(values: T): SQLActionBuilder + protected def slickConverter: GetResult[R] protected val alias = "A" - val set: Set[String] = Set("a") - protected def selectEntry: String = { val fieldsSeq = fieldsToSelect if (fieldsSeq.isEmpty) { @@ -57,25 +42,7 @@ trait SlickPgFunction[T, R] extends DBFunctionFabric { } } - // type MyQueryType[A] = Query[A] - // protected def query[Q[String] >: MyQueryType[String]](values: T): Q[String] = { - protected def query(values: T): Q = { - // val q: Q = new SlickQuery(sql(values), slickConverter) - // val q2: Q = ??? - //// q2.foo - //// val q: (Q <: Query[R]) = new Query[R] {} - // new Q{} //TODO + protected def query(values: T): dbEngine.QueryType[R] = { new SlickQuery(sql(values), slickConverter) } - -// protected def query(values: T): schema.dBEngine.QueryType[R] = { -// new SlickQuery(sql(values), slickConverter) -// ??? -// //new Query[R] {} -// } - - - protected def sql(values: T): SQLActionBuilder - protected def slickConverter: GetResult[R] - } diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala index bcf910ad..21926441 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala @@ -1,5 +1,5 @@ /* - * Copyright 2023 ABSA Group Limited + * Copyright 2022 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. @@ -17,21 +17,24 @@ package za.co.absa.fadb.slick import slick.jdbc.{GetResult, PositionedResult} -import za.co.absa.fadb.DBFunction +import za.co.absa.fadb.statushandling.FunctionStatus import scala.util.Try trait SlickPgFunctionWithStatusSupport[T, R] extends SlickPgFunction[T, R] { - protected def checkStatus(status: Integer, statusText: String): Try[Unit] + protected def checkStatus(status: FunctionStatus): Try[FunctionStatus] private def converterWithStatus(queryResult: PositionedResult, actualConverter: GetResult[R]): R = { - val status: Int = queryResult.<< + val status:Int = queryResult.<< val statusText: String = queryResult.<< - checkStatus(status, statusText).get //throw exception if status was off + checkStatus(FunctionStatus(status, statusText)).get //throw exception if status was off actualConverter(queryResult) } - //TODO missing query function + override protected def query(values: T): dbEngine.QueryType[R] = { + val original = super.query(values) + new SlickQuery[R](original.sql, GetResult{converterWithStatus(_, original.getResult)}) + } } diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala index b0f8892e..82fbe576 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala @@ -19,4 +19,4 @@ package za.co.absa.fadb.slick import slick.jdbc.{GetResult, SQLActionBuilder} import za.co.absa.fadb.Query -class SlickQuery[SLICKRESULT](val sql: SQLActionBuilder, val getResult: GetResult[SLICKRESULT]) extends Query {type RESULT = SLICKRESULT} +class SlickQuery[R](val sql: SQLActionBuilder, val getResult: GetResult[R]) extends Query[R] From 54ae61ad31f15f0a712d3d37d19c138e95f018ef Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Sun, 4 Jun 2023 09:48:54 +0200 Subject: [PATCH 06/17] * removed println from code --- .../co/absa/fadb/examples/enceladus/DatasetSchema.scala | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala index 7be9be0d..b4b5174a 100644 --- a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala +++ b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala @@ -66,12 +66,10 @@ object DatasetSchema { with UserDefinedStatusHandling { override protected def sql(values: SchemaInput): SQLActionBuilder = { - val r = sql"""SELECT A.status, A.status_text, A.id_schema_version + sql"""SELECT A.status, A.status_text, A.id_schema_version FROM #$functionName(${values.schemaName}, ${values.schemaVersion}, ${values.schemaDescription}, ${values.fields}::JSONB, ${values.userName} ) A;""" - //println(r) - r } override protected def slickConverter: GetResult[Long] = GetResult.GetLong @@ -113,10 +111,8 @@ object DatasetSchema { override def apply(values: Boolean = false): Future[Seq[SchemaHeader]] = super.apply(values) override protected def sql(values: Boolean): SQLActionBuilder = { - val r =sql"""SELECT A.entity_name, A.entity_latest_version + sql"""SELECT A.entity_name, A.entity_latest_version FROM #$functionName($values) as A;""" - println(r) - r } override protected val slickConverter: GetResult[SchemaHeader] = GetResult(r => {SchemaHeader(r.<<, r.<<)}) From 7e396081f73b23ede7631c69204bd524c007dc50 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Sun, 4 Jun 2023 12:46:34 +0200 Subject: [PATCH 07/17] * example test in `DatasetSchemaSuite` ignored --- .../enceladus/DatasetSchemaSuite.scala | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala b/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala index 057f1715..e481f276 100644 --- a/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala +++ b/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala @@ -37,7 +37,7 @@ class DatasetSchemaSuite extends AnyWordSpec with Matchers { // test cases are set to be ignored now, as they are not idempotent and require other project's (Enceladus) data structures - "listSchemas" should { + "listSchemas" ignore { "list the schemas" should { val ls = schemas.list() val result = Await.result(ls, Duration.Inf) @@ -45,35 +45,35 @@ class DatasetSchemaSuite extends AnyWordSpec with Matchers { } } - "getSchema" should { + "getSchema" ignore { "return the particular schema" when { - "given name and version" ignore { + "given name and version" should { val ls = schemas.getSchema(("aaa", Option(1))) val result = Await.result(ls, Duration.Inf) println(result) } - "given id" ignore { + "given id" should { val gs = schemas.getSchema(1000000000000051L) val result = Await.result(gs, Duration.Inf) println(result) } } "return the latest schema version" when { - "only the schema name is given" ignore { + "only the schema name is given" should { val ls = schemas.getSchema(("aaa", None)) val result = Await.result(ls, Duration.Inf) println(result) } } "fail" when { - "schema does not exist" ignore { + "schema does not exist" should { val exception = intercept[StatusException] { val gs = schemas.getSchema(("xxx", None)) Await.result(gs, Duration.Inf) } checkException(exception) } - "requested schema version does not exist" ignore { + "requested schema version does not exist" should { val exception = intercept[StatusException] { val gs = schemas.getSchema(("aaa", Some(1000))) Await.result(gs, Duration.Inf) @@ -83,8 +83,8 @@ class DatasetSchemaSuite extends AnyWordSpec with Matchers { } } - "addSchema" should { - "add a schema" ignore { + "addSchema" ignore { + "add a schema" should { val schemaInput = SchemaInput( schemaName = "bbe", schemaVersion = 1, @@ -96,7 +96,7 @@ class DatasetSchemaSuite extends AnyWordSpec with Matchers { println(result) } "fail" when { - "Schema already exists" ignore { + "Schema already exists" should { val schemaInput = SchemaInput( schemaName = "aaa", schemaVersion = 2, @@ -109,7 +109,7 @@ class DatasetSchemaSuite extends AnyWordSpec with Matchers { } checkException(exception) } - "Schema version wrong" ignore { + "Schema version wrong" should { val schemaInput = SchemaInput( schemaName = "aaa", schemaVersion = 1000, From 0857701ec9a4b6276883138e1593919d5da8af41 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Mon, 5 Jun 2023 03:07:03 +0200 Subject: [PATCH 08/17] * added documentation --- .../main/scala/za/co/absa/fadb/DBEngine.scala | 2 +- .../scala/za/co/absa/fadb/DBFunction.scala | 102 +++++++++--------- .../za/co/absa/fadb/DBFunctionFabric.scala | 7 ++ .../main/scala/za/co/absa/fadb/DBSchema.scala | 20 +++- .../main/scala/za/co/absa/fadb/Query.scala | 4 + .../fadb/statushandling/StatusHandling.scala | 4 +- .../fadbstandard/StandardStatusHandling.scala | 2 +- .../scala/za/co/absa/fadb/DBSchemaSuite.scala | 46 ++++---- .../statushandling/StatusHandlingSuite.scala | 2 - project/plugins.sbt | 2 + .../za/co/absa/fadb/slick/SlickPgEngine.scala | 14 +++ .../co/absa/fadb/slick/SlickPgFunction.scala | 33 +++++- .../SlickPgFunctionWithStatusSupport.scala | 24 +++++ .../za/co/absa/fadb/slick/SlickQuery.scala | 7 ++ 14 files changed, 184 insertions(+), 85 deletions(-) diff --git a/core/src/main/scala/za/co/absa/fadb/DBEngine.scala b/core/src/main/scala/za/co/absa/fadb/DBEngine.scala index 931bb92e..e39b895b 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBEngine.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBEngine.scala @@ -26,7 +26,7 @@ import scala.language.higherKinds trait DBEngine { /** - * A type repesenting the (SQL) query within the engine + * A type representing the (SQL) query within the engine * @tparam X - the return type of the query */ type QueryType[X] <: Query[X] diff --git a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala index 3d625617..157ff571 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala @@ -20,29 +20,12 @@ import za.co.absa.fadb.naming_conventions.NamingConvention import scala.concurrent.Future -/* - -overriding method query in class DBFunction of type [Q >: CreateQuestion.this.QueryType[Unit]](values: za.co.absa.fadb.examples.aul.Questions.QuestionInput)Q; - method query in trait SlickPgFunctionWithStatusSupport of type [Q >: CreateQuestion.this.QueryType[Unit]](values: za.co.absa.fadb.examples.aul.Questions.QuestionInput)Q has incompatible type; - other members with override errors are: QueryType - case class CreateQuestion(implicit override val schema: DBSchema) - */ - -/** - * The most general abstraction of database function representation - * The database name of the function is derives from the class name based on the provided naming convention (in schema) - * - * @param schema - the schema the function belongs into - * @param functionNameOverride - in case the class name would not match the database function name, this gives the - * possibility of override - */ - /** * * @param functionNameOverride- in case the class name would not match the database function name, this gives the * possibility of override * @param schema - the schema the function belongs into - * @param dBEngine - the database engine that is supposed to execute the function (persumable contains + * @param dBEngine - the database engine that is supposed to execute the function (presumably contains * connection to the database * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function @@ -51,11 +34,13 @@ overriding method query in class DBFunction of type [Q abstract class DBFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None) (implicit val schema: DBSchema, val dBEngine: E) extends DBFunctionFabric { + /* alternative constructors for different availability of input parameters */ def this(schema: DBSchema, functionNameOverride: String) (implicit dBEngine: E) = { this(Option(functionNameOverride))(schema, dBEngine) } + /* only one constructor of a class can have default values for parameters*/ def this(schema: DBSchema) (implicit dBEngine: E) = { this(None)(schema, dBEngine) @@ -71,6 +56,17 @@ abstract class DBFunction[T, R, E <: DBEngine](functionNameOverride: Option[Stri this(None)(schema, dBEngine) } + /** + * Function to create the DB function call specific to the provided [[DBEngine]]. Expected to be implemented by the + * DBEngine specific mix-in. + * @param values - the values to pass over to the database function + * @return - the SQL query in the format specific to the provided [[DBEngine]] + */ + protected def query(values: T): dBEngine.QueryType[R] + + /** + * Name of the function, based on the class name, unless it is overridden in the constructor + */ val functionName: String = { val fn = functionNameOverride.getOrElse(schema.objectNameFromClassName(getClass)) if (schema.schemaName.isEmpty) { @@ -82,42 +78,30 @@ abstract class DBFunction[T, R, E <: DBEngine](functionNameOverride: Option[Stri def namingConvention: NamingConvention = schema.namingConvention - override protected def fieldsToSelect: Seq[String] = super.fieldsToSelect //TODO should get the names from R #6 - - protected def query(values: T): dBEngine.QueryType[R] - /** - * For the given output it returns a function to execute the SQL query and interpret the results. - * Basically it should create a function which contains a query to be executable and executed on on the [[DBExecutor]] - * and transforming the result of that query to result type. - * @param values - the input values of the DB function (stored procedure) - * @return - the query function that when provided an executor will return the result of the DB function call + * List of fields to select from the DB function. Expected to be based on the return type `R` + * @return - list of fields to select */ + override protected def fieldsToSelect: Seq[String] = super.fieldsToSelect //TODO should get the names from R #6 - protected def execute(values: T): Future[Seq[R]] = { - dBEngine.execute[R](query(values)) - } - - protected def unique(values: T): Future[R] = { - dBEngine.unique(query(values)) - } - - protected def option(values: T): Future[Option[R]] = { - dBEngine.option(query(values)) - } + /*these 3 functions has to be defined here and not in the ancestors, as there the query type is not compatible - path-dependent types*/ + protected def execute(values: T): Future[Seq[R]] = dBEngine.execute[R](query(values)) + protected def unique(values: T): Future[R] = dBEngine.unique(query(values)) + protected def option(values: T): Future[Option[R]] = dBEngine.option(query(values)) } object DBFunction { /** * Represents a function returning a set (in DB sense) of rows - * - * @param schema - the schema the function belongs into - * @param functionNameOverride - in case the class name would not match the database function name, this gives the + * @param functionNameOverride- in case the class name would not match the database function name, this gives the * possibility of override - * @tparam E - the type of the [[DBExecutor]] engine + * @param schema - the schema the function belongs into + * @param dBEngine - the database engine that is supposed to execute the function (presumably contains + * connection to the database * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function + * @tparam E - the type of the [[DBEngine]] engine */ abstract class DBSeqFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None)(implicit schema: DBSchema, dBEngine: E) extends DBFunction[T, R, E](functionNameOverride) { @@ -142,18 +126,25 @@ object DBFunction { this(None)(schema, dBEngine) } + /** + * For easy and convenient execution of the DB function call + * @param values - the values to pass over to the database function + * @return - a sequence of values, each coming from a row returned from the DB function transformed to scala + * type `R` + */ def apply(values: T): Future[Seq[R]] = execute(values) } /** * Represents a function returning exactly one record - * - * @param schema - the schema the function belongs into - * @param functionNameOverride - in case the class name would not match the database function name, this gives the + * @param functionNameOverride- in case the class name would not match the database function name, this gives the * possibility of override - * @tparam E - the type of the [[DBExecutor]] engine + * @param schema - the schema the function belongs into + * @param dBEngine - the database engine that is supposed to execute the function (presumably contains + * connection to the database * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function + * @tparam E - the type of the [[DBEngine]] engine */ abstract class DBUniqueFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None)(implicit schema: DBSchema, dBEngine: E) extends DBFunction[T, R, E](functionNameOverride) { @@ -178,18 +169,24 @@ object DBFunction { this(None)(schema, dBEngine) } + /** + * For easy and convenient execution of the DB function call + * @param values - the values to pass over to the database function + * @return - the value returned from the DB function transformed to scala type `R` + */ def apply(values: T): Future[R] = unique(values) } /** * Represents a function returning one optional record - * - * @param schema - the schema the function belongs into - * @param functionNameOverride - in case the class name would not match the database function name, this gives the + * @param functionNameOverride- in case the class name would not match the database function name, this gives the * possibility of override - * @tparam E - the type of the [[DBExecutor]] engine + * @param schema - the schema the function belongs into + * @param dBEngine - the database engine that is supposed to execute the function (presumably contains + * connection to the database * @tparam T - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function + * @tparam E - the type of the [[DBEngine]] engine */ abstract class DBOptionFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None)(implicit schema: DBSchema, dBEngine: E) extends DBFunction[T, R, E](functionNameOverride) { @@ -214,6 +211,11 @@ object DBFunction { this(None)(schema, dBEngine) } + /** + * For easy and convenient execution of the DB function call + * @param values - the values to pass over to the database function + * @return - the value returned from the DB function transformed to scala type `R` if a row is returned, otherwise `None` + */ def apply(values: T): Future[Option[R]] = option(values) } } diff --git a/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala b/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala index 9ebc1aea..5d4fc55e 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBFunctionFabric.scala @@ -22,7 +22,14 @@ package za.co.absa.fadb */ trait DBFunctionFabric { + /** + * Name of the function the class represents + */ def functionName: String + /** + * List of fields to select from the DB function. + * @return - list of fields to select + */ protected def fieldsToSelect: Seq[String] = Seq.empty } diff --git a/core/src/main/scala/za/co/absa/fadb/DBSchema.scala b/core/src/main/scala/za/co/absa/fadb/DBSchema.scala index ae01b05c..5fab700c 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBSchema.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBSchema.scala @@ -20,13 +20,12 @@ import za.co.absa.fadb.naming_conventions.NamingConvention /** * An abstract class, an ancestor to represent a database schema (each database function should be placed in a schema) - * The database name of the schema is derives from the class name based on the provided naming convention - * - * @param executor - executor to execute the queries through + * The database name of the schema is derived from the class name based on the provided naming convention * @param schemaNameOverride - in case the class name would not match the database schema name, this gives the * possibility of override + * @param dBEngine - [[DBEngine]] to execute the functions with. Not directly needed for the DBSchema class, rather + * to be passed on to [[DBFunction]] members of the schema * @param namingConvention - the [[za.co.absa.fadb.naming_conventions.NamingConvention]](NamingConvention) prescribing how to convert a class name into a db object name - * @tparam E - the engine of the executor type, e.g. Slick Database */ abstract class DBSchema(schemaNameOverride: Option[String] = None) (implicit dBEngine: DBEngine, implicit val namingConvention: NamingConvention) { @@ -52,13 +51,24 @@ abstract class DBSchema(schemaNameOverride: Option[String] = None) this(None)(dBEngine, namingConvention) } - //type QueryType[R] = dBEngine.QueryType[R] + /** + * To easy pass over to [[DBFunction]] members of the schema + */ protected implicit val schema: DBSchema = this + /** + * Function to convert a class to the associated DB object name, based on the class' name. For transformation from the + * class name to usual db name the schema's [[za.co.absa.fadb.naming_conventions.NamingConvention NamingConvention]] is used. + * @param c - class which name to use to get the DB object name + * @return - the db object name + */ def objectNameFromClassName(c: Class[_]): String = { namingConvention.fromClassNamePerConvention(c) } + /** + * Name of the schema. Based on the schema's class name or provided override + */ val schemaName: String = schemaNameOverride.getOrElse(objectNameFromClassName(getClass)) } diff --git a/core/src/main/scala/za/co/absa/fadb/Query.scala b/core/src/main/scala/za/co/absa/fadb/Query.scala index 39ad9fc0..60fb8938 100644 --- a/core/src/main/scala/za/co/absa/fadb/Query.scala +++ b/core/src/main/scala/za/co/absa/fadb/Query.scala @@ -16,6 +16,10 @@ package za.co.absa.fadb +/** + * Tha basis for all query types of [[DBEngine]] implementations + * @tparam R - the return type of the query + */ trait Query[R] { type RESULT = R } diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala index 51a194a2..4005fa11 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala @@ -23,7 +23,7 @@ import za.co.absa.fadb.statushandling.StatusHandling.{defaultStatusFieldName, de import scala.util.Try /** - * A basis for mix-in traits for [[DBFunction]] that support `status` and `status text` for easier handling + * A basis for mix-in traits for [[za.co.absa.fadb.DBFunction]] that support `status` and `status text` for easier handling */ trait StatusHandling extends DBFunctionFabric { @@ -34,7 +34,7 @@ trait StatusHandling extends DBFunctionFabric { /** * Verifies if the give status means success or failure - * @param status - the staus to check + * @param status - the status to check * @return - Success or failure the status means */ protected def checkStatus(status: FunctionStatus): Try[FunctionStatus] diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala index 848bf622..40f6ff1d 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala @@ -23,7 +23,7 @@ import za.co.absa.fadb.statushandling.StatusException._ import scala.util.{Failure, Success, Try} /** - * A mix in trait for [[DBFunction]] for standard handling of `status` and `status_text` fields. + * A mix in trait for [[za.co.absa.fadb.DBFunction]] for standard handling of `status` and `status_text` fields. */ trait StandardStatusHandling extends StatusHandling { override protected def checkStatus(status: FunctionStatus): Try[FunctionStatus] = { diff --git a/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala b/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala index 9a7e83ce..15d77a8a 100644 --- a/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala +++ b/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala @@ -20,26 +20,26 @@ import za.co.absa.fadb.naming_conventions.SnakeCaseNaming.Implicits.namingConven import scala.concurrent.{Await, Future} -//class DBSchemaSuite extends AnyFunSuite { -// -// private object EngineThrow extends DBEngine { -// override def run[R](query: QueryType[R]): Future[Seq[R]] = { -// throw new Exception("Should never get here") -// } -// } -// -// test("schema name default") { -// -// class Foo extends DBSchema(EngineThrow) -// val schema = new Foo -// assert(schema.schemaName == "foo") -// } -// -// test("schema name overridden") { -// class Foo extends DBSchema(EngineThrow, Some("bar")) -// -// val schema = new Foo -// assert(schema.schemaName == "bar") -// } -// -//} +class DBSchemaSuite extends AnyFunSuite { + + private object EngineThrow extends DBEngine { + override def run[R](query: QueryType[R]): Future[Seq[R]] = { + throw new Exception("Should never get here") + } + } + + test("schema name default") { + + class Foo extends DBSchema(EngineThrow) + val schema = new Foo + assert(schema.schemaName == "foo") + } + + test("schema name overridden") { + class Foo extends DBSchema(EngineThrow, "bar") + + val schema = new Foo + assert(schema.schemaName == "bar") + } + +} diff --git a/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingSuite.scala b/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingSuite.scala index fa70f328..fb65b759 100644 --- a/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingSuite.scala +++ b/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingSuite.scala @@ -20,8 +20,6 @@ import org.scalatest.funsuite.AnyFunSuite import za.co.absa.fadb.DBFunctionFabric import za.co.absa.fadb.naming_conventions.{NamingConvention, SnakeCaseNaming} -import scala.util.Try - class StatusHandlingSuite extends AnyFunSuite { test("Fields to select filled with default values") { trait FooDBFunction extends DBFunctionFabric { diff --git a/project/plugins.sbt b/project/plugins.sbt index 9e34801a..b93f8279 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -36,3 +36,5 @@ addSbtPlugin("org.ow2.asm" % "asm-commons" % ow2Version from ow2Url("asm-commons addSbtPlugin("org.ow2.asm" % "asm-tree" % ow2Version from ow2Url("asm-tree")) addSbtPlugin("za.co.absa.sbt" % "sbt-jacoco" % "3.4.1-absa.3" from "https://github.com/AbsaOSS/sbt-jacoco/releases/download/3.4.1-absa.3/sbt-jacoco-3.4.1-absa.3.jar") + +addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.2") \ No newline at end of file diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala index 1edbc4c6..e65cc9ec 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala @@ -24,10 +24,24 @@ import slick.jdbc.PostgresProfile.api._ import scala.language.higherKinds +/** + * [[DBEngine]] based on the Slick library in the Postgres flavor + * @param db - the Slick database + */ class SlickPgEngine(val db: Database) extends DBEngine { + /** + * The type of Queries for Slick + * @tparam X - the return type of the query + */ type QueryType[X] = SlickQuery[X] + /** + * Execution using Slick + * @param query - the Slick query to execute + * @tparam R - return the of the query + * @return - sequence of the results of database query + */ override protected def run[R](query: QueryType[R]): Future[Seq[R]] = { // It can be expected that a GetResult will be passed into the run function as converter. // Unfortunately it has to be recreated to be used by Slick diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala index 3a43e713..ddc20afd 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala @@ -19,16 +19,42 @@ package za.co.absa.fadb.slick import slick.jdbc.{GetResult, SQLActionBuilder} import za.co.absa.fadb.DBFunctionFabric +/** + * Mix-in trait to use [[za.co.absa.fadb.DBFunction]] with [[SlickPgEngine]]. Implements the abstract function `query` + * @tparam T - The input type of the function + * @tparam R - The return type of the function + */ trait SlickPgFunction[T, R] extends DBFunctionFabric { + /** + * A reference to the [[SlickPgEngine]] to use the [[za.co.absa.fadb.DBFunction]] with + */ implicit val dbEngine: SlickPgEngine + /** + * This is expected to return SQL part of the [[SlickQuery]] (eventually returned by the `SlickPgFunction.query` function + * @param values - the values to pass over to the database function + * @return - the Slick representation of the SQL + */ protected def sql(values: T): SQLActionBuilder + + /** + * This is expected to return a method to convert the [[slick.jdbc.PositionedResult]], the Slick general SQL result + * format into the `R` type + * @return - the converting function + */ protected def slickConverter: GetResult[R] + /** + * Alias to use within the SQL query + */ protected val alias = "A" - protected def selectEntry: String = { + /** + * Helper function to use in the actual DB function class + * @return the SELECT part of the function call SQL query + */ + protected def selectEntry: String = { // TODO Not suggested to use until #6 will be implemented val fieldsSeq = fieldsToSelect if (fieldsSeq.isEmpty) { "*" @@ -42,6 +68,11 @@ trait SlickPgFunction[T, R] extends DBFunctionFabric { } } + /** + * This mix-in main reason of existence. It implements the `query` function for [[za.co.absa.fadb.DBFunction]] for [[SlickPgEngine]] + * @param values - the values to pass over to the database function + * @return - the SQL query in [[SlickQuery]] form + */ protected def query(values: T): dbEngine.QueryType[R] = { new SlickQuery(sql(values), slickConverter) } diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala index 21926441..7c3dd49f 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala @@ -21,10 +21,29 @@ import za.co.absa.fadb.statushandling.FunctionStatus import scala.util.Try +/** + * An extension of the [[SlickPgFunction]] mix-in trait to add support of status handling + * This trait expects another mix-in of [[za.co.absa.fadb.statushandling.StatusHandling]] (or implementation of `checkStatus` function) + * @tparam T - The input type of the function + * @tparam R - The return type of the function + */ trait SlickPgFunctionWithStatusSupport[T, R] extends SlickPgFunction[T, R] { + /** + * Function which should actually check the status code returned by the DB function. Expected to got implemented by + * [[za.co.absa.fadb.statushandling.StatusHandling]] successor trait. But of course can be implemented directly. + * @param status - the status to check + * @return - Success or failure the status means + */ protected def checkStatus(status: FunctionStatus): Try[FunctionStatus] + /** + * A special extension of the converting function that first picks up status code and status check and checks for their + * meaning. Then the original conversion is executed. + * @param queryResult - the result of the SQL query, the input of the original converting function + * @param actualConverter - the original converting function + * @return - new converting function that also checks for status + */ private def converterWithStatus(queryResult: PositionedResult, actualConverter: GetResult[R]): R = { val status:Int = queryResult.<< val statusText: String = queryResult.<< @@ -32,6 +51,11 @@ trait SlickPgFunctionWithStatusSupport[T, R] extends SlickPgFunction[T, R] { actualConverter(queryResult) } + /** + * Replaces the converter with one that also extracts and checks status code and status text. + * @param values - the values to pass over to the database function + * @return - the SQL query in [[SlickQuery]] form + */ override protected def query(values: T): dbEngine.QueryType[R] = { val original = super.query(values) new SlickQuery[R](original.sql, GetResult{converterWithStatus(_, original.getResult)}) diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala index 82fbe576..2c85b368 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala @@ -19,4 +19,11 @@ package za.co.absa.fadb.slick import slick.jdbc.{GetResult, SQLActionBuilder} import za.co.absa.fadb.Query +/** + * + * @param sql - the SQL query in Slick format + * @param getResult - the converting function, that converts the [[slick.jdbc.PositionedResult]] (the result of Slick + * execution) into the desire `R`` type + * @tparam R - the return type of the query + */ class SlickQuery[R](val sql: SQLActionBuilder, val getResult: GetResult[R]) extends Query[R] From a93d9695b434669d045ed986a434c14fbd3c7c8a Mon Sep 17 00:00:00 2001 From: David Benedeki <14905969+benedeki@users.noreply.github.com> Date: Mon, 5 Jun 2023 15:25:05 +0200 Subject: [PATCH 09/17] Apply suggestions from code review Co-authored-by: miroslavpojer --- core/src/main/scala/za/co/absa/fadb/DBFunction.scala | 8 ++++---- core/src/main/scala/za/co/absa/fadb/Query.scala | 2 +- .../za/co/absa/fadb/statushandling/FunctionStatus.scala | 2 +- .../za/co/absa/fadb/statushandling/StatusHandling.scala | 4 ++-- .../fadbstandard/StandardStatusHandling.scala | 2 +- .../co/absa/fadb/statushandling/StatusHandlingSuite.scala | 2 +- .../statushandling/UserDefinedStatusHandlingSuite.scala | 2 +- .../co/absa/fadb/examples/enceladus/DatasetSchema.scala | 2 +- .../src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala index 157ff571..22d49bcb 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala @@ -22,7 +22,7 @@ import scala.concurrent.Future /** * - * @param functionNameOverride- in case the class name would not match the database function name, this gives the + * @param functionNameOverride - in case the class name would not match the database function name, this gives the * possibility of override * @param schema - the schema the function belongs into * @param dBEngine - the database engine that is supposed to execute the function (presumably contains @@ -94,7 +94,7 @@ abstract class DBFunction[T, R, E <: DBEngine](functionNameOverride: Option[Stri object DBFunction { /** * Represents a function returning a set (in DB sense) of rows - * @param functionNameOverride- in case the class name would not match the database function name, this gives the + * @param functionNameOverride - in case the class name would not match the database function name, this gives the * possibility of override * @param schema - the schema the function belongs into * @param dBEngine - the database engine that is supposed to execute the function (presumably contains @@ -137,7 +137,7 @@ object DBFunction { /** * Represents a function returning exactly one record - * @param functionNameOverride- in case the class name would not match the database function name, this gives the + * @param functionNameOverride - in case the class name would not match the database function name, this gives the * possibility of override * @param schema - the schema the function belongs into * @param dBEngine - the database engine that is supposed to execute the function (presumably contains @@ -179,7 +179,7 @@ object DBFunction { /** * Represents a function returning one optional record - * @param functionNameOverride- in case the class name would not match the database function name, this gives the + * @param functionNameOverride - in case the class name would not match the database function name, this gives the * possibility of override * @param schema - the schema the function belongs into * @param dBEngine - the database engine that is supposed to execute the function (presumably contains diff --git a/core/src/main/scala/za/co/absa/fadb/Query.scala b/core/src/main/scala/za/co/absa/fadb/Query.scala index 60fb8938..070fa3db 100644 --- a/core/src/main/scala/za/co/absa/fadb/Query.scala +++ b/core/src/main/scala/za/co/absa/fadb/Query.scala @@ -17,7 +17,7 @@ package za.co.absa.fadb /** - * Tha basis for all query types of [[DBEngine]] implementations + * The basis for all query types of [[DBEngine]] implementations * @tparam R - the return type of the query */ trait Query[R] { diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/FunctionStatus.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/FunctionStatus.scala index c0f5baa1..fef3ff6b 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/FunctionStatus.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/FunctionStatus.scala @@ -17,7 +17,7 @@ package za.co.absa.fadb.statushandling /** - * Class represents the status of calling an fa-db function (if it supports status that is) + * Class represents the status of calling a fa-db function (if it supports status that is) * @param status - status code identifying if the function call succeeded or failed and how * @param statusText - human readable description of the status returned */ diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala index 4005fa11..ea831c05 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala @@ -23,12 +23,12 @@ import za.co.absa.fadb.statushandling.StatusHandling.{defaultStatusFieldName, de import scala.util.Try /** - * A basis for mix-in traits for [[za.co.absa.fadb.DBFunction]] that support `status` and `status text` for easier handling + * A basis for mix-in traits for [[za.co.absa.fadb.DBFunction DBFunction]] that support `status` and `status text` for easier handling */ trait StatusHandling extends DBFunctionFabric { /** - * @return - the naming convention to use when convertin the internal status and status text fields to DB fields + * @return - the naming convention to use when converting the internal status and status text fields to DB fields */ def namingConvention: NamingConvention diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala index 40f6ff1d..cb4437b0 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala @@ -23,7 +23,7 @@ import za.co.absa.fadb.statushandling.StatusException._ import scala.util.{Failure, Success, Try} /** - * A mix in trait for [[za.co.absa.fadb.DBFunction]] for standard handling of `status` and `status_text` fields. + * A mix in trait for [[za.co.absa.fadb.DBFunction DBFunction]] for standard handling of `status` and `statusText` fields. */ trait StandardStatusHandling extends StatusHandling { override protected def checkStatus(status: FunctionStatus): Try[FunctionStatus] = { diff --git a/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingSuite.scala b/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingSuite.scala index fb65b759..221e6eb1 100644 --- a/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingSuite.scala +++ b/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingSuite.scala @@ -23,7 +23,7 @@ import za.co.absa.fadb.naming_conventions.{NamingConvention, SnakeCaseNaming} class StatusHandlingSuite extends AnyFunSuite { test("Fields to select filled with default values") { trait FooDBFunction extends DBFunctionFabric { - override def fieldsToSelect: Seq[String] = Seq("alpha", "beta") + override def fieldsToSelect: Seq[String] = Seq("Alpha", "beta") } class StatusHandlingForTest extends FooDBFunction with StatusHandling { diff --git a/core/src/test/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandlingSuite.scala b/core/src/test/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandlingSuite.scala index bbe8fb97..bde1ff4a 100644 --- a/core/src/test/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandlingSuite.scala +++ b/core/src/test/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandlingSuite.scala @@ -22,7 +22,7 @@ import za.co.absa.fadb.naming_conventions.{NamingConvention, SnakeCaseNaming} import scala.util.{Failure, Success, Try} class UserDefinedStatusHandlingSuite extends AnyFunSuite { - test("") { + test("Check user defined status") { class UserDefinedStatusHandlingForTest(val OKStatuses: Set[Integer]) extends UserDefinedStatusHandling { override def checkStatus(status: FunctionStatus): Try[FunctionStatus] = super.checkStatus(status) override def functionName: String = "Never needed" diff --git a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala index b4b5174a..df745de7 100644 --- a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala +++ b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala @@ -26,7 +26,7 @@ import java.sql.Timestamp import scala.concurrent.Future import DatasetSchema._ import za.co.absa.fadb.DBFunction.{DBSeqFunction, DBUniqueFunction} -import za.co.absa.fadb.statushandling.{StatusException, UserDefinedStatusHandling} +import za.co.absa.fadb.statushandling.UserDefinedStatusHandling class DatasetSchema(implicit engine: SlickPgEngine) extends DBSchema { diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala index 2c85b368..dbe829ba 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala @@ -23,7 +23,7 @@ import za.co.absa.fadb.Query * * @param sql - the SQL query in Slick format * @param getResult - the converting function, that converts the [[slick.jdbc.PositionedResult]] (the result of Slick - * execution) into the desire `R`` type + * execution) into the desire `R` type * @tparam R - the return type of the query */ class SlickQuery[R](val sql: SQLActionBuilder, val getResult: GetResult[R]) extends Query[R] From b8944116f201d9ac0be5509d81e7c43df848d55d Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Mon, 5 Jun 2023 19:34:41 +0200 Subject: [PATCH 10/17] * further PR comments addressed --- .../main/scala/za/co/absa/fadb/DBSchema.scala | 2 +- core/src/main/scala/za/co/absa/fadb/Query.scala | 4 +--- .../fadb/statushandling/StatusHandling.scala | 16 ++++++++-------- .../fadbstandard/StandardStatusHandling.scala | 2 +- .../statushandling/StatusHandlingSuite.scala | 2 +- .../za/co/absa/fadb/slick/SlickPgEngine.scala | 2 +- .../za/co/absa/fadb/slick/SlickPgFunction.scala | 8 ++++---- .../slick/SlickPgFunctionWithStatusSupport.scala | 4 ++-- .../scala/za/co/absa/fadb/slick/SlickQuery.scala | 4 ++-- 9 files changed, 21 insertions(+), 23 deletions(-) diff --git a/core/src/main/scala/za/co/absa/fadb/DBSchema.scala b/core/src/main/scala/za/co/absa/fadb/DBSchema.scala index 5fab700c..7dfd7116 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBSchema.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBSchema.scala @@ -25,7 +25,7 @@ import za.co.absa.fadb.naming_conventions.NamingConvention * possibility of override * @param dBEngine - [[DBEngine]] to execute the functions with. Not directly needed for the DBSchema class, rather * to be passed on to [[DBFunction]] members of the schema - * @param namingConvention - the [[za.co.absa.fadb.naming_conventions.NamingConvention]](NamingConvention) prescribing how to convert a class name into a db object name + * @param namingConvention - the [[za.co.absa.fadb.naming_conventions.NamingConvention NamingConvention]] prescribing how to convert a class name into a db object name */ abstract class DBSchema(schemaNameOverride: Option[String] = None) (implicit dBEngine: DBEngine, implicit val namingConvention: NamingConvention) { diff --git a/core/src/main/scala/za/co/absa/fadb/Query.scala b/core/src/main/scala/za/co/absa/fadb/Query.scala index 070fa3db..79fdb6a4 100644 --- a/core/src/main/scala/za/co/absa/fadb/Query.scala +++ b/core/src/main/scala/za/co/absa/fadb/Query.scala @@ -20,6 +20,4 @@ package za.co.absa.fadb * The basis for all query types of [[DBEngine]] implementations * @tparam R - the return type of the query */ -trait Query[R] { - type RESULT = R -} +trait Query[R] diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala index ea831c05..a96befec 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala @@ -18,7 +18,7 @@ package za.co.absa.fadb.statushandling import za.co.absa.fadb.DBFunctionFabric import za.co.absa.fadb.naming_conventions.NamingConvention -import za.co.absa.fadb.statushandling.StatusHandling.{defaultStatusFieldName, defaultStatusTextFieldName} +import za.co.absa.fadb.statushandling.StatusHandling.{defaultStatusField, defaultStatusTextField} import scala.util.Try @@ -40,23 +40,23 @@ trait StatusHandling extends DBFunctionFabric { protected def checkStatus(status: FunctionStatus): Try[FunctionStatus] protected def checkStatus(status: Integer, statusText: String): Try[FunctionStatus] = checkStatus((FunctionStatus(status, statusText))) - def statusFieldName: String = namingConvention.stringPerConvention(defaultStatusFieldName) - def statusTextFieldName: String = namingConvention.stringPerConvention(defaultStatusTextFieldName) + def statusField: String = defaultStatusField + def statusTextField: String = defaultStatusTextField /** - * A mixin to add the status fields into the SELECT statement + * A mix-in to add the status fields into the SELECT statement * @return a sequence of fields to use in SELECT */ override protected def fieldsToSelect: Seq[String] = { Seq( - namingConvention.stringPerConvention(statusFieldName), - namingConvention.stringPerConvention(statusTextFieldName) + namingConvention.stringPerConvention(statusField), + namingConvention.stringPerConvention(statusTextField) ) ++ super.fieldsToSelect } } object StatusHandling { - val defaultStatusFieldName = "status" - val defaultStatusTextFieldName = "statusText" + val defaultStatusField = "status" + val defaultStatusTextField = "statusText" } diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala index cb4437b0..41ee7346 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala @@ -23,7 +23,7 @@ import za.co.absa.fadb.statushandling.StatusException._ import scala.util.{Failure, Success, Try} /** - * A mix in trait for [[za.co.absa.fadb.DBFunction DBFunction]] for standard handling of `status` and `statusText` fields. + * A mix-in trait for [[za.co.absa.fadb.DBFunction DBFunction]] for standard handling of `status` and `statusText` fields. */ trait StandardStatusHandling extends StatusHandling { override protected def checkStatus(status: FunctionStatus): Try[FunctionStatus] = { diff --git a/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingSuite.scala b/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingSuite.scala index 221e6eb1..fb65b759 100644 --- a/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingSuite.scala +++ b/core/src/test/scala/za/co/absa/fadb/statushandling/StatusHandlingSuite.scala @@ -23,7 +23,7 @@ import za.co.absa.fadb.naming_conventions.{NamingConvention, SnakeCaseNaming} class StatusHandlingSuite extends AnyFunSuite { test("Fields to select filled with default values") { trait FooDBFunction extends DBFunctionFabric { - override def fieldsToSelect: Seq[String] = Seq("Alpha", "beta") + override def fieldsToSelect: Seq[String] = Seq("alpha", "beta") } class StatusHandlingForTest extends FooDBFunction with StatusHandling { diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala index e65cc9ec..fe9465c4 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala @@ -45,7 +45,7 @@ class SlickPgEngine(val db: Database) extends DBEngine { override protected def run[R](query: QueryType[R]): Future[Seq[R]] = { // It can be expected that a GetResult will be passed into the run function as converter. // Unfortunately it has to be recreated to be used by Slick - val slickAction = query.sql.as[query.RESULT](query.getResult) + val slickAction = query.sql.as[R](query.getResult) db.run(slickAction) } diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala index ddc20afd..02c47805 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala @@ -20,14 +20,14 @@ import slick.jdbc.{GetResult, SQLActionBuilder} import za.co.absa.fadb.DBFunctionFabric /** - * Mix-in trait to use [[za.co.absa.fadb.DBFunction]] with [[SlickPgEngine]]. Implements the abstract function `query` + * Mix-in trait to use [[za.co.absa.fadb.DBFunction DBFunction]] with [[SlickPgEngine]]. Implements the abstract function `query` * @tparam T - The input type of the function * @tparam R - The return type of the function */ trait SlickPgFunction[T, R] extends DBFunctionFabric { /** - * A reference to the [[SlickPgEngine]] to use the [[za.co.absa.fadb.DBFunction]] with + * A reference to the [[SlickPgEngine]] to use the [[za.co.absa.fadb.DBFunction DBFunction]] with */ implicit val dbEngine: SlickPgEngine @@ -39,7 +39,7 @@ trait SlickPgFunction[T, R] extends DBFunctionFabric { protected def sql(values: T): SQLActionBuilder /** - * This is expected to return a method to convert the [[slick.jdbc.PositionedResult]], the Slick general SQL result + * This is expected to return a method to convert the [[slick.jdbc.PositionedResult slick.PositionedResult]], the Slick general SQL result * format into the `R` type * @return - the converting function */ @@ -69,7 +69,7 @@ trait SlickPgFunction[T, R] extends DBFunctionFabric { } /** - * This mix-in main reason of existence. It implements the `query` function for [[za.co.absa.fadb.DBFunction]] for [[SlickPgEngine]] + * This mix-in main reason of existence. It implements the `query` function for [[za.co.absa.fadb.DBFunction DBFunction]] for [[SlickPgEngine]] * @param values - the values to pass over to the database function * @return - the SQL query in [[SlickQuery]] form */ diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala index 7c3dd49f..ff18f0ad 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala @@ -23,7 +23,7 @@ import scala.util.Try /** * An extension of the [[SlickPgFunction]] mix-in trait to add support of status handling - * This trait expects another mix-in of [[za.co.absa.fadb.statushandling.StatusHandling]] (or implementation of `checkStatus` function) + * This trait expects another mix-in of [[za.co.absa.fadb.statushandling.StatusHandling StatusHandling]] (or implementation of `checkStatus` function) * @tparam T - The input type of the function * @tparam R - The return type of the function */ @@ -31,7 +31,7 @@ trait SlickPgFunctionWithStatusSupport[T, R] extends SlickPgFunction[T, R] { /** * Function which should actually check the status code returned by the DB function. Expected to got implemented by - * [[za.co.absa.fadb.statushandling.StatusHandling]] successor trait. But of course can be implemented directly. + * [[za.co.absa.fadb.statushandling.StatusHandling StatusHandling]] successor trait. But of course can be implemented directly. * @param status - the status to check * @return - Success or failure the status means */ diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala index dbe829ba..8cc6335a 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickQuery.scala @@ -20,9 +20,9 @@ import slick.jdbc.{GetResult, SQLActionBuilder} import za.co.absa.fadb.Query /** - * + * SQL query representation for Slick * @param sql - the SQL query in Slick format - * @param getResult - the converting function, that converts the [[slick.jdbc.PositionedResult]] (the result of Slick + * @param getResult - the converting function, that converts the [[slick.jdbc.PositionedResult slick.PositionedResult]] (the result of Slick * execution) into the desire `R` type * @tparam R - the return type of the query */ From 071dce15e2be1ac3963e33e414c222577ca8e435 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Tue, 6 Jun 2023 08:42:27 +0200 Subject: [PATCH 11/17] * New line --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index b93f8279..3578f10c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -37,4 +37,4 @@ addSbtPlugin("org.ow2.asm" % "asm-tree" % ow2Version from ow2Url("asm-tree")) addSbtPlugin("za.co.absa.sbt" % "sbt-jacoco" % "3.4.1-absa.3" from "https://github.com/AbsaOSS/sbt-jacoco/releases/download/3.4.1-absa.3/sbt-jacoco-3.4.1-absa.3.jar") -addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.2") \ No newline at end of file +addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.2") From 4a470997c0679fb43607f51b36b4ff4b75840018 Mon Sep 17 00:00:00 2001 From: David Benedeki <14905969+benedeki@users.noreply.github.com> Date: Tue, 6 Jun 2023 09:27:01 +0200 Subject: [PATCH 12/17] Update core/src/test/scala/za/co/absa/fadb/statushandling/StatusExceptionSuite.scala Co-authored-by: miroslavpojer --- .../co/absa/fadb/statushandling/StatusExceptionSuite.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/test/scala/za/co/absa/fadb/statushandling/StatusExceptionSuite.scala b/core/src/test/scala/za/co/absa/fadb/statushandling/StatusExceptionSuite.scala index ef7e2f89..d6f8450b 100644 --- a/core/src/test/scala/za/co/absa/fadb/statushandling/StatusExceptionSuite.scala +++ b/core/src/test/scala/za/co/absa/fadb/statushandling/StatusExceptionSuite.scala @@ -42,4 +42,11 @@ class StatusExceptionSuite extends AnyFunSuite { assert(statusException != otherStatusException) } + + test("Test equals - when values are same but classes inheritance differ") { + val statusException = StatusException(10, "OK") + val otherException = new ClassNotFoundException() + + assert(statusException != otherException) + } } From 6e32cb3f9a19da59172a1ebe760c6a771ef13cd0 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Thu, 8 Jun 2023 17:05:42 +0200 Subject: [PATCH 13/17] * Little fixes --- .../src/main/scala/za/co/absa/fadb/DBFunction.scala | 13 ++++++++----- .../fadb/examples/enceladus/DatasetSchema.scala | 3 ++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala index 22d49bcb..6bb10310 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala @@ -32,11 +32,11 @@ import scala.concurrent.Future * @tparam E - the type of the [[DBEngine]] engine */ abstract class DBFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None) - (implicit val schema: DBSchema, val dBEngine: E) extends DBFunctionFabric { + (implicit val schema: DBSchema, val dBEngine: E) extends DBFunctionFabric { /* alternative constructors for different availability of input parameters */ def this(schema: DBSchema, functionNameOverride: String) - (implicit dBEngine: E) = { + (implicit dBEngine: E) = { this(Option(functionNameOverride))(schema, dBEngine) } @@ -103,7 +103,8 @@ object DBFunction { * @tparam R - the type covering the returned fields from the database function * @tparam E - the type of the [[DBEngine]] engine */ - abstract class DBSeqFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None)(implicit schema: DBSchema, dBEngine: E) + abstract class DBSeqFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None) + (implicit schema: DBSchema, dBEngine: E) extends DBFunction[T, R, E](functionNameOverride) { def this(schema: DBSchema, functionNameOverride: String) @@ -146,7 +147,8 @@ object DBFunction { * @tparam R - the type covering the returned fields from the database function * @tparam E - the type of the [[DBEngine]] engine */ - abstract class DBUniqueFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None)(implicit schema: DBSchema, dBEngine: E) + abstract class DBUniqueFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None) + (implicit schema: DBSchema, dBEngine: E) extends DBFunction[T, R, E](functionNameOverride) { def this(schema: DBSchema, functionNameOverride: String) @@ -188,7 +190,8 @@ object DBFunction { * @tparam R - the type covering the returned fields from the database function * @tparam E - the type of the [[DBEngine]] engine */ - abstract class DBOptionFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None)(implicit schema: DBSchema, dBEngine: E) + abstract class DBOptionFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None) + (implicit schema: DBSchema, dBEngine: E) extends DBFunction[T, R, E](functionNameOverride) { def this(schema: DBSchema, functionNameOverride: String) diff --git a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala index df745de7..7d471383 100644 --- a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala +++ b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala @@ -28,6 +28,7 @@ import DatasetSchema._ import za.co.absa.fadb.DBFunction.{DBSeqFunction, DBUniqueFunction} import za.co.absa.fadb.statushandling.UserDefinedStatusHandling +/* The Schema doesn't need the dBEngine directly, but it seems cleaner this way to hand it over to schema's functions */ class DatasetSchema(implicit engine: SlickPgEngine) extends DBSchema { val addSchema = new AddSchema @@ -62,7 +63,7 @@ object DatasetSchema { final class AddSchema(implicit override val schema: DBSchema, override val dbEngine: SlickPgEngine) extends DBUniqueFunction[SchemaInput, Long, SlickPgEngine] - with SlickPgFunction[SchemaInput, Long] + with SlickPgFunctionWithStatusSupport[SchemaInput, Long] with UserDefinedStatusHandling { override protected def sql(values: SchemaInput): SQLActionBuilder = { From e72e9e9eb089f5d45147a0822ceaa2c94e08cfbd Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Fri, 9 Jun 2023 17:07:55 +0200 Subject: [PATCH 14/17] * replaced T type for I * changed alias from "A" to "FNC" --- .../main/scala/za/co/absa/fadb/DBEngine.scala | 4 +-- .../scala/za/co/absa/fadb/DBFunction.scala | 36 +++++++++---------- .../za/co/absa/fadb/slick/SlickPgEngine.scala | 4 +-- .../co/absa/fadb/slick/SlickPgFunction.scala | 10 +++--- .../SlickPgFunctionWithStatusSupport.scala | 6 ++-- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/core/src/main/scala/za/co/absa/fadb/DBEngine.scala b/core/src/main/scala/za/co/absa/fadb/DBEngine.scala index e39b895b..18fa47e2 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBEngine.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBEngine.scala @@ -27,9 +27,9 @@ trait DBEngine { /** * A type representing the (SQL) query within the engine - * @tparam X - the return type of the query + * @tparam T - the return type of the query */ - type QueryType[X] <: Query[X] + type QueryType[T] <: Query[T] /** * The actual query executioner of the queries of the engine diff --git a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala index 6bb10310..7b842fec 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBFunction.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBFunction.scala @@ -27,11 +27,11 @@ import scala.concurrent.Future * @param schema - the schema the function belongs into * @param dBEngine - the database engine that is supposed to execute the function (presumably contains * connection to the database - * @tparam T - the type covering the input fields of the database function + * @tparam I - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function * @tparam E - the type of the [[DBEngine]] engine */ -abstract class DBFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None) +abstract class DBFunction[I, R, E <: DBEngine](functionNameOverride: Option[String] = None) (implicit val schema: DBSchema, val dBEngine: E) extends DBFunctionFabric { /* alternative constructors for different availability of input parameters */ @@ -62,7 +62,7 @@ abstract class DBFunction[T, R, E <: DBEngine](functionNameOverride: Option[Stri * @param values - the values to pass over to the database function * @return - the SQL query in the format specific to the provided [[DBEngine]] */ - protected def query(values: T): dBEngine.QueryType[R] + protected def query(values: I): dBEngine.QueryType[R] /** * Name of the function, based on the class name, unless it is overridden in the constructor @@ -85,9 +85,9 @@ abstract class DBFunction[T, R, E <: DBEngine](functionNameOverride: Option[Stri override protected def fieldsToSelect: Seq[String] = super.fieldsToSelect //TODO should get the names from R #6 /*these 3 functions has to be defined here and not in the ancestors, as there the query type is not compatible - path-dependent types*/ - protected def execute(values: T): Future[Seq[R]] = dBEngine.execute[R](query(values)) - protected def unique(values: T): Future[R] = dBEngine.unique(query(values)) - protected def option(values: T): Future[Option[R]] = dBEngine.option(query(values)) + protected def execute(values: I): Future[Seq[R]] = dBEngine.execute[R](query(values)) + protected def unique(values: I): Future[R] = dBEngine.unique(query(values)) + protected def option(values: I): Future[Option[R]] = dBEngine.option(query(values)) } @@ -99,13 +99,13 @@ object DBFunction { * @param schema - the schema the function belongs into * @param dBEngine - the database engine that is supposed to execute the function (presumably contains * connection to the database - * @tparam T - the type covering the input fields of the database function + * @tparam I - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function * @tparam E - the type of the [[DBEngine]] engine */ - abstract class DBSeqFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None) + abstract class DBSeqFunction[I, R, E <: DBEngine](functionNameOverride: Option[String] = None) (implicit schema: DBSchema, dBEngine: E) - extends DBFunction[T, R, E](functionNameOverride) { + extends DBFunction[I, R, E](functionNameOverride) { def this(schema: DBSchema, functionNameOverride: String) (implicit dBEngine: E) = { @@ -133,7 +133,7 @@ object DBFunction { * @return - a sequence of values, each coming from a row returned from the DB function transformed to scala * type `R` */ - def apply(values: T): Future[Seq[R]] = execute(values) + def apply(values: I): Future[Seq[R]] = execute(values) } /** @@ -143,13 +143,13 @@ object DBFunction { * @param schema - the schema the function belongs into * @param dBEngine - the database engine that is supposed to execute the function (presumably contains * connection to the database - * @tparam T - the type covering the input fields of the database function + * @tparam I - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function * @tparam E - the type of the [[DBEngine]] engine */ - abstract class DBUniqueFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None) + abstract class DBUniqueFunction[I, R, E <: DBEngine](functionNameOverride: Option[String] = None) (implicit schema: DBSchema, dBEngine: E) - extends DBFunction[T, R, E](functionNameOverride) { + extends DBFunction[I, R, E](functionNameOverride) { def this(schema: DBSchema, functionNameOverride: String) (implicit dBEngine: E) = { @@ -176,7 +176,7 @@ object DBFunction { * @param values - the values to pass over to the database function * @return - the value returned from the DB function transformed to scala type `R` */ - def apply(values: T): Future[R] = unique(values) + def apply(values: I): Future[R] = unique(values) } /** @@ -186,13 +186,13 @@ object DBFunction { * @param schema - the schema the function belongs into * @param dBEngine - the database engine that is supposed to execute the function (presumably contains * connection to the database - * @tparam T - the type covering the input fields of the database function + * @tparam I - the type covering the input fields of the database function * @tparam R - the type covering the returned fields from the database function * @tparam E - the type of the [[DBEngine]] engine */ - abstract class DBOptionFunction[T, R, E <: DBEngine](functionNameOverride: Option[String] = None) + abstract class DBOptionFunction[I, R, E <: DBEngine](functionNameOverride: Option[String] = None) (implicit schema: DBSchema, dBEngine: E) - extends DBFunction[T, R, E](functionNameOverride) { + extends DBFunction[I, R, E](functionNameOverride) { def this(schema: DBSchema, functionNameOverride: String) (implicit dBEngine: E) = { @@ -219,6 +219,6 @@ object DBFunction { * @param values - the values to pass over to the database function * @return - the value returned from the DB function transformed to scala type `R` if a row is returned, otherwise `None` */ - def apply(values: T): Future[Option[R]] = option(values) + def apply(values: I): Future[Option[R]] = option(values) } } diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala index fe9465c4..effa4f24 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgEngine.scala @@ -32,9 +32,9 @@ class SlickPgEngine(val db: Database) extends DBEngine { /** * The type of Queries for Slick - * @tparam X - the return type of the query + * @tparam T - the return type of the query */ - type QueryType[X] = SlickQuery[X] + type QueryType[T] = SlickQuery[T] /** * Execution using Slick diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala index 02c47805..401a4da7 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunction.scala @@ -21,10 +21,10 @@ import za.co.absa.fadb.DBFunctionFabric /** * Mix-in trait to use [[za.co.absa.fadb.DBFunction DBFunction]] with [[SlickPgEngine]]. Implements the abstract function `query` - * @tparam T - The input type of the function + * @tparam I - The input type of the function * @tparam R - The return type of the function */ -trait SlickPgFunction[T, R] extends DBFunctionFabric { +trait SlickPgFunction[I, R] extends DBFunctionFabric { /** * A reference to the [[SlickPgEngine]] to use the [[za.co.absa.fadb.DBFunction DBFunction]] with @@ -36,7 +36,7 @@ trait SlickPgFunction[T, R] extends DBFunctionFabric { * @param values - the values to pass over to the database function * @return - the Slick representation of the SQL */ - protected def sql(values: T): SQLActionBuilder + protected def sql(values: I): SQLActionBuilder /** * This is expected to return a method to convert the [[slick.jdbc.PositionedResult slick.PositionedResult]], the Slick general SQL result @@ -48,7 +48,7 @@ trait SlickPgFunction[T, R] extends DBFunctionFabric { /** * Alias to use within the SQL query */ - protected val alias = "A" + protected val alias = "FNC" /** * Helper function to use in the actual DB function class @@ -73,7 +73,7 @@ trait SlickPgFunction[T, R] extends DBFunctionFabric { * @param values - the values to pass over to the database function * @return - the SQL query in [[SlickQuery]] form */ - protected def query(values: T): dbEngine.QueryType[R] = { + protected def query(values: I): dbEngine.QueryType[R] = { new SlickQuery(sql(values), slickConverter) } } diff --git a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala index ff18f0ad..e1588372 100644 --- a/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala +++ b/slick/src/main/scala/za/co/absa/fadb/slick/SlickPgFunctionWithStatusSupport.scala @@ -24,10 +24,10 @@ import scala.util.Try /** * An extension of the [[SlickPgFunction]] mix-in trait to add support of status handling * This trait expects another mix-in of [[za.co.absa.fadb.statushandling.StatusHandling StatusHandling]] (or implementation of `checkStatus` function) - * @tparam T - The input type of the function + * @tparam I - The input type of the function * @tparam R - The return type of the function */ -trait SlickPgFunctionWithStatusSupport[T, R] extends SlickPgFunction[T, R] { +trait SlickPgFunctionWithStatusSupport[I, R] extends SlickPgFunction[I, R] { /** * Function which should actually check the status code returned by the DB function. Expected to got implemented by @@ -56,7 +56,7 @@ trait SlickPgFunctionWithStatusSupport[T, R] extends SlickPgFunction[T, R] { * @param values - the values to pass over to the database function * @return - the SQL query in [[SlickQuery]] form */ - override protected def query(values: T): dbEngine.QueryType[R] = { + override protected def query(values: I): dbEngine.QueryType[R] = { val original = super.query(values) new SlickQuery[R](original.sql, GetResult{converterWithStatus(_, original.getResult)}) } From 795fea3260d9988ba74bd86044c18ffa378f4c2f Mon Sep 17 00:00:00 2001 From: David Benedeki <14905969+benedeki@users.noreply.github.com> Date: Fri, 9 Jun 2023 17:47:54 +0200 Subject: [PATCH 15/17] Apply suggestions from code review Co-authored-by: Ladislav Sulak --- .../scala/za/co/absa/fadb/statushandling/StatusHandling.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala index a96befec..453a946b 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/StatusHandling.scala @@ -33,12 +33,12 @@ trait StatusHandling extends DBFunctionFabric { def namingConvention: NamingConvention /** - * Verifies if the give status means success or failure + * Verifies if the given status means success or failure * @param status - the status to check * @return - Success or failure the status means */ protected def checkStatus(status: FunctionStatus): Try[FunctionStatus] - protected def checkStatus(status: Integer, statusText: String): Try[FunctionStatus] = checkStatus((FunctionStatus(status, statusText))) + protected def checkStatus(status: Integer, statusText: String): Try[FunctionStatus] = checkStatus(FunctionStatus(status, statusText)) def statusField: String = defaultStatusField def statusTextField: String = defaultStatusTextField From a3380c60508b848abf45ef6ec787cd3d46755648 Mon Sep 17 00:00:00 2001 From: David Benedeki Date: Mon, 12 Jun 2023 09:11:25 +0200 Subject: [PATCH 16/17] * addressed PR comments --- core/src/main/scala/za/co/absa/fadb/DBSchema.scala | 1 - .../za/co/absa/fadb/exceptions/DBFailException.scala | 9 ++++++++- .../co/absa/fadb/statushandling/FunctionStatus.scala | 4 ++-- .../statushandling/UserDefinedStatusHandling.scala | 2 +- .../fadbstandard/StandardStatusHandling.scala | 4 ++-- .../src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala | 2 +- .../fadbstandard/StandardStatusHandlingSuite.scala | 10 ++++++++-- .../fadb/examples/enceladus/DatasetSchemaSuite.scala | 2 +- 8 files changed, 23 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/za/co/absa/fadb/DBSchema.scala b/core/src/main/scala/za/co/absa/fadb/DBSchema.scala index 7dfd7116..6681a388 100644 --- a/core/src/main/scala/za/co/absa/fadb/DBSchema.scala +++ b/core/src/main/scala/za/co/absa/fadb/DBSchema.scala @@ -35,7 +35,6 @@ abstract class DBSchema(schemaNameOverride: Option[String] = None) this(Option(schemaNameOverride))(dBEngine, namingConvention) } - def this(dBEngine: DBEngine) (implicit namingConvention: NamingConvention) { this(None)(dBEngine, namingConvention) diff --git a/core/src/main/scala/za/co/absa/fadb/exceptions/DBFailException.scala b/core/src/main/scala/za/co/absa/fadb/exceptions/DBFailException.scala index c390cf0d..6f693143 100644 --- a/core/src/main/scala/za/co/absa/fadb/exceptions/DBFailException.scala +++ b/core/src/main/scala/za/co/absa/fadb/exceptions/DBFailException.scala @@ -20,7 +20,14 @@ package za.co.absa.fadb.exceptions * General Fa-DB exception class * @param message - the message describing the reason of exception */ -class DBFailException(message: String) extends Exception(message) +class DBFailException(message: String) extends Exception(message) { + override def equals(obj: Any): Boolean = { + obj match { + case other: DBFailException => (other.getMessage == message) && (getClass == other.getClass) + case _ => false + } + } +} object DBFailException { def apply(message: String): DBFailException = new DBFailException(message) diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/FunctionStatus.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/FunctionStatus.scala index fef3ff6b..03eee520 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/FunctionStatus.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/FunctionStatus.scala @@ -18,7 +18,7 @@ package za.co.absa.fadb.statushandling /** * Class represents the status of calling a fa-db function (if it supports status that is) - * @param status - status code identifying if the function call succeeded or failed and how + * @param statusCode - status code identifying if the function call succeeded or failed and how * @param statusText - human readable description of the status returned */ -case class FunctionStatus(status: Int, statusText: String) +case class FunctionStatus(statusCode: Int, statusText: String) diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.scala index 01c2bd6e..c49f64ce 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/UserDefinedStatusHandling.scala @@ -25,7 +25,7 @@ trait UserDefinedStatusHandling extends StatusHandling { def OKStatuses: Set[Integer] def checkStatus(status: FunctionStatus): Try[FunctionStatus] = { - if (OKStatuses.contains(status.status)) { + if (OKStatuses.contains(status.statusCode)) { Success(status) } else { Failure(StatusException(status)) diff --git a/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala b/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala index 41ee7346..23957508 100644 --- a/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala +++ b/core/src/main/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandling.scala @@ -27,14 +27,14 @@ import scala.util.{Failure, Success, Try} */ trait StandardStatusHandling extends StatusHandling { override protected def checkStatus(status: FunctionStatus): Try[FunctionStatus] = { - status.status / 10 match { + status.statusCode / 10 match { case 1 => Success(status) case 2 => Failure(ServerMisconfigurationException(status)) case 3 => Failure(DataConflictException(status)) case 4 => Failure(DataNotFoundException(status)) case 5 | 6 | 7 | 8 => Failure(ErrorInDataException(status)) case 9 => Failure(OtherStatusException(status)) - case _ => Failure(DBFailException(s"Status out of range - with status: $status and status text: '${status.statusText}'")) + case _ => Failure(DBFailException(s"Status out of range - with status: ${status.statusCode} and status text: '${status.statusText}'")) } } } diff --git a/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala b/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala index 15d77a8a..85f7fc83 100644 --- a/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala +++ b/core/src/test/scala/za/co/absa/fadb/DBSchemaSuite.scala @@ -29,8 +29,8 @@ class DBSchemaSuite extends AnyFunSuite { } test("schema name default") { - class Foo extends DBSchema(EngineThrow) + val schema = new Foo assert(schema.schemaName == "foo") } diff --git a/core/src/test/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandlingSuite.scala b/core/src/test/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandlingSuite.scala index 3f415fbf..004f7ed8 100644 --- a/core/src/test/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandlingSuite.scala +++ b/core/src/test/scala/za/co/absa/fadb/statushandling/fadbstandard/StandardStatusHandlingSuite.scala @@ -17,12 +17,13 @@ package za.co.absa.fadb.statushandling.fadbstandard import org.scalatest.funsuite.AnyFunSuite +import za.co.absa.fadb.exceptions.DBFailException import za.co.absa.fadb.naming_conventions.{NamingConvention, SnakeCaseNaming} import za.co.absa.fadb.statushandling.{FunctionStatus, StatusException} import za.co.absa.fadb.statushandling.StatusException._ import scala.reflect.ClassTag -import scala.util.{Success, Try} +import scala.util.{Failure, Success, Try} class StandardStatusHandlingSuite extends AnyFunSuite { test("Verify checkStatus error mapping") { @@ -35,6 +36,7 @@ class StandardStatusHandlingSuite extends AnyFunSuite { def assertCheckStatusFailure[F <: StatusException](status: Int, statusText: String) (implicit classTag: ClassTag[F], checker: StandardStatusHandlingForTest): Unit = { + val failure = intercept[F] { checker.checkStatus(status, statusText).get } @@ -51,6 +53,10 @@ class StandardStatusHandlingSuite extends AnyFunSuite { assertCheckStatusFailure[ErrorInDataException](73, "Value ABC is out of range") assertCheckStatusFailure[ErrorInDataException](84, "Json value of field FF is missing property PPP") assertCheckStatusFailure[OtherStatusException](95, "This is a special error") - assert(standardStatusHandling.checkStatus(101, "Server is wrongly set up").isFailure) + + val status = 101 + val statusText = "Server is wrongly set up" + val expectedFailure = Failure(DBFailException(s"Status out of range - with status: $status and status text: '${statusText}'")) + assert(standardStatusHandling.checkStatus(101, "Server is wrongly set up") == expectedFailure) } } diff --git a/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala b/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala index e481f276..07f9cb76 100644 --- a/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala +++ b/examples/src/test/scala/za/co/absa/fadb/examples/enceladus/DatasetSchemaSuite.scala @@ -32,7 +32,7 @@ class DatasetSchemaSuite extends AnyWordSpec with Matchers { private val schemas = new DatasetSchema private def checkException(exception: StatusException): Unit = { - println(s"Requested failed with: ${exception.status.status} - ${exception.status.statusText}") + println(s"Requested failed with: ${exception.status.statusCode} - ${exception.status.statusText}") } // test cases are set to be ignored now, as they are not idempotent and require other project's (Enceladus) data structures From 8b97a75fce9b6f67dda74d180c0dc2cd97b663b0 Mon Sep 17 00:00:00 2001 From: David Benedeki <14905969+benedeki@users.noreply.github.com> Date: Tue, 13 Jun 2023 04:54:40 +0200 Subject: [PATCH 17/17] Update examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala Co-authored-by: Ladislav Sulak --- .../scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala index 7d471383..266c177b 100644 --- a/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala +++ b/examples/src/main/scala/za/co/absa/fadb/examples/enceladus/DatasetSchema.scala @@ -84,6 +84,7 @@ object DatasetSchema { with SlickPgFunctionWithStatusSupport[(String, Option[Int]), Schema] with UserDefinedStatusHandling { + /* This is an example of how to deal with overloaded DB functions - see different input type: Long vs what's in the class type: (String, Option[Int]) */ def apply(id: Long): Future[Schema] = { val sql = sql"""SELECT A.*