Skip to content

Commit 740f7c6

Browse files
morazowjakobbraun
andauthored
#17: Added support for user defined separators for connection object (#19)
Co-authored-by: jakobbraun <jakob.braun@posteo.de>
1 parent afaa5b6 commit 740f7c6

18 files changed

+185
-153
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ charset = utf-8
88
indent_size = 2
99
end_of_line = lf
1010
indent_style = space
11-
max_line_length = 98
11+
max_line_length = 120
1212
trim_trailing_whitespace = true
1313

1414
[*.md]

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ project/target
88
target/
99
.history
1010
.bsp
11+
.bloop
12+
.metals
13+
metals.sbt
1114

1215
# Java
1316
*.class

doc/changes/changes_0.3.0.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1-
# Import Export UDF Common Scala 0.3.0, released 2021-??-??
1+
# Import Export UDF Common Scala 0.3.0, released 2021-10-??
22

3-
Code name:
3+
Code name: Added custom separators
44

55
## Summary
66

7+
In this release, we added custom user defined property separators. We also migrated our continuous integration from Travis CI to Github Actions.
8+
9+
## Features
10+
11+
* #17: Added support for user provided separators
12+
713
## Refactoring
814

15+
* #15: Migrated to Github actions
16+
917
## Dependency Updates
1018

1119
### Runtime Dependency Updates
@@ -19,7 +27,7 @@ Code name:
1927
### Test Dependency Updates
2028

2129
* Updated `org.mockito:mockito-core:test:3.6.0` to `3.12.4`
22-
* Updated `org.scalatest:scalatest:test:3.2.2` to `3.2.9`
30+
* Updated `org.scalatest:scalatest:test:3.2.2` to `3.2.10`
2331

2432
### Plugin Updates
2533

@@ -28,7 +36,7 @@ Code name:
2836
* Updated `com.timushev.sbt:sbt-updates:0.5.1` to `0.6.0`
2937
* Updated `com.typesafe.sbt:sbt-git:1.0.0` to `1.0.1`
3038
* Updated `org.scoverage:sbt-coveralls:1.2.7` to `1.3.1`
31-
* Updated `org.scoverage:sbt-scoverage:1.6.1` to `1.8.2`
39+
* Updated `org.scoverage:sbt-scoverage:1.6.1` to `1.9.0`
3240
* Updated `org.wartremover:sbt-wartremover:2.4.12` to `2.4.16`
3341
* Updated `org.wartremover:sbt-wartremover-contib:1.3.10` to `1.3.12`
3442
* Updated `org.xerial.sbt:sbt-sonatype:3.9.4` to `3.9.10`

