Skip to content

Additional db support#108

Merged
driessamyn merged 6 commits into
mainfrom
additional-db-support
Apr 23, 2025
Merged

Additional db support#108
driessamyn merged 6 commits into
mainfrom
additional-db-support

Conversation

@driessamyn

Copy link
Copy Markdown
Owner

Support for:

  • Oracle
  • MS SQL Server
  • SQLite

Copilot AI review requested due to automatic review settings April 23, 2025 19:32
Support for SQLite, integrating it in the Integration Tests and enhance date/time support to handle SQLite storing date/time as strings/Long.
bugfix: char converter

- Update integration tests to support MS SQL Server
- Update UUID Converter to support SQL server using JDBC type CHAR
- Fix char conversion.
@driessamyn driessamyn force-pushed the additional-db-support branch from b5f7f27 to 18d99e2 Compare April 23, 2025 19:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds support for Oracle, MS SQL Server, and SQLite by extending the DbFlavour enum, updating conversion functions, and adding new integration tests. Key changes include:

  • Enhancements to database flavor detection and conversion functions in Converters.kt and DbFlavour.kt.
  • Updates to integration tests to use dynamic table names and improved UUID normalization.
  • Adjustments in the Field data class to include DB flavor information.

Reviewed Changes

Copilot reviewed 37 out of 42 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
core/src/main/kotlin/net/samyn/kapper/internal/KapperImpl.kt Updated field extraction to pass the connection’s DB flavor.
core/src/main/kotlin/net/samyn/kapper/internal/DbFlavour.kt Extended to include Oracle and MSSQL support.
core/src/main/kotlin/net/samyn/kapper/internal/Converters.kt Adjusted timezone conversions and added new conversion functions.
core/src/main/kotlin/net/samyn/kapper/Field.kt Added a new dbFlavour parameter to the Field data class.
Integration test files Updated table naming, UUID normalization, and query syntax to support multiple DB flavors.
README.md & build workflows Updated statuses and build parameters to reflect new DB support.
Files not reviewed (5)
  • .run/Integration Tests (ALL).run.xml: Language not supported
  • .run/Integration Tests (MS SQL Server).run.xml: Language not supported
  • .run/Integration Tests (Oracle).run.xml: Language not supported
  • .run/Integration Tests.run.xml: Language not supported
  • buildSrc/src/main/kotlin/kapper.library-conventions.gradle.kts: Language not supported
Comments suppressed due to low confidence (2)

core/src/main/kotlin/net/samyn/kapper/internal/Converters.kt:23

  • Changing the time zone conversion from UTC to systemDefault may lead to unexpected behavior; please confirm that this change is intentional and update any related documentation accordingly.
value.atZone(java.time.ZoneOffset.systemDefault()).toInstant()

core/src/main/kotlin/net/samyn/kapper/Field.kt:13

  • The addition of the 'dbFlavour' parameter to the Field data class requires updates to all Field instantiations; please ensure that all usages are revised accordingly.
data class Field(val columnIndex: Int, val type: JDBCType, val typeName: String, val dbFlavour: DbFlavour)

Comment thread core/src/main/kotlin/net/samyn/kapper/internal/Converters.kt
Comment thread core/src/main/kotlin/net/samyn/kapper/internal/Converters.kt
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Apr 23, 2025

Copy link
Copy Markdown

PR Code Suggestions ✨

Latest suggestions up to dd0d9b4

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix connection initialization order

The databaseContainers() method is using the connections map before it's
populated. The connections map is only filled during test execution, but the
method is called during test initialization. Initialize the connections map
first before returning it.

core/src/integrationTest/kotlin/net/samyn/kapper/AbstractDbTests.kt [59-95]

 private val connections = ConcurrentHashMap<DbFlavour, Connection>()
 
 private fun getConnection(container: JdbcDatabaseContainer<*>): Connection {
     Class.forName(container.driverClassName)
     return DriverManager.getConnection(container.jdbcUrl, container.username, container.password)
 }
 
 val dbs =
     mapOf(
         DbFlavour.POSTGRESQL to { getConnection(postgresql) },
         DbFlavour.MYSQL to { getConnection(mysql) },
         DbFlavour.SQLITE to { DriverManager.getConnection("jdbc:sqlite::memory:") },
         DbFlavour.MSSQLSERVER to { getConnection(msSqlServer) },
         DbFlavour.ORACLE to { getConnection(oracle) },
     ).filter {
         // by default run against SQLite and PG only
         //  this allows parallel runs for different int tests.
         when (System.getProperty("db", "").uppercase()) {
             "" -> it.key == DbFlavour.SQLITE || it.key == DbFlavour.POSTGRESQL
             "ALL" -> true
             else -> it.key == DbFlavour.valueOf(System.getProperty("db").uppercase())
         }
     }
 
 @JvmStatic
 fun databaseContainers(): List<Arguments> {
     println("--------------------------------")
     println("Running tests against:")
-    val connections =
+    // Initialize connections first
+    dbs.forEach { (key, connectionFactory) ->
+        connections.computeIfAbsent(key) { connectionFactory() }
+    }
+    val connectionArgs =
         connections
             .map {
                 println("   ${it.key}")
                 arguments(named(it.key.toString(), it.value))
             }
     println("--------------------------------")
-    return connections
+    return connectionArgs
 }
  • Apply this suggestion
