Skip to content

Commit 5c6f641

Browse files
authored
Separating db engine and core (#3)
Separating core and db egine * Core, Slick and example classes seperated * Removed some unnecessary classes * Clearer placement for NamingConventions implicits * Documentation on core classes
1 parent 765b4b0 commit 5c6f641

File tree

17 files changed

+825
-0
lines changed

17 files changed

+825
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2022 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package za.co.absa.fadb
17+
18+
import za.co.absa.fadb.DBFunction.QueryFunction
19+
20+
import scala.concurrent.Future
21+
22+
/**
23+
* And abstraction to make it possible to execute queries through regardless of the provided database engine library
24+
*
25+
* @tparam E - the type of the engine, E.g. a Slick Postgres Database
26+
*/
27+
trait DBExecutor[+E] {
28+
def run[R](fnc: QueryFunction[E, R]): Future[Seq[R]]
29+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2021 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package za.co.absa.fadb
17+
18+
import za.co.absa.fadb.DBFunction.QueryFunction
19+
20+
import scala.concurrent.Future
21+
22+
/**
23+
* The most general abstraction of database function representation
24+
* The database name of the function is derives from the class name based on the provided naming convention (in schema)
25+
*
26+
* @param schema - the schema the function belongs into
27+
* @param functionNameOverride - in case the class name would not match the database function name, this gives the
28+
* possibility of override
29+
* @tparam E - the type of the [[DBExecutor]] engine
30+
* @tparam T - the type covering the input fields of the database function
31+
* @tparam R - the type covering the returned fields from the database function
32+
*/
33+
abstract class DBFunction[E, T, R](schema: DBSchema[E], functionNameOverride: Option[String] = Some("a")) {
34+
val functionName: String = {
35+
val fn = functionNameOverride.getOrElse(schema.objectNameFromClassName(getClass))
36+
if (schema.schemaName.isEmpty) {
37+
fn
38+
} else {
39+
s"${schema.schemaName}.$fn"
40+
}
41+
}
42+
43+
protected def queryFunction(values: T): QueryFunction[E, R]
44+
}
45+
46+
object DBFunction {
47+
type QueryFunction[E, R] = E => Future[Seq[R]]
48+
49+
/**
50+
* Represents a function returning a set (in DB sense) of rows
51+
*
52+
* @param schema - the schema the function belongs into
53+
* @param functionNameOverride - in case the class name would not match the database function name, this gives the
54+
* possibility of override
55+
* @tparam E - the type of the [[DBExecutor]] engine
56+
* @tparam T - the type covering the input fields of the database function
57+
* @tparam R - the type covering the returned fields from the database function
58+
*/
59+
abstract class DBSeqFunction[E, T, R](schema: DBSchema[E], functionNameOverride: Option[String] = None)
60+
extends DBFunction[E, T, R](schema, functionNameOverride) {
61+
def apply(values: T): Future[Seq[R]] = {
62+
schema.execute(queryFunction(values))
63+
}
64+
}
65+
66+
/**
67+
* Represents a function returning exactly one record
68+
*
69+
* @param schema - the schema the function belongs into
70+
* @param functionNameOverride - in case the class name would not match the database function name, this gives the
71+
* possibility of override
72+
* @tparam E - the type of the [[DBExecutor]] engine
73+
* @tparam T - the type covering the input fields of the database function
74+
* @tparam R - the type covering the returned fields from the database function
75+
*/
76+
abstract class DBUniqueFunction[E, T, R](schema: DBSchema[E], functionNameOverride: Option[String] = None)
77+
extends DBFunction[E, T, R](schema, functionNameOverride) {
78+
def apply(values: T): Future[R] = {
79+
schema.unique(queryFunction(values))
80+
}
81+
}
82+
83+
/**
84+
* Represents a function returning one optional record
85+
*
86+
* @param schema - the schema the function belongs into
87+
* @param functionNameOverride - in case the class name would not match the database function name, this gives the
88+
* possibility of override
89+
* @tparam E - the type of the [[DBExecutor]] engine
90+
* @tparam T - the type covering the input fields of the database function
91+
* @tparam R - the type covering the returned fields from the database function
92+
*/
93+
abstract class DBOptionFunction[E, T, R](schema: DBSchema[E], functionNameOverride: Option[String] = None)
94+
extends DBFunction[E, T, R](schema, functionNameOverride) {
95+
def apply(values: T): Future[Option[R]] = {
96+
schema.option(queryFunction(values))
97+
}
98+
}
99+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2022 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package za.co.absa.fadb
17+
18+
import za.co.absa.fadb.naming_conventions.NamingConvention
19+
20+
import scala.concurrent.Future
21+
import scala.concurrent.ExecutionContext.Implicits.global
22+
23+
/**
24+
* An abstract class, an ancestor to represent a database schema (each database function should be placed in a schema)
25+
* The database name of the schema is derives from the class name based on the provided naming convention
26+
*
27+
* @param executor - executor to execute the queries through
28+
* @param schemaNameOverride - in case the class name would not match the database schema name, this gives the
29+
* possibility of override
30+
* @param namingConvention - the [[NamingConvention]] prescribing how to convert a class name into a db object name
31+
* @tparam E - the engine of the executor type, e.g. Slick [[Database]]
32+
*/
33+
abstract class DBSchema[E](val executor: DBExecutor[E], schemaNameOverride: Option[String] = None)
34+
(implicit namingConvention: NamingConvention) {
35+
36+
37+
def objectNameFromClassName(c: Class[_]): String = {
38+
namingConvention.fromClassNamePerConvention(c)
39+
}
40+
41+
val schemaName: String = schemaNameOverride.getOrElse(objectNameFromClassName(getClass))
42+
43+
def execute[R](query: E => Future[Seq[R]]): Future[Seq[R]] = {
44+
executor.run(query)
45+
}
46+
47+
def unique[R](query: E => Future[Seq[R]]): Future[R] = {
48+
for {
49+
all <- execute(query)
50+
} yield all.head
51+
}
52+
53+
def option[R](query: E => Future[Seq[R]]): Future[Option[R]] = {
54+
for {
55+
all <- execute(query)
56+
} yield all.headOption
57+
}
58+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright 2022 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package za.co.absa.fadb.examples.enceladus
17+
18+
import za.co.absa.fadb.DBSchema
19+
import za.co.absa.fadb.slick.{SlickPgExecutor, SlickPgFunction}
20+
import za.co.absa.fadb.naming_conventions.SnakeCaseNaming.Implicits.namingConvention
21+
import slick.jdbc.GetResult
22+
import za.co.absa.fadb.DBFunction._
23+
import slick.jdbc.PostgresProfile.api._
24+
import za.co.absa.fadb.exceptions.DBFailException
25+
26+
import java.sql.Timestamp
27+
import scala.concurrent.Future
28+
29+
import DatasetSchema._
30+
31+
class DatasetSchema(executor: SlickPgExecutor) extends DBSchema(executor) {
32+
33+
private implicit val schema: DBSchema[ExecutorEngineType] = this
34+
val addSchema = new AddSchema
35+
val getSchema = new GetSchema
36+
val listSchemas = new ListSchemas
37+
}
38+
39+
40+
object DatasetSchema {
41+
type ExecutorEngineType = Database
42+
43+
case class SchemaInput(schemaName: String,
44+
schemaVersion: Int,
45+
schemaDescription: Option[String],
46+
fields: Option[String],
47+
userName: String)
48+
49+
case class Schema(idSchemaVersion: Long,
50+
schemaName: String,
51+
schemaVersion: Int,
52+
schemaDescription: Option[String],
53+
fields: Option[String],
54+
createdBy: String,
55+
createdWhen: Timestamp,
56+
updatedBy: String,
57+
updatedWhen: Timestamp,
58+
lockedBy: Option[String],
59+
lockedWhen: Option[Timestamp],
60+
deletedBy: Option[String],
61+
deletedWhen: Option[Timestamp])
62+
63+
case class SchemaHeader(schemaName: String, schemaLatestVersion: Int)
64+
65+
private implicit val SchemaHeaderImplicit: GetResult[SchemaHeader] = GetResult(r => {SchemaHeader(r.<<, r.<<)})
66+
private implicit val GetSchemaImplicit: GetResult[Schema] = GetResult(r => {
67+
val status: Int = r.<<
68+
val statusText: String = r.<<
69+
if (status != 200) {
70+
throw DBFailException(status, statusText)
71+
}
72+
Schema(r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<)
73+
})
74+
75+
final class AddSchema(implicit schema: DBSchema[ExecutorEngineType])
76+
extends DBUniqueFunction[ExecutorEngineType, SchemaInput, Long](schema)
77+
with SlickPgFunction {
78+
79+
override protected def queryFunction(values: SchemaInput): QueryFunction[ExecutorEngineType, Long] = {
80+
val gr:GetResult[Long] = GetResult(r => {
81+
val status: Int = r.<<
82+
val statusText: String = r.<<
83+
if (status != 201) throw DBFailException(status, statusText)
84+
r.<<
85+
})
86+
87+
val sql =
88+
sql"""SELECT A.status, A.status_text, A.id_schema_version
89+
FROM #$functionName(${values.schemaName}, ${values.schemaVersion}, ${values.schemaDescription},
90+
${values.fields}::JSONB, ${values.userName}
91+
) A;"""
92+
93+
makeQueryFunction(sql)(gr)
94+
}
95+
}
96+
97+
final class GetSchema(implicit schema: DBSchema[ExecutorEngineType])
98+
extends DBUniqueFunction[ExecutorEngineType, (String, Option[Int]), Schema](schema)
99+
with SlickPgFunction {
100+
101+
def apply(id: Long): Future[Schema] = {
102+
val sql =
103+
sql"""SELECT A.*
104+
FROM #$functionName($id) A;"""
105+
schema.unique(makeQueryFunction[Schema](sql))
106+
}
107+
108+
override protected def queryFunction(values: (String, Option[Int])): QueryFunction[ExecutorEngineType, Schema] = {
109+
val sql =
110+
sql"""SELECT A.*
111+
FROM #$functionName(${values._1}, ${values._2}) A;"""
112+
113+
makeQueryFunction[Schema](sql)
114+
}
115+
}
116+
117+
final class ListSchemas(implicit schema: DBSchema[ExecutorEngineType])
118+
extends DBSeqFunction[ExecutorEngineType, Boolean, SchemaHeader](schema)
119+
with SlickPgFunction {
120+
121+
override def apply(values: Boolean = false): Future[Seq[SchemaHeader]] = super.apply(values)
122+
123+
override protected def queryFunction(values: Boolean): QueryFunction[ExecutorEngineType, SchemaHeader] = {
124+
val sql =
125+
sql"""SELECT A.schema_name, A.schema_latest_version
126+
FROM #$functionName($values) as A;"""
127+
makeQueryFunction[SchemaHeader](sql)
128+
}
129+
}
130+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright 2022 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package za.co.absa.fadb.exceptions
17+
18+
case class DBFailException(status: Int, message: String) extends Exception(message)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright 2022 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package za.co.absa.fadb.exceptions
17+
18+
case class NamingException(message: String) extends Exception(message)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2022 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package za.co.absa.fadb.naming_conventions
17+
18+
import za.co.absa.fadb.naming_conventions.letters_case.LettersCase
19+
import za.co.absa.fadb.naming_conventions.letters_case.LettersCase.AsIs
20+
21+
class AsIsNaming(lettersCase: LettersCase) extends NamingConvention{
22+
override def stringPerConvention(original: String): String = {
23+
lettersCase.convert(original)
24+
}
25+
}
26+
27+
object AsIsNaming {
28+
object Implicits {
29+
implicit val namingConvention: NamingConvention = new AsIsNaming(AsIs)
30+
}
31+
}

0 commit comments

Comments
 (0)