DbPool.kt (3122B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2024-2025 Taler Systems S.A. 4 * 5 * LibEuFin is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation; either version 3, or 8 * (at your option) any later version. 9 * 10 * LibEuFin is distributed in the hope that it will be useful, but 11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General 13 * Public License for more details. 14 * 15 * You should have received a copy of the GNU Affero General Public 16 * License along with LibEuFin; see the file COPYING. If not, see 17 * <http://www.gnu.org/licenses/> 18 */ 19 20 package tech.libeufin.common.db 21 22 import com.zaxxer.hikari.HikariConfig 23 import com.zaxxer.hikari.HikariDataSource 24 import kotlinx.coroutines.Dispatchers 25 import kotlinx.coroutines.withContext 26 import org.postgresql.jdbc.PgConnection 27 import tech.libeufin.common.MIN_VERSION 28 import java.sql.PreparedStatement 29 30 open class DbPool(cfg: DatabaseConfig, schema: String) : java.io.Closeable { 31 val pgSource = pgDataSource(cfg.dbConnStr) 32 private val pool: HikariDataSource 33 34 init { 35 val config = HikariConfig() 36 config.dataSource = pgSource 37 config.schema = schema.replace("-", "_") 38 config.transactionIsolation = "TRANSACTION_SERIALIZABLE" 39 pool = HikariDataSource(config) 40 pool.connection.use { con -> 41 val meta = con.metaData 42 val majorVersion = meta.databaseMajorVersion 43 val minorVersion = meta.databaseMinorVersion 44 require(majorVersion >= MIN_VERSION) { 45 "postgres version must be at least $MIN_VERSION.0 got $majorVersion.$minorVersion" 46 } 47 checkMigrations(con.unwrap(PgConnection::class.java), cfg, schema) 48 } 49 } 50 51 /** Executes a query with automatic retry on serialization errors */ 52 suspend fun <R> serializable(query: String, lambda: TalerStatement.() -> R): R = conn { conn -> 53 // We could explicitly tell Postgres when a request is read-only, 54 // but the performance improvement isn't obvious, it doesn't prevent 55 // stored procedures from modifying the database and it adds a 56 // round-trip during configuration 57 58 conn.withStatement(query) { 59 retrySerializationError { lambda() } 60 } 61 } 62 63 /** Executes a transaction with automatic retry on serialization errors */ 64 suspend fun <R> serializableTransaction(transaction: (PgConnection) -> R): R = conn { conn -> 65 retrySerializationError { 66 conn.transaction(transaction) 67 } 68 } 69 70 /** Run db logic using a connection from the pool */ 71 suspend fun <R> conn(lambda: suspend (PgConnection) -> R): R { 72 // Use a coroutine dispatcher that we can block as JDBC API is blocking 73 return withContext(Dispatchers.IO) { 74 pool.connection.use { lambda(it.unwrap(PgConnection::class.java)) } 75 } 76 } 77 78 override fun close() { 79 pool.close() 80 } 81 }