Suggestion importance[1-10]: 9

__

Why: The suggestion identifies a critical issue where connections are being accessed before they're initialized. This could lead to empty test results or test failures. The fix properly initializes connections before they're used in the databaseContainers() method, which is essential for test execution.

High
Use consistent timezone handling

Using system default timezone for date-time conversions can lead to inconsistent
behavior across different environments. Consider using a fixed timezone like UTC
for consistent and predictable date-time handling.

core/src/main/kotlin/net/samyn/kapper/internal/Converters.kt [23-24]

 internal fun convertInstant(value: Any): Instant =
     when (value) {
         is LocalTime -> {
             LocalDate.now().atTime(value).toInstant(java.time.ZoneOffset.UTC)
         }
 
         is LocalDateTime -> {
-            value.atZone(java.time.ZoneOffset.systemDefault()).toInstant()
+            value.atZone(java.time.ZoneOffset.UTC).toInstant()
         }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies an inconsistency in timezone handling where the PR changed from UTC to system default. Using system default can lead to unpredictable behavior across different environments, making UTC a more reliable choice for consistent date-time operations.

Medium
Fix case-sensitive property comparison

The property key comparison is case-sensitive but column names are converted to
lowercase, which can cause incorrect missing property detection. Convert both
the property key and column name to lowercase for proper case-insensitive
comparison.

core/src/main/kotlin/net/samyn/kapper/internal/KotlinDataClassMapper.kt [32-33]

 } else if (columns.size < properties.size) {
     val all = columns.map { it.name.lowercase() }
-    val missing = properties.filter { !it.value.isOptional && !all.contains(it.key) }
+    val missing = properties.filter { !it.value.isOptional && !all.contains(it.key.lowercase()) }
     if (missing.isNotEmpty()) {
         throw KapperMappingException("The following properties are non-optional and missing: ${missing.keys}")
     }
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a bug where column names are converted to lowercase but property keys remain in their original case, which could cause incorrect missing property detection in case-insensitive database systems.

Medium
Fix type inconsistency

The test is using an Int value but then converting it to Long inside the
assertion. This inconsistency could lead to confusion and potential bugs. It
would be clearer to use a consistent type throughout the test.

core/src/test/kotlin/net/samyn/kapper/internal/converter/LocalTimeConverterTest.kt [41-42]

+val twoOclockPmUTCLong = 14 * 60 * 60 * 1000L // 14:00:00 - SQLite stores time as ms from 1970
 val localTime = convertLocalTime(twoOclockPmUTC)
-localTime.shouldBe(Instant.ofEpochMilli(twoOclockPmUTC.toLong()).atZone(ZoneOffset.systemDefault()).toLocalTime())
+localTime.shouldBe(Instant.ofEpochMilli(twoOclockPmUTCLong).atZone(ZoneOffset.systemDefault()).toLocalTime())
  • Apply this suggestion
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a type inconsistency where an Int value is used but then converted to Long in the assertion. Using a consistent type (Long) throughout the test improves code clarity and prevents potential implicit conversion issues.

Medium
General
Remove unused parameter

The sql parameter is passed to queryFlow() but it's not used in the
implementation. This is unnecessary and could lead to confusion. Remove the
unused parameter to simplify the function signature.

coroutines/src/main/kotlin/net/samyn/kapper/coroutines/KapperKotlinFlowQueryFun.kt [84-89]

 inline fun <reified T : Any> Connection.queryAsFlow(
     sql: String,
     vararg args: Pair<String, Any?>,
     fetchSize: Int = 0,
     noinline mapper: (ResultSet, Map<String, Field>) -> T = { rs, fields -> createMapper<T>()(rs, fields) },
 ): Flow<T> {
     require(sql.isNotBlank()) { "SQL query cannot be empty or blank" }
     this.executeQuery(Query(sql), args.toMap(), fetchSize).let { rs ->
-        return queryFlow(rs, mapper, sql, this.getDbFlavour())
+        return queryFlow(rs, mapper, this.getDbFlavour())
     }
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that the 'sql' parameter is passed to queryFlow() but not used in its implementation. Removing unused parameters improves code clarity and maintainability by simplifying the function signature.

Low
  • Update

Previous suggestions

✅ Suggestions up to commit 5cb0c29
CategorySuggestion                                                                                                                                    Impact
Possible issue
Initialize connections before use

The databaseContainers() method is using the connections map before it's
populated. The connections map is empty when this method is called, resulting in
empty test runs. You need to initialize the connections first.

core/src/integrationTest/kotlin/net/samyn/kapper/AbstractDbTests.kt [59-95]

 private val connections = ConcurrentHashMap<DbFlavour, Connection>()
 
 private fun getConnection(container: JdbcDatabaseContainer<*>): Connection {
     Class.forName(container.driverClassName)
     return DriverManager.getConnection(container.jdbcUrl, container.username, container.password)
 }
 
 val dbs =
     mapOf(
         DbFlavour.POSTGRESQL to { getConnection(postgresql) },
         DbFlavour.MYSQL to { getConnection(mysql) },
         DbFlavour.SQLITE to { DriverManager.getConnection("jdbc:sqlite::memory:") },
         DbFlavour.MSSQLSERVER to { getConnection(msSqlServer) },
         DbFlavour.ORACLE to { getConnection(oracle) },
     ).filter {
         // by default run against SQLite and PG only
         //  this allows parallel runs for different int tests.
         when (System.getProperty("db", "").uppercase()) {
             "" -> it.key == DbFlavour.SQLITE || it.key == DbFlavour.POSTGRESQL
             "ALL" -> true
             else -> it.key == DbFlavour.valueOf(System.getProperty("db").uppercase())
         }
     }
 
 @JvmStatic
 fun databaseContainers(): List<Arguments> {
+    // Initialize connections if not already done
+    dbs.forEach { (key, connectionFactory) ->
+        connections.computeIfAbsent(key) { connectionFactory() }
+    }
+    
     println("--------------------------------")
     println("Running tests against:")
-    val connections =
+    val testConnections =
         connections
             .map {
                 println("   ${it.key}")
                 arguments(named(it.key.toString(), it.value))
             }
     println("--------------------------------")
-    return connections
+    return testConnections
 }
Suggestion importance[1-10]: 9

__

Why: This is a critical bug fix. The databaseContainers() method is trying to use the connections map before it's populated, which would result in empty test runs. The improved code ensures connections are initialized before being used in tests.

High
Use fixed timezone

Using system default timezone for date-time conversions can lead to inconsistent
behavior across different environments. Consider using a fixed timezone like UTC
for consistent and predictable date-time handling.

core/src/main/kotlin/net/samyn/kapper/internal/Converters.kt [23]

 internal fun convertInstant(value: Any): Instant =
     when (value) {
         ...
         is LocalDateTime -> {
-            value.atZone(java.time.ZoneOffset.systemDefault()).toInstant()
+            value.atZone(java.time.ZoneOffset.UTC).toInstant()
         }
         ...
     }
Suggestion importance[1-10]: 8

__

Why: The PR changed the timezone from UTC to systemDefault(), which can lead to inconsistent behavior across different environments. Using a fixed timezone like UTC would provide more predictable and consistent date-time handling.

Medium
Fix UUID comparison

The test is assuming that UUIDs will be converted to lowercase without dashes,
but this behavior might vary across different database systems. Oracle, for
example, stores UUIDs as RAW(16) which might have a different string
representation. This could lead to test failures when running against different
databases.

core/src/integrationTest/kotlin/net/samyn/kapper/QuerySingleTests.kt [94]

-villain.id!!.shouldBe(superman.id.toString().lowercase().replace("-", ""))
+// For Oracle, the UUID might be stored as a binary value
+// For other DBs, it might be stored with or without dashes
+villain.id!!.lowercase().replace("-", "").shouldBe(superman.id.toString().lowercase().replace("-", ""))
Suggestion importance[1-10]: 7

__

Why: This suggestion addresses a potential cross-database compatibility issue. The current code assumes a specific UUID string representation, but this could vary across different database systems, causing test failures.

Medium
Fix case-sensitive comparison
Suggestion Impact:The commit addressed the case-sensitivity issue but used a different approach. Instead of converting the property name to lowercase with .lowercase(), it used the property's key (it.key) which appears to be the correct identifier for comparison with the lowercase column names.

code diff:

-            val missing = properties.filter { !it.value.isOptional && !all.contains(it.value.name) }
+            val missing = properties.filter { !it.value.isOptional && !all.contains(it.key) }

The property name comparison is case-sensitive in the filter but
case-insensitive in the columns list. This inconsistency can cause the code to
incorrectly report missing properties. Ensure both sides of the comparison use
the same case handling.

core/src/main/kotlin/net/samyn/kapper/internal/KotlinDataClassMapper.kt [32-33]

 } else if (columns.size < properties.size) {
     val all = columns.map { it.name.lowercase() }
-    val missing = properties.filter { !it.value.isOptional && !all.contains(it.value.name) }
+    val missing = properties.filter { !it.value.isOptional && !all.contains(it.value.name.lowercase()) }
     if (missing.isNotEmpty()) {
         throw KapperMappingException("The following properties are non-optional and missing: ${missing.keys}")
     }
 }
Suggestion importance[1-10]: 7

__

Why: The code converts column names to lowercase but compares them with property names that aren't converted, creating a case-sensitivity mismatch that could cause the code to incorrectly report missing properties.

Medium
Fix type conversion mismatch

The CHAR type conversion returns a CharArray which is inconsistent with the
expected Char type in the convertChar function. This can lead to type mismatch
errors. Consider returning a Char or null instead.

core/src/main/kotlin/net/samyn/kapper/internal/SQLTypesConverter.kt [38-37]

 in
 listOf(JDBCType.CHAR),
--> resultSet.getString(fieldIndex)?.toCharArray()
+-> resultSet.getString(fieldIndex)?.let { if (it.isNotEmpty()) it[0] else null }
Suggestion importance[1-10]: 6

__

Why: The CHAR type conversion returns a CharArray but the convertChar function expects a Char, which could lead to type mismatch errors when these components interact.

Low
General
Stop database containers

The tearDown method closes connections but doesn't stop the database containers.
This can lead to resource leaks, especially with containers like Oracle and MS
SQL Server which are resource-intensive. You should stop all started containers
in the tearDown method.

core/src/integrationTest/kotlin/net/samyn/kapper/AbstractDbTests.kt [110-116]

 @AfterAll
 fun tearDown() {
     connections.forEach { (_, connection) ->
         connection.close()
     }
     connections.clear()
+    
+    // Stop any containers that were started
+    if (postgresql.isRunning) postgresql.stop()
+    if (mysql.isRunning) mysql.stop()
+    if (oracle.isRunning) oracle.stop()
+    if (msSqlServer.isRunning) msSqlServer.stop()
 }
Suggestion importance[1-10]: 8

__

Why: This suggestion prevents resource leaks by properly stopping database containers after tests. Resource-intensive containers like Oracle and MS SQL Server could cause performance issues if not properly stopped.

Medium
Separate test cases

The test is checking for invalid UUID strings, but the empty string case might
be handled differently than malformed UUIDs. Consider separating these test
cases to ensure proper exception handling for each specific case.

core/src/test/kotlin/net/samyn/kapper/internal/converter/UUIDConverterTest.kt [43-49]

-@ParameterizedTest
-@ValueSource(strings = ["", "123e4567-e89b-12d3-a456-426614ZZZZZZ"])
-fun `when invalid string cannot convert to UUID`(input: String) {
+@Test
+fun `when empty string cannot convert to UUID`() {
     shouldThrow<KapperParseException> {
-        convertUUID(input)
+        convertUUID("")
     }
 }
 
+@Test
+fun `when malformed string cannot convert to UUID`() {
+    shouldThrow<KapperParseException> {
+        convertUUID("123e4567-e89b-12d3-a456-426614ZZZZZZ")
+    }
+}
+
Suggestion importance[1-10]: 5

__

Why: The suggestion to separate the parameterized test into individual test cases has merit for improved test clarity and more specific error messages. While the current implementation is functional, having separate tests would make it clearer which specific invalid UUID format caused a failure.

Low
Suggestions up to commit b5f7f27
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix empty connections map

The databaseContainers() method is using the connections variable before it's
populated. This will result in empty test runs because the connections map is
empty when the method is called. You need to initialize the connections map
before returning it.

core/src/integrationTest/kotlin/net/samyn/kapper/AbstractDbTests.kt [59-95]

 private val connections = ConcurrentHashMap<DbFlavour, Connection>()
 
 private fun getConnection(container: JdbcDatabaseContainer<*>): Connection {
     Class.forName(container.driverClassName)
     return DriverManager.getConnection(container.jdbcUrl, container.username, container.password)
 }
 
 val dbs =
     mapOf(
         DbFlavour.POSTGRESQL to { getConnection(postgresql) },
         DbFlavour.MYSQL to { getConnection(mysql) },
         DbFlavour.SQLITE to { DriverManager.getConnection("jdbc:sqlite::memory:") },
         DbFlavour.MSSQLSERVER to { getConnection(msSqlServer) },
         DbFlavour.ORACLE to { getConnection(oracle) },
     ).filter {
         // by default run against SQLite and PG only
         //  this allows parallel runs for different int tests.
         when (System.getProperty("db", "").uppercase()) {
             "" -> it.key == DbFlavour.SQLITE || it.key == DbFlavour.POSTGRESQL
             "ALL" -> true
             else -> it.key == DbFlavour.valueOf(System.getProperty("db").uppercase())
         }
     }
 
 @JvmStatic
 fun databaseContainers(): List<Arguments> {
     println("--------------------------------")
     println("Running tests against:")
-    val connections =
+    
+    // Initialize connections if empty
+    if (connections.isEmpty()) {
+        dbs.forEach { (flavour, connectionFactory) ->
+            connections.computeIfAbsent(flavour) { connectionFactory() }
+        }
+    }
+    
+    val connectionArgs =
         connections
             .map {
                 println("   ${it.key}")
                 arguments(named(it.key.toString(), it.value))
             }
     println("--------------------------------")
-    return connections
+    return connectionArgs
 }
Suggestion importance[1-10]: 9

__

Why: The suggestion identifies a critical issue where the databaseContainers() method is using the connections map before it's populated, which would result in empty test runs. The fix properly initializes connections if the map is empty, ensuring tests will run against the configured databases.

High
Handle null timestamps

The getTimestamp method can return null, but the code doesn't handle this case,
which could lead to a NullPointerException. Add null checks before accessing the
timestamp.

core/src/main/kotlin/net/samyn/kapper/internal/SQLTypesConverter.kt [83-93]

 private fun convertTimestamp(
     resultSet: ResultSet,
     fieldIndex: Int,
     sqlTypeName: String,
 ): Any =
     when (sqlTypeName.uppercase()) {
         "DATE" -> {
-            resultSet.getTimestamp(fieldIndex).toLocalDateTime()
+            resultSet.getTimestamp(fieldIndex)?.toLocalDateTime() 
+                ?: throw KapperUnsupportedOperationException("Null timestamp cannot be converted to LocalDateTime")
         }
-        else -> resultSet.getTimestamp(fieldIndex).toInstant()
+        else -> resultSet.getTimestamp(fieldIndex)?.toInstant() 
+            ?: throw KapperUnsupportedOperationException("Null timestamp cannot be converted to Instant")
     }
Suggestion importance[1-10]: 8

__

Why: This is a valid and important suggestion that addresses a potential NullPointerException. The ResultSet.getTimestamp method can return null, and the current implementation doesn't handle this case, which could lead to runtime errors.

Medium
Check for empty collections

The function doesn't check for empty strings or empty char arrays before
accessing the first element, which could lead to an IndexOutOfBoundsException.
Add checks for empty collections.

core/src/main/kotlin/net/samyn/kapper/internal/Converters.kt [150-169]

 internal fun convertChar(value: Any): Char =
     if (value is String) {
+        if (value.isEmpty()) {
+            throw KapperParseException("Cannot parse empty string to Char")
+        }
         if (value.length > 1) {
             throw KapperParseException(
                 "Cannot parse $value to Char (length > 1)",
             )
         }
         value[0]
     } else if (value is CharArray) {
+        if (value.isEmpty()) {
+            throw KapperParseException("Cannot parse empty char array to Char")
+        }
         if (value.size > 1) {
             throw KapperParseException(
                 "Cannot parse $value to Char (size > 1)",
             )
         }
         value[0]
     } else {
         throw KapperUnsupportedOperationException(
             "Cannot auto-convert from ${value.javaClass} to Char",
         )
     }
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential IndexOutOfBoundsException that could occur when trying to access the first element of an empty string or char array. Adding checks for empty collections would prevent runtime errors.

Medium
Use fixed timezone

Using system default timezone for date-time conversions can lead to inconsistent
behavior across different environments. Consider using a fixed timezone like UTC
for consistent and predictable date-time handling.

core/src/main/kotlin/net/samyn/kapper/internal/Converters.kt [23-24]

 internal fun convertInstant(value: Any): Instant =
     when (value) {
         is String -> {
             Instant.parse(value)
         }
 
         is LocalTime -> {
             LocalDate.now().atTime(value).toInstant(java.time.ZoneOffset.UTC)
         }
 
         is LocalDateTime -> {
-            value.atZone(java.time.ZoneOffset.systemDefault()).toInstant()
+            value.atZone(java.time.ZoneOffset.UTC).toInstant()
         }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential issue with using system default timezone which can lead to inconsistent behavior across environments. Reverting to UTC (as in the original code) would provide more predictable and consistent date-time handling.

Medium
General
Remove unnecessary type cast

Remove the unnecessary cast to LocalTime since convertLocalTime() should already
return a LocalTime object. The cast suggests a potential type mismatch or
ambiguity in the return type.

core/src/test/kotlin/net/samyn/kapper/internal/converter/LocalTimeConverterTest.kt [16-19]

 val now = Instant.now()
-val locaTime = convertLocalTime(now) as LocalTime
+val locaTime = convertLocalTime(now)
 val expectedTime = LocalTime.ofInstant(now, ZoneOffset.systemDefault())
 locaTime.shouldBe(expectedTime)
Suggestion importance[1-10]: 7

__

Why: The cast to LocalTime is unnecessary if the convertLocalTime() function already returns a LocalTime object. Removing the cast improves code clarity and prevents potential issues if the return type changes.

Medium
Fix variable name typo

Fix the variable name locaTime to localTime for consistency and readability.
This typo appears in multiple test methods and should be corrected throughout
the file.

core/src/test/kotlin/net/samyn/kapper/internal/converter/LocalTimeConverterTest.kt [24-27]

 val timeString = "12:30:00"
-val locaTime = convertLocalTime(timeString)
+val localTime = convertLocalTime(timeString)
 val expectedTime = LocalTime.parse(timeString)
-locaTime.shouldBe(expectedTime)
+localTime.shouldBe(expectedTime)
Suggestion importance[1-10]: 6

__

Why: Correcting the variable name from "locaTime" to "localTime" improves code readability and consistency. This typo appears in multiple test methods and fixing it enhances code quality.

Low
Organization
best practice
Extract database connection management into a separate service class to improve testability and separation of concerns

The connection management code is not easily testable. Extract the database
connection logic into a separate service class that can be mocked in tests. This
would make the test class more focused on testing rather than managing
connections.

core/src/integrationTest/kotlin/net/samyn/kapper/AbstractDbTests.kt [59-64]

-private val connections = ConcurrentHashMap<DbFlavour, Connection>()
-
-private fun getConnection(container: JdbcDatabaseContainer<*>): Connection {
-    Class.forName(container.driverClassName)
-    return DriverManager.getConnection(container.jdbcUrl, container.username, container.password)
+// Extract to a separate service class
+class DatabaseConnectionService {
+    private val connections = ConcurrentHashMap<DbFlavour, Connection>()
+    
+    fun getConnection(container: JdbcDatabaseContainer<*>): Connection {
+        Class.forName(container.driverClassName)
+        return DriverManager.getConnection(container.jdbcUrl, container.username, container.password)
+    }
+    
+    fun getOrCreateConnection(dbFlavour: DbFlavour, connectionFactory: () -> Connection): Connection {
+        return connections.computeIfAbsent(dbFlavour) { connectionFactory() }
+    }
+    
+    fun closeAll() {
+        connections.forEach { (_, connection) -> connection.close() }
+        connections.clear()
+    }
 }
Suggestion importance[1-10]: 6
Low

@github-actions

github-actions Bot commented Apr 23, 2025

Copy link
Copy Markdown

Test Results

 43 files  +  5   43 suites  +5   34s ⏱️ +4s
396 tests + 86  396 ✅ + 86  0 💤 ±0  0 ❌ ±0 
517 runs  +133  517 ✅ +133  0 💤 ±0  0 ❌ ±0 

Results for commit a591979. ± Comparison against base commit 013b188.

This pull request removes 110 and adds 195 tests. Note that renamed tests count towards both.

net.samyn.kapper.ExecuteTests ‑ [1] PostgreSQL
net.samyn.kapper.ExecuteTests ‑ [2] MySQL
net.samyn.kapper.QuerySingleTests ‑ [1] PostgreSQL
net.samyn.kapper.QuerySingleTests ‑ [2] MySQL
net.samyn.kapper.QueryTests ‑ [1] PostgreSQL
net.samyn.kapper.QueryTests ‑ [2] MySQL
net.samyn.kapper.SqlInjectionTest ‑ [1] PostgreSQL
net.samyn.kapper.SqlInjectionTest ‑ [2] MySQL
net.samyn.kapper.TypeCastTest ‑ [1] PostgreSQL
…
net.samyn.kapper.ExecuteTests ‑ [1] MYSQL
net.samyn.kapper.ExecuteTests ‑ [2] MSSQLSERVER
net.samyn.kapper.ExecuteTests ‑ [3] ORACLE
net.samyn.kapper.ExecuteTests ‑ [4] SQLITE
net.samyn.kapper.ExecuteTests ‑ [5] POSTGRESQL
net.samyn.kapper.QuerySingleTests ‑ [1] MYSQL
net.samyn.kapper.QuerySingleTests ‑ [2] MSSQLSERVER
net.samyn.kapper.QuerySingleTests ‑ [3] ORACLE
net.samyn.kapper.QuerySingleTests ‑ [4] SQLITE
net.samyn.kapper.QuerySingleTests ‑ [5] POSTGRESQL
…

♻️ This comment has been updated with latest results.

- Update integration tests to support Oracle
- Various updates to converter/mapper functions to support Oracle types
- Refactor converter tests to be more readable
@driessamyn driessamyn force-pushed the additional-db-support branch from 18d99e2 to 5cb0c29 Compare April 23, 2025 19:59
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: Build and run tests

Failed stage: Run webiny/action-conventional-commits@v1.3.0 [❌]

Failure summary:

The action failed because one or more commit messages did not comply with the conventional-commits
specification. Specifically, line 203 shows the error: "🚫 According to the conventional-commits
specification, some of the commit messages are not valid." The issue appears to be with the commit
message on line 201 which is marked with a 🚩: "bugfix: fix case-insensitive property name comparison
in auto-mapper." This commit message likely doesn't follow the required format.

Relevant error logs:
1:  ##[group]Operating System
2:  Ubuntu
...

188:  ✅ feat: SQLite support
189:  Support for SQLite, integrating it in the Integration Tests and enhance date/time support to handle SQLite storing date/time as strings/Long.
190:  ✅ refactor: Optimise test container lifecycle for integration tests
191:  Only start test container when needed.
192:  ✅ feat: Support for MS SQL Server
193:  bugfix: char converter
194:  - Update integration tests to support MS SQL Server
195:  - Update UUID Converter to support SQL server using JDBC type CHAR
196:  - Fix char conversion.
197:  ✅ feat: Support for Oracle DB
198:  - Update integration tests to support Oracle
199:  - Various updates to converter/mapper functions to support Oracle types
200:  - Refactor converter tests to be more readable
201:  🚩 bugfix: fix case-insensitive property name comparison in auto-mapper.
202:  ##[endgroup]
203:  ##[error]🚫 According to the conventional-commits specification, some of the commit messages are not valid.
204:  ##[group]Run actions/upload-artifact@v4
...

210:  compression-level: 6
211:  overwrite: false
212:  include-hidden-files: false
213:  ##[endgroup]
214:  ##[warning]No files were found with the provided path: **/build/test-results/*/TEST-*.xml. No artifacts will be uploaded.
215:  ##[group]Run EnricoMi/publish-unit-test-result-action@v2
216:  with:
217:  files: **/build/test-results/*/TEST-*.xml
218:  
219:  github_token: ***
220:  github_token_actor: github-actions
221:  github_retries: 10
222:  ssl_verify: true
223:  check_name: Test Results
224:  comment_mode: always
225:  fail_on: test failures
226:  action_fail: false
227:  action_fail_on_inconclusive: false
228:  time_unit: seconds
229:  report_suite_logs: none
230:  ignore_runs: false
231:  check_run: true
232:  job_summary: true
233:  compare_to_earlier_commit: true
234:  pull_request_build: merge
235:  check_run_annotations: all tests, skipped tests
236:  seconds_between_github_reads: 0.25
237:  seconds_between_github_writes: 2.0
238:  json_thousands_separator:  
239:  json_suite_details: false
240:  json_test_case_results: false
241:  search_pull_requests: false
242:  ##[endgroup]
243:  ##[command]/usr/bin/docker run --name ghcrioenricomipublishunittestresultactionv2190_a19095 --label 9f0834 --workdir /github/workspace --rm -e "INPUT_FILES" -e "INPUT_GITHUB_TOKEN" -e "INPUT_GITHUB_TOKEN_ACTOR" -e "INPUT_GITHUB_RETRIES" -e "INPUT_SSL_VERIFY" -e "INPUT_COMMIT" -e "INPUT_CHECK_NAME" -e "INPUT_COMMENT_TITLE" -e "INPUT_COMMENT_MODE" -e "INPUT_FAIL_ON" -e "INPUT_ACTION_FAIL" -e "INPUT_ACTION_FAIL_ON_INCONCLUSIVE" -e "INPUT_JUNIT_FILES" -e "INPUT_NUNIT_FILES" -e "INPUT_XUNIT_FILES" -e "INPUT_TRX_FILES" -e "INPUT_TIME_UNIT" -e "INPUT_TEST_FILE_PREFIX" -e "INPUT_REPORT_INDIVIDUAL_RUNS" -e "INPUT_REPORT_SUITE_LOGS" -e "INPUT_DEDUPLICATE_CLASSES_BY_FILE_NAME" -e "INPUT_LARGE_FILES" -e "INPUT_IGNORE_RUNS" -e "INPUT_CHECK_RUN" -e "INPUT_JOB_SUMMARY" -e "INPUT_COMPARE_TO_EARLIER_COMMIT" -e "INPUT_PULL_REQUEST_BUILD" -e "INPUT_EVENT_FILE" -e "INPUT_EVENT_NAME" -e "INPUT_TEST_CHANGES_LIMIT" -e "INPUT_CHECK_RUN_ANNOTATIONS" -e "INPUT_CHECK_RUN_ANNOTATIONS_BRANCH" -e "INPUT_SECONDS_BETWEEN_GITHUB_READS" -e "INPUT_SECONDS_BETWEEN_GITHUB_WRITES" -e "INPUT_SECONDARY_RATE_LIMIT_WAIT_SECONDS" -e "INPUT_JSON_FILE" -e "INPUT_JSON_THOUSANDS_SEPARATOR" -e "INPUT_JSON_SUITE_DETAILS" -e "INPUT_JSON_TEST_CASE_RESULTS" -e "INPUT_SEARCH_PULL_REQUESTS" -e "HOME" -e "GITHUB_JOB" -e "GITHUB_REF" -e "GITHUB_SHA" -e "GITHUB_REPOSITORY" -e "GITHUB_REPOSITORY_OWNER" -e "GITHUB_REPOSITORY_OWNER_ID" -e "GITHUB_RUN_ID" -e "GITHUB_RUN_NUMBER" -e "GITHUB_RETENTION_DAYS" -e "GITHUB_RUN_ATTEMPT" -e "GITHUB_ACTOR_ID" -e "GITHUB_ACTOR" -e "GITHUB_WORKFLOW" -e "GITHUB_HEAD_REF" -e "GITHUB_BASE_REF" -e "GITHUB_EVENT_NAME" -e "GITHUB_SERVER_URL" -e "GITHUB_API_URL" -e "GITHUB_GRAPHQL_URL" -e "GITHUB_REF_NAME" -e "GITHUB_REF_PROTECTED" -e "GITHUB_REF_TYPE" -e "GITHUB_WORKFLOW_REF" -e "GITHUB_WORKFLOW_SHA" -e "GITHUB_REPOSITORY_ID" -e "GITHUB_TRIGGERING_ACTOR" -e "GITHUB_WORKSPACE" -e "GITHUB_ACTION" -e "GITHUB_EVENT_PATH" -e "GITHUB_ACTION_REPOSITORY" -e "GITHUB_ACTION_REF" -e "GITHUB_PATH" -e "GITHUB_ENV" -e "GITHUB_STEP_SUMMARY" -e "GITHUB_STATE" -e "GITHUB_OUTPUT" -e "RUNNER_OS" -e "RUNNER_ARCH" -e "RUNNER_NAME" -e "RUNNER_ENVIRONMENT" -e "RUNNER_TOOL_CACHE" -e "RUNNER_TEMP" -e "RUNNER_WORKSPACE" -e "ACTIONS_RUNTIME_URL" -e "ACTIONS_RUNTIME_TOKEN" -e "ACTIONS_CACHE_URL" -e "ACTIONS_RESULTS_URL" -e GITHUB_ACTIONS=true -e CI=true -v "/var/run/docker.sock":"/var/run/docker.sock" -v "/home/runner/work/_temp/_github_home":"/github/home" -v "/home/runner/work/_temp/_github_workflow":"/github/workflow" -v "/home/runner/work/_temp/_runner_file_commands":"/github/file_commands" -v "/home/runner/work/kapper/kapper":"/github/workspace" ghcr.io/enricomi/publish-unit-test-result-action:v2.19.0
244:  2025-04-23 20:15:00 +0000 - publish -  INFO - Available memory to read files: 14.5 GiB

@driessamyn driessamyn force-pushed the additional-db-support branch from dd0d9b4 to a591979 Compare April 23, 2025 20:28
@sonarqubecloud

Copy link
Copy Markdown

@driessamyn driessamyn merged commit f9ff155 into main Apr 23, 2025
@driessamyn driessamyn deleted the additional-db-support branch April 23, 2025 20:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants