Skip to content

Commit

Permalink
Merge pull request #8 from driessamyn/query-simple-implementation
Browse files Browse the repository at this point in the history
feat!: Basic implementation of the Query API.
  • Loading branch information
driessamyn authored Nov 11, 2024
2 parents b2dfa4e + e1f3341 commit da83d67
Show file tree
Hide file tree
Showing 14 changed files with 675 additions and 36 deletions.
4 changes: 3 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[versions]
junit-jupiter = "5.11.3"
kotest = "6.0.0.M1"
kotlin = "2.0.21"
mockito = "5.14.2"
mockk = "1.13.13"
mysql-driver = "8.0.33"
Expand All @@ -13,6 +14,7 @@ test-conatainers = "1.20.3"
[libraries]
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
kotlin-reflect = { module ="org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin"}
# mockito only to be used from Java code (API usability test)
mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
Expand All @@ -28,6 +30,6 @@ test-containers = ["test-cotainers-junit", "test-containers-junit-mysql", "test-
test-dbs = ["mysql-driver", "postgresql-driver"]

[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version = "2.0.21" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kover = { id = "org.jetbrains.kotlinx.kover", version = "0.7.3" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "12.1.1" }
2 changes: 2 additions & 0 deletions lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ tasks.check {
}

dependencies {
implementation(libs.kotlin.reflect)

testImplementation(libs.mockito)
testImplementation(libs.bundles.test)

Expand Down
12 changes: 3 additions & 9 deletions lib/src/integrationTest/kotlin/net/samyn/kapper/QueryTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Named.named
import org.junit.jupiter.api.Order
import org.junit.jupiter.api.TestInstance
Expand Down Expand Up @@ -91,8 +90,7 @@ class QueryTests {

@ParameterizedTest()
@MethodSource("databaseContainers")
@Disabled("TODO")
fun `should query all users`(container: JdbcDatabaseContainer<*>) {
fun `should query all heros`(container: JdbcDatabaseContainer<*>) {
createConnection(container).use { connection ->
val heroes = connection.query<SuperHero>("SELECT * FROM super_heroes")
heroes.shouldContainExactlyInAnyOrder(
Expand All @@ -105,8 +103,7 @@ class QueryTests {

@ParameterizedTest
@MethodSource("databaseContainers")
@Disabled("TODO")
fun `should query users with condition`(container: JdbcDatabaseContainer<*>) {
fun `should query heros with condition`(container: JdbcDatabaseContainer<*>) {
createConnection(container).use { connection ->
val heroes = connection.query<SuperHero>("SELECT * FROM super_heroes WHERE age > :age", "age" to 80)
heroes.shouldContainExactlyInAnyOrder(
Expand All @@ -118,13 +115,12 @@ class QueryTests {

@ParameterizedTest
@MethodSource("databaseContainers")
@Disabled("TODO")
fun `should query specific columns`(container: JdbcDatabaseContainer<*>) {
createConnection(container).use { connection ->
val heroes =
connection.query<SuperHero>(
"SELECT id, name FROM super_heroes WHERE name = :name",
"name" to superman,
"name" to superman.name,
)
heroes.shouldContainExactlyInAnyOrder(
SuperHero(superman.id, superman.name),
Expand All @@ -134,7 +130,6 @@ class QueryTests {

@ParameterizedTest
@MethodSource("databaseContainers")
@Disabled("TODO")
fun `should handle empty result set`(container: JdbcDatabaseContainer<*>) {
createConnection(container).use { connection ->
val heroes =
Expand All @@ -148,7 +143,6 @@ class QueryTests {

@ParameterizedTest
@MethodSource("databaseContainers")
@Disabled("TODO")
fun `should query with multiple conditions`(container: JdbcDatabaseContainer<*>) {
createConnection(container).use { connection ->
val heroes =
Expand Down
10 changes: 10 additions & 0 deletions lib/src/main/kotlin/net/samyn/kapper/Exceptions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package net.samyn.kapper

class KapperMappingException(message: String, cause: Throwable? = null) :
RuntimeException(message, cause)

class KapperUnsupportedOperationException(message: String, cause: Throwable? = null) :
RuntimeException(message, cause)

class KapperParseException(message: String, cause: Throwable? = null) :
RuntimeException(message, cause)
110 changes: 87 additions & 23 deletions lib/src/main/kotlin/net/samyn/kapper/Kapper.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
package net.samyn.kapper

import net.samyn.kapper.internal.KapperImpl
import java.sql.Connection
import kotlin.reflect.KClass

/**
* Execute a SQL statement and return the number of affected rows.
*
* @param sql The SQL statement to execute.
* @param args Optional parameters to be substituted in the SQL statement.
* @return The number of affected rows.
*/
fun Connection.execute(
sql: String,
vararg args: Any?,
): Int {
// Implementation to execute the SQL statement and return the number of affected rows
TODO()
val impl: Kapper by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
Kapper.getInstance()
}

/**
Expand Down Expand Up @@ -44,10 +34,7 @@ fun <T : Any> Connection.query(
clazz: KClass<T>,
sql: String,
vararg args: Pair<String, Any?>,
): List<T> {
// Implementation to execute the SQL query and map the results to a list of Kotlin data class instances
TODO()
}
): List<T> = impl.query(clazz.java, this, sql, args.toMap())

/**
* Execute a SQL query and map the result to a single Kotlin data class instance.
Expand Down Expand Up @@ -76,16 +63,48 @@ fun <T : Any> Connection.querySingle(
clazz: KClass<T>,
sql: String,
vararg args: Pair<String, Any?>,
): T {
// Implementation to execute the SQL query and map the single result to a Kotlin data class instance
TODO()
): T = impl.querySingle(clazz.java, this, sql, args.toMap())

/**
* Execute a SQL statement and return the number of affected rows.
*
* @param sql The SQL statement to execute.
* @param args Optional parameters to be substituted in the SQL statement.
* @return The number of affected rows.
*/
fun Connection.execute(
sql: String,
vararg args: Pair<String, Any?>,
): Int {
return impl.execute(this, sql, args.toMap())
}

/**
* Kapper API interface for executing SQL statements and queries.
* Used in cases where the extension methods cannot be used or ar not preferred.
*/
interface Kapper {
companion object {
@JvmStatic
fun getInstance(): Kapper = KapperImpl()
}

/**
* Execute a SQL query and map the results to a list of instances of the specified class.
*
* @param clazz The class to map the results to.
* @param connection The SQL connection to use.
* @param sql The SQL query to execute.
* @param args Optional parameters to be substituted in the SQL query.
* @return A list of Kotlin data class instances.
*/
fun <T : Any> query(
clazz: Class<T>,
connection: Connection,
sql: String,
args: Map<String, Any?>,
): List<T>

/**
* Execute a SQL query and map the results to a list of instances of the specified class.
*
Expand All @@ -99,19 +118,64 @@ interface Kapper {
clazz: Class<T>,
connection: Connection,
sql: String,
args: java.util.Map<String, Any?>,
args: java.util.HashMap<String, Any?>,
): List<T>

/**
* Execute a SQL query that returns a signle row and map the result to an instance of the specified class.
*
* @param clazz The class to map the result to.
* @param connection The SQL connection to use.
* @param sql The SQL query to execute.
* @param args Optional parameters to be substituted in the SQL query.
*/
fun <T : Any> querySingle(
clazz: Class<T>,
connection: Connection,
sql: String,
args: java.util.HashMap<String, Any?>,
): T

/**
* Execute a SQL query that returns a signle row and map the result to an instance of the specified class.
*
* @param clazz The class to map the result to.
* @param connection The SQL connection to use.
* @param sql The SQL query to execute.
* @param args Optional parameters to be substituted in the SQL query.
*/
fun <T : Any> querySingle(
clazz: Class<T>,
connection: Connection,
sql: String,
args: java.util.Map<String, Any?>,
args: Map<String, Any?>,
): T

/**
* Execute a SQL statement that does not return a result set.
*
* @param connection The SQL connection to use.
* @param sql The SQL statement to execute.
* @param args Optional parameters to be substituted in the SQL statement.
* @return The number of rows affected by the statement.
*/
fun execute(
connection: Connection,
sql: String,
args: java.util.HashMap<String, Any?>,
): Int

/**
* Execute a SQL statement that does not return a result set.
*
* @param connection The SQL connection to use.
* @param sql The SQL statement to execute.
* @param args Optional parameters to be substituted in the SQL statement.
* @return The number of rows affected by the statement.
*/
fun execute(
connection: Connection,
sql: String,
args: java.util.Map<String, Any?>,
args: Map<String, Any?>,
): Int
}
38 changes: 38 additions & 0 deletions lib/src/main/kotlin/net/samyn/kapper/internal/AutoConverter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package net.samyn.kapper.internal

import net.samyn.kapper.KapperUnsupportedOperationException
import java.nio.ByteBuffer
import java.util.UUID
import kotlin.reflect.KClass

object AutoConverter {
fun convert(
value: Any,
target: KClass<*>,
): Any {
val converted =
when (target) {
UUID::class -> {
return if (value is String) {
UUID.fromString(value)
} else if (value is ByteArray) {
value.asUUID()
} else {
throw KapperUnsupportedOperationException(
"Cannot auto-convert from ${value.javaClass} to ${target::class.java}",
)
}
}
else ->
throw KapperUnsupportedOperationException(
"Cannot auto-convert from ${value.javaClass} to ${target::class.java}",
)
}
return converted
}

private fun ByteArray.asUUID(): UUID {
val b = ByteBuffer.wrap(this)
return UUID(b.getLong(), b.getLong())
}
}
Loading

0 comments on commit da83d67

Please sign in to comment.