project/Dependencies.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ object Dependencies {
1313
private val SLF4JSimpleVersion = "1.7.32"
1414

1515
// Test dependencies versions
16-
private val ScalaTestVersion = "3.2.9"
16+
private val ScalaTestVersion = "3.2.10"
1717
private val ScalaTestPlusVersion = "1.0.0-M2"
1818
private val MockitoCoreVersion = "3.12.4"
1919

project/plugins.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.0")
1616

1717
// Adds Scala Code Coverage (Scoverage) used during unit tests
1818
// http://github.com/scoverage/sbt-scoverage
19-
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2")
19+
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.0")
2020

2121
// Adds SBT Coveralls plugin for uploading Scala code coverage to
2222
// https://coveralls.io

src/main/scala/com/exasol/common/AbstractProperties.scala

Lines changed: 31 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
11
package com.exasol.common
22

3-
import scala.collection.SortedMap
4-
5-
import com.exasol.{ExaConnectionInformation, ExaMetadata}
3+
import com.exasol.ExaConnectionInformation
4+
import com.exasol.ExaMetadata
5+
import com.exasol.common.CommonConstants._
66

77
/**
8-
* An abstract class that holds the user provided key-value parameters
9-
* when using the user-defined-functions (UDFs).
8+
* An abstract class that holds the user provided key-value parameters when using the user-defined-functions (UDFs).
109
*
11-
* This only represents the raw string key-value pairs. Specific
12-
* implementations should extends this class to support required UDF
13-
* key-value parameters.
10+
* This only represents the raw string key-value pairs. Specific implementations should extends this class to support
11+
* required UDF key-value parameters.
1412
*/
1513
abstract class AbstractProperties(private val properties: Map[String, String]) {
1614

17-
/** An optional property key name for the named connection object. */
18-
final val CONNECTION_NAME_PROPERTY: String = "CONNECTION_NAME"
19-
2015
/**
2116
* Checks whether the key-value properties map is empty.
2217
*/
@@ -55,37 +50,29 @@ abstract class AbstractProperties(private val properties: Map[String, String]) {
5550

5651
/** Checks if the Exasol named connection property is provided. */
5752
final def hasNamedConnection(): Boolean =
58-
containsKey(CONNECTION_NAME_PROPERTY)
53+
containsKey(CONNECTION_NAME)
54+
55+
private[this] def getPropertySeparator(): String =
56+
get(CONNECTION_PROPERTY_SEPARATOR).fold(CONNECTION_PROPERTY_SEPARATOR_DEFAULT_VALUE)(identity)
57+
58+
private[this] def getKeyValueAssignment(): String =
59+
get(CONNECTION_KEYVALUE_ASSIGNMENT).fold(CONNECTION_KEYVALUE_ASSIGNMENT_DEFAULT_VALUE)(identity)
5960

6061
/**
61-
* Parses the connection object password into key-value map pairs.
62+
* Parses the connection object password into key-value pairs.
63+
*
64+
* If the connection object contains the username, it is mapped to the {@code keyForUsername}
65+
* parameter. However, this value is overwritten if the provided key is available in password
66+
* string of connection object.
6267
*
63-
* If the connection object contains the username, it is mapped to the
64-
* {@code keyForUsername} parameter. However, this value is
65-
* overwritten if the provided key is available in password string of
66-
* connection object.
68+
* Users can set also property separators using {@code CONNECTION_SEPARATOR} and {@code
69+
* CONNECTION_KEYVALUE_ASSIGNMENT} parameters.
6770
*/
68-
final def parseConnectionInfo(
69-
keyForUsername: String,
70-
exaMetadata: Option[ExaMetadata]
71-
): Map[String, String] = {
71+
final def parseConnectionInfo(keyForUsername: String, exaMetadata: Option[ExaMetadata]): Map[String, String] = {
7272
val connection = getConnectionInformation(exaMetadata)
7373
val username = connection.getUser()
7474
val password = connection.getPassword();
75-
val map = password
76-
.split(";")
77-
.map { str =>
78-
val idx = str.indexOf('=')
79-
if (idx < 0) {
80-
throw new IllegalArgumentException(
81-
"Connection object password does not contain key=value pairs!"
82-
)
83-
}
84-
str.substring(0, idx).strip().replace("\n", "") ->
85-
str.substring(idx + 1).strip().replace("\n", "")
86-
}
87-
.toMap
88-
75+
val map = PropertiesParser(getPropertySeparator(), getKeyValueAssignment()).mapFromString(password)
8976
if (username.isEmpty()) {
9077
map
9178
} else {
@@ -94,15 +81,12 @@ abstract class AbstractProperties(private val properties: Map[String, String]) {
9481
}
9582

9683
/**
97-
* Returns an Exasol [[ExaConnectionInformation]] named connection
98-
* information.
84+
* Returns an Exasol [[ExaConnectionInformation]] named connection information.
9985
*/
100-
private[common] final def getConnectionInformation(
101-
exaMetadata: Option[ExaMetadata]
102-
): ExaConnectionInformation =
86+
private[common] final def getConnectionInformation(exaMetadata: Option[ExaMetadata]): ExaConnectionInformation =
10387
exaMetadata.fold {
10488
throw new IllegalArgumentException("Exasol metadata is None!")
105-
}(_.getConnection(getString(CONNECTION_NAME_PROPERTY)))
89+
}(_.getConnection(getString(CONNECTION_NAME)))
10690

10791
/**
10892
* Returns the value of the key as a String.
@@ -124,17 +108,14 @@ abstract class AbstractProperties(private val properties: Map[String, String]) {
124108
/**
125109
* Returns a string listing of all key-value property pairs.
126110
*
127-
* The resulting string contains key-value pairs in a sorted order by
128-
* keys.
111+
* The resulting string contains key-value pairs in a sorted order by keys.
129112
*
130-
* @param keyValueSeparator The separator between each key-value pairs
131-
* @param propertySeparator The separator between each key-value pair strings
132-
* @return The string value of properties with provided separators
113+
* @param propertySeparator a separator between each key-value pair strings
114+
* @param keyValueAssignment an assignment between each key-value pairs
115+
* @return a string value of properties with provided separators
133116
*/
134-
final def mkString(keyValueSeparator: String, propertySeparator: String): String =
135-
(SortedMap.empty[String, String] ++ properties)
136-
.map { case (k, v) => s"$k$keyValueSeparator$v" }
137-
.mkString(propertySeparator)
117+
final def mkString(propertySeparator: String, keyValueAssignment: String): String =
118+
PropertiesParser(propertySeparator, keyValueAssignment).mapToString(properties)
138119

139120
@SuppressWarnings(Array("org.wartremover.warts.Return"))
140121
// scalastyle:off
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.exasol.common
2+
3+
object CommonConstants {
4+
val CONNECTION_NAME: String = "CONNECTION_NAME"
5+
val CONNECTION_PROPERTY_SEPARATOR: String = "CONNECTION_SEPARATOR"
6+
val CONNECTION_PROPERTY_SEPARATOR_DEFAULT_VALUE: String = ";"
7+
val CONNECTION_KEYVALUE_ASSIGNMENT: String = "CONNECTION_KEYVALUE_ASSIGNMENT"
8+
val CONNECTION_KEYVALUE_ASSIGNMENT_DEFAULT_VALUE: String = "="
9+
}

src/main/scala/com/exasol/common/CommonProperties.scala

Lines changed: 0 additions & 39 deletions
This file was deleted.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.exasol.common
2+
3+
import scala.collection.SortedMap
4+
5+
/**
6+
* A class that reads, serializes and deserializes key value properties to a string.
7+
*
8+
* For example, given {@code "key1=A;key2=B"} parses to a map of {@code Map('key1' -> 'A', 'key2' -> 'B')}.
9+
*
10+
* @param propertySeparator a separator for properties
11+
* @param keyValueAssignment an assignment for key value pairs
12+
*/
13+
final case class PropertiesParser(private val propertySeparator: String, private val keyValueAssignment: String) {
14+
15+
/**
16+
* Parses properly separated input string into key-value properties map.
17+
*
18+
* @return a map of key value pairs
19+
*/
20+
def mapFromString(string: String): Map[String, String] =
21+
string
22+
.split(propertySeparator)
23+
.map(splitStringToTuple)
24+
.toMap
25+
26+
/**
27+
* Deserializes a key-value properties map into a string.
28+
*
29+
* The resulting string contains key-value pairs in a sorted order by keys.
30+
*
31+
* @return a string value of properties with provided separators
32+
*/
33+
def mapToString(map: Map[String, String]): String =
34+
(SortedMap.empty[String, String] ++ map)
35+
.map { case (k, v) => s"$k$keyValueAssignment$v" }
36+
.mkString(propertySeparator)
37+
38+
private[this] def splitStringToTuple(string: String): Tuple2[String, String] = {
39+
val idx = string.indexOf(keyValueAssignment)
40+
if (idx < 0) {
41+
throw new IllegalArgumentException(
42+
s"Properties input string does not contain key-value assignment '$keyValueAssignment'."
43+
)
44+
}
45+
stripAndReplace(string.substring(0, idx)) -> stripAndReplace(string.substring(idx + keyValueAssignment.length()))
46+
}
47+
48+
private[this] def stripAndReplace(string: String): String =
49+
string.strip().replace("\n", "").replace("\r", "")
50+
51+
}

src/test/scala/com/exasol/avro/AvroComplexTypesTest.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class AvroComplexTypesTest extends AnyFunSuite {
105105
val thrown = intercept[IllegalArgumentException] {
106106
AvroRow(record)
107107
}
108-
assert(thrown.getMessage.contains("Unsupported Avro Array type"))
108+
assert(thrown.getMessage().contains("Unsupported Avro Array type"))
109109
}
110110

111111
test("parse avro map type") {
@@ -195,7 +195,7 @@ class AvroComplexTypesTest extends AnyFunSuite {
195195
val thrown = intercept[IllegalArgumentException] {
196196
AvroRow(record)
197197
}
198-
assert(thrown.getMessage.contains("Unsupported Avro Record type"))
198+
assert(thrown.getMessage().contains("Unsupported Avro Record type"))
199199
}
200200

201201
}

0 commit comments

Comments
 (0)