abstract db connection

This commit is contained in:
Maxime Duchêne-Savard 2026-07-28 16:11:04 -04:00
parent ca62140e6f
commit e2cf9c04b5
3 changed files with 79 additions and 62 deletions

View File

@ -2,11 +2,39 @@ package dev.mduchene.bolts.persistence
import java.sql.Connection import java.sql.Connection
import java.sql.DriverManager import java.sql.DriverManager
import java.sql.PreparedStatement
import java.sql.ResultSet
class Database(private val config: DatabaseConfig) { class Database(private val config: DatabaseConfig) {
fun getConnection(): Connection = fun getConnection(): Connection =
DriverManager.getConnection(config.url, config.username, config.password) DriverManager.getConnection(config.url, config.username, config.password)
fun executeUpdate(
sql: String,
bind: PreparedStatement.() -> Unit = {},
): Int = withPreparedStatement(sql) {
bind()
executeUpdate()
}
fun <T> queryOne(
sql: String,
bind: PreparedStatement.() -> Unit = {},
map: ResultSet.() -> T,
): T? = query(sql, bind) {
if (next()) map() else null
}
fun <T> queryList(
sql: String,
bind: PreparedStatement.() -> Unit = {},
map: ResultSet.() -> T,
): List<T> = query(sql, bind) {
buildList {
while (next()) add(map())
}
}
fun initialize() { fun initialize() {
getConnection().use { connection -> getConnection().use { connection ->
connection.createStatement().use { statement -> connection.createStatement().use { statement ->
@ -17,6 +45,22 @@ class Database(private val config: DatabaseConfig) {
} }
} }
private fun <T> query(
sql: String,
bind: PreparedStatement.() -> Unit,
read: ResultSet.() -> T,
): T = withPreparedStatement(sql) {
bind()
executeQuery().use { result -> result.read() }
}
private fun <T> withPreparedStatement(
sql: String,
action: PreparedStatement.() -> T,
): T = getConnection().use { connection ->
connection.prepareStatement(sql).use { statement -> statement.action() }
}
private companion object { private companion object {
const val CREATE_USERS_TABLE = """ const val CREATE_USERS_TABLE = """
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (

View File

@ -1,18 +1,14 @@
package dev.mduchene.bolts.user package dev.mduchene.bolts.user
import dev.mduchene.bolts.persistence.Database import dev.mduchene.bolts.persistence.Database
import java.sql.ResultSet
class SessionRepository(private val database: Database) { class SessionRepository(private val database: Database) {
fun create(token: String, userId: Long) { fun create(token: String, userId: Long) {
val sql = "INSERT INTO sessions (token, user_id) VALUES (?, ?)" val sql = "INSERT INTO sessions (token, user_id) VALUES (?, ?)"
database.getConnection().use { connection -> database.executeUpdate(sql) {
connection.prepareStatement(sql).use { statement -> setString(1, token)
statement.setString(1, token) setLong(2, userId)
statement.setLong(2, userId)
statement.executeUpdate()
}
} }
} }
@ -24,20 +20,10 @@ class SessionRepository(private val database: Database) {
WHERE sessions.token = ? WHERE sessions.token = ?
""".trimIndent() """.trimIndent()
database.getConnection().use { connection -> return database.queryOne(
connection.prepareStatement(sql).use { statement -> sql,
statement.setString(1, token) bind = { setString(1, token) },
statement.executeQuery().use { result -> map = { toUser() },
return if (result.next()) result.toUser() else null )
}
}
}
} }
private fun ResultSet.toUser() = User(
id = getLong("id"),
username = getString("username"),
passwordHash = getString("password_hash"),
role = getString("role"),
)
} }

View File

@ -11,17 +11,17 @@ class UserRepository(private val database: Database) {
RETURNING id, username, password_hash, role RETURNING id, username, password_hash, role
""".trimIndent() """.trimIndent()
database.getConnection().use { connection -> return checkNotNull(
connection.prepareStatement(sql).use { statement -> database.queryOne(
statement.setString(1, username) sql,
statement.setString(2, passwordHash) bind = {
statement.setString(3, role) setString(1, username)
statement.executeQuery().use { result -> setString(2, passwordHash)
check(result.next()) { "User insert returned no row" } setString(3, role)
return result.toUser() },
} map = ResultSet::toUser,
} ),
} ) { "User insert returned no row" }
} }
fun findByUsername(username: String): User? { fun findByUsername(username: String): User? {
@ -31,45 +31,32 @@ class UserRepository(private val database: Database) {
WHERE username = ? WHERE username = ?
""".trimIndent() """.trimIndent()
database.getConnection().use { connection -> return database.queryOne(
connection.prepareStatement(sql).use { statement -> sql,
statement.setString(1, username) bind = { setString(1, username) },
statement.executeQuery().use { result -> map = ResultSet::toUser,
return if (result.next()) result.toUser() else null )
}
}
}
} }
fun findAll(): List<User> { fun findAll(): List<User> {
val sql = "SELECT id, username, password_hash, role FROM users ORDER BY username" val sql = "SELECT id, username, password_hash, role FROM users ORDER BY username"
database.getConnection().use { connection -> return database.queryList(sql, map = ResultSet::toUser)
connection.prepareStatement(sql).use { statement ->
statement.executeQuery().use { result ->
return buildList {
while (result.next()) add(result.toUser())
}
}
}
}
} }
fun updateRole(id: Long, role: String) { fun updateRole(id: Long, role: String) {
val sql = "UPDATE users SET role = ? WHERE id = ?" val sql = "UPDATE users SET role = ? WHERE id = ?"
database.getConnection().use { connection -> database.executeUpdate(sql) {
connection.prepareStatement(sql).use { statement -> setString(1, role)
statement.setString(1, role) setLong(2, id)
statement.setLong(2, id)
statement.executeUpdate()
}
} }
} }
private fun ResultSet.toUser() = User(
id = getLong("id"),
username = getString("username"),
passwordHash = getString("password_hash"),
role = getString("role"),
)
} }
internal fun ResultSet.toUser() = User(
id = getLong("id"),
username = getString("username"),
passwordHash = getString("password_hash"),
role = getString("role"),
)