commit c62a6ea1b16657bf410c10baf52d7aeb5e790134 parent 2cce8ae13da4e1592bf83ffca37afc64f26fcf8f Author: Antoine A <> Date: Mon, 1 Jul 2024 13:54:39 +0200 common: clean and document Diffstat:
19 files changed, 72 insertions(+), 78 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt @@ -485,7 +485,7 @@ data class BankWithdrawalOperationStatus( val selected_reserve_pub: EddsaPublicKey? = null, val selected_exchange_account: String? = null, val currency: String? = null, - // TODO remove + // TODO deprecated remove in the next breaking release val aborted: Boolean, val selection_done: Boolean, val transfer_done: Boolean, @@ -509,7 +509,7 @@ data class BankWithdrawalOperationPostRequest( data class BankWithdrawalOperationPostResponse( val status: WithdrawalStatus, val confirm_transfer_url: String? = null, - // TODO remove + // TODO deprecated remove in the next breaking release val transfer_done: Boolean, ) diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt @@ -197,7 +197,7 @@ class AccountDAO(private val db: Database) { suspend fun delete( login: String, is2fa: Boolean - ): AccountDeletionResult = db.serializableWrite( + ): AccountDeletionResult = db.serializable( """ SELECT out_not_found, @@ -439,7 +439,7 @@ class AccountDAO(private val db: Database) { } /** Get password hash of account [login] */ - suspend fun passwordHash(login: String): String? = db.serializableRead( + suspend fun passwordHash(login: String): String? = db.serializable( "SELECT password_hash FROM customers WHERE login=? AND deleted_at IS NULL" ) { setString(1, login) @@ -449,7 +449,7 @@ class AccountDAO(private val db: Database) { } /** Get bank info of account [login] */ - suspend fun bankInfo(login: String, ctx: BankPaytoCtx): BankInfo? = db.serializableRead( + suspend fun bankInfo(login: String, ctx: BankPaytoCtx): BankInfo? = db.serializable( """ SELECT bank_account_id @@ -472,7 +472,7 @@ class AccountDAO(private val db: Database) { } /** Get data of account [login] */ - suspend fun get(login: String, ctx: BankPaytoCtx): AccountData? = db.serializableRead( + suspend fun get(login: String, ctx: BankPaytoCtx): AccountData? = db.serializable( """ SELECT name diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt @@ -48,7 +48,7 @@ class CashoutDAO(private val db: Database) { subject: String, timestamp: Instant, is2fa: Boolean - ): CashoutCreationResult = db.serializableWrite( + ): CashoutCreationResult = db.serializable( """ SELECT out_bad_conversion, @@ -88,7 +88,7 @@ class CashoutDAO(private val db: Database) { } /** Get status of cashout operation [id] owned by [login] */ - suspend fun get(id: Long, login: String): CashoutStatusResponse? = db.serializableRead( + suspend fun get(id: Long, login: String): CashoutStatusResponse? = db.serializable( """ SELECT (amount_debit).val as amount_debit_val diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/ConversionDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/ConversionDAO.kt @@ -100,7 +100,7 @@ class ConversionDAO(private val db: Database) { } /** Clear in-db conversion config */ - suspend fun clearConfig() = db.serializableWrite( + suspend fun clearConfig() = db.serializable( "DELETE FROM config WHERE key LIKE 'cashin%' OR key like 'cashout%'" ) { executeUpdate() @@ -114,7 +114,7 @@ class ConversionDAO(private val db: Database) { } /** Perform [direction] conversion of [amount] using in-db [function] */ - private suspend fun conversion(amount: TalerAmount, direction: String, function: String): ConversionResult = db.serializableRead( + private suspend fun conversion(amount: TalerAmount, direction: String, function: String): ConversionResult = db.serializable( "SELECT too_small, no_config, (converted).val AS amount_val, (converted).frac AS amount_frac FROM $function((?, ?)::taler_amount, ?, (0, 0)::taler_amount)" ) { setLong(1, amount.value) diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt @@ -105,7 +105,7 @@ class Database(dbConfig: DatabaseConfig, internal val bankCurrency: String, inte suspend fun monitor( params: MonitorParams - ): MonitorResponse = serializableRead( + ): MonitorResponse = serializable( """ SELECT cashin_count diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt @@ -102,7 +102,7 @@ class ExchangeDAO(private val db: Database) { req: TransferRequest, login: String, timestamp: Instant - ): TransferResult = db.serializableWrite( + ): TransferResult = db.serializable( """ SELECT out_debtor_not_found @@ -166,7 +166,7 @@ class ExchangeDAO(private val db: Database) { req: AddIncomingRequest, login: String, timestamp: Instant - ): AddIncomingResult = db.serializableWrite( + ): AddIncomingResult = db.serializable( """ SELECT out_creditor_not_found diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt @@ -42,7 +42,7 @@ class TanDAO(private val db: Database) { validityPeriod: Duration, channel: TanChannel? = null, info: String? = null - ): Long = db.serializableWrite( + ): Long = db.serializable( "SELECT tan_challenge_create(?,?::op_enum,?,?,?,?,?,?::tan_enum,?)" ) { setString(1, body) @@ -73,7 +73,7 @@ class TanDAO(private val db: Database) { timestamp: Instant, retryCounter: Int, validityPeriod: Duration - ) = db.serializableWrite( + ) = db.serializable( "SELECT out_no_op, out_tan_code, out_tan_channel, out_tan_info FROM tan_challenge_send(?,?,?,?,?,?)" ) { setLong(1, id) @@ -99,7 +99,7 @@ class TanDAO(private val db: Database) { id: Long, timestamp: Instant, retransmissionPeriod: Duration - ) = db.serializableWrite( + ) = db.serializable( "SELECT tan_challenge_mark_sent(?,?,?)" ) { setLong(1, id) @@ -123,7 +123,7 @@ class TanDAO(private val db: Database) { login: String, code: String, timestamp: Instant - ) = db.serializableWrite( + ) = db.serializable( """ SELECT out_ok, out_no_op, out_no_retry, out_expired, @@ -162,7 +162,7 @@ class TanDAO(private val db: Database) { id: Long, login: String, op: Operation - ) = db.serializableWrite( + ) = db.serializable( """ SELECT body, tan_challenges.tan_channel, tan_info FROM tan_challenges diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt @@ -42,7 +42,7 @@ class TokenDAO(private val db: Database) { scope: TokenScope, isRefreshable: Boolean, description: String? - ): Boolean = db.serializableWrite( + ): Boolean = db.serializable( """ INSERT INTO bearer_tokens ( content, @@ -72,7 +72,7 @@ class TokenDAO(private val db: Database) { } /** Get info for [token] */ - suspend fun access(token: ByteArray, accessTime: Instant): BearerToken? = db.serializableWrite( + suspend fun access(token: ByteArray, accessTime: Instant): BearerToken? = db.serializable( """ UPDATE bearer_tokens SET last_access=? @@ -100,7 +100,7 @@ class TokenDAO(private val db: Database) { } /** Delete token [token] */ - suspend fun delete(token: ByteArray) = db.serializableWrite( + suspend fun delete(token: ByteArray) = db.serializable( "DELETE FROM bearer_tokens WHERE content = ?" ) { setBytes(1, token) diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt @@ -158,7 +158,7 @@ class TransactionDAO(private val db: Database) { } /** Get transaction [rowId] owned by [login] */ - suspend fun get(rowId: Long, login: String, ctx: BankPaytoCtx): BankAccountTransactionInfo? = db.serializableRead( + suspend fun get(rowId: Long, login: String, ctx: BankPaytoCtx): BankAccountTransactionInfo? = db.serializable( """ SELECT creditor_payto_uri diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt @@ -50,7 +50,7 @@ class WithdrawalDAO(private val db: Database) { wireTransferFees: TalerAmount, minAmount: TalerAmount, maxAmount: TalerAmount - ): WithdrawalCreationResult = db.serializableWrite( + ): WithdrawalCreationResult = db.serializable( """ SELECT out_account_not_found, @@ -97,7 +97,7 @@ class WithdrawalDAO(private val db: Database) { } /** Abort withdrawal operation [uuid] */ - suspend fun abort(uuid: UUID): AbortResult = db.serializableWrite( + suspend fun abort(uuid: UUID): AbortResult = db.serializable( """ SELECT out_no_op, @@ -138,7 +138,7 @@ class WithdrawalDAO(private val db: Database) { wireTransferFees: TalerAmount, minAmount: TalerAmount, maxAmount: TalerAmount - ): WithdrawalSelectionResult = db.serializableWrite( + ): WithdrawalSelectionResult = db.serializable( """ SELECT out_no_op, @@ -211,7 +211,7 @@ class WithdrawalDAO(private val db: Database) { wireTransferFees: TalerAmount, minAmount: TalerAmount, maxAmount: TalerAmount - ): WithdrawalConfirmationResult = db.serializableWrite( + ): WithdrawalConfirmationResult = db.serializable( """ SELECT out_no_op, @@ -249,7 +249,7 @@ class WithdrawalDAO(private val db: Database) { } /** Get withdrawal operation [uuid] linked account username */ - suspend fun getUsername(uuid: UUID): String? = db.serializableRead( + suspend fun getUsername(uuid: UUID): String? = db.serializable( """ SELECT login FROM taler_withdrawal_operations @@ -297,7 +297,7 @@ class WithdrawalDAO(private val db: Database) { /** Pool public info of operation [uuid] */ suspend fun pollInfo(uuid: UUID, params: StatusParams): WithdrawalPublicInfo? = poll(uuid, params, status = { it.status }) { - db.serializableRead( + db.serializable( """ SELECT CASE @@ -344,7 +344,7 @@ class WithdrawalDAO(private val db: Database) { maxAmount: TalerAmount ): BankWithdrawalOperationStatus? = poll(uuid, params, status = { it.status }) { - db.serializableRead( + db.serializable( """ SELECT CASE diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -1451,7 +1451,7 @@ class CoreBankWithdrawalApiTest { suspend fun run(amount: TalerAmount): HttpResponse { val uuid = UUID.randomUUID() // Create a selected withdrawal directly in the database to bypass checks - db.serializableWrite(""" + db.serializable(""" INSERT INTO taler_withdrawal_operations(withdrawal_uuid,amount,selected_exchange_payto,selection_done,wallet_bank_account,creation_date) VALUES (?, (?, ?)::taler_amount, ?, true, 3, 0) """) { diff --git a/bank/src/test/kotlin/bench.kt b/bank/src/test/kotlin/bench.kt @@ -35,11 +35,14 @@ import kotlin.time.Duration.* import java.util.UUID import java.time.* import kotlin.random.Random +import kotlin.math.min class Bench { /** Generate [amount] rows to fill the database */ fun genData(conn: PgConnection, amount: Int) { + val amount = max(amount, 10) + // Skip 4 accounts created by bankSetup val skipAccount = 4 // Customer account will be used in tests so we want to generate more data for him @@ -48,7 +51,6 @@ class Bench { // In general half of the data is for generated account and half is for customer val mid = amount / 2 - val copyManager = conn.getCopyAPI() val password = PwCrypto.hashpw("password") fun gen(table: String, lambda: (Int) -> String) { diff --git a/common/src/main/kotlin/AnsiColor.kt b/common/src/main/kotlin/AnsiColor.kt @@ -33,27 +33,22 @@ object ANSI { WHITE(7) } - fun espacedSize(msg: String): Int { - // TODO remove allocation ? - val clean = msg.replace(ANSI_PATTERN, "") - return clean.length - } + /** Compute [msg] length without ANSI escape sequences */ + fun CharSequence.displayLength(): Int = + splitToSequence(ANSI_PATTERN).sumOf { it.length } + /** Format a [msg] using optionals [fg] and [bg] colors and optionaly make the text [bold] */ fun fmt(msg: String, fg: Color? = null, bg: Color? = null, bold: Boolean = false): String { - if (fg == null && bg == null && !bold) { - return msg - } + if (fg == null && bg == null && !bold) return msg return buildString { fun next() { - val c = last() - if (c != '[' && c != ';') { + if (last() != '[') { append(';') } } append("\u001b[") if (bold) { - next() append('1') } if (fg != null) { diff --git a/common/src/main/kotlin/Table.kt b/common/src/main/kotlin/Table.kt @@ -20,6 +20,7 @@ package tech.libeufin.common import kotlin.math.max +import tech.libeufin.common.ANSI.displayLength data class ColumnStyle( val alignLeft: Boolean = true @@ -37,13 +38,13 @@ fun printTable( ) { val cols: List<Pair<String, Int>> = columns.mapIndexed { i, name -> val maxRow: Int = rows.asSequence().map { - ANSI.espacedSize(it[i]) + it[i].displayLength() }.maxOrNull() ?: 0 - Pair(name, max(ANSI.espacedSize(name), maxRow)) + Pair(name, max(name.displayLength(), maxRow)) } val table = buildString { fun padding(length: Int) { - repeat(length) { append (" ") } + repeat(length) { append (' ') } } var first = true for ((name, len) in cols) { @@ -52,12 +53,12 @@ fun printTable( } else { first = false } - val pad = len - ANSI.espacedSize(name) + val pad = len - name.displayLength() padding(pad / 2) append(name) padding(pad / 2 + if (pad % 2 == 0) { 0 } else { 1 }) } - append("\n") + append('\n') for (row in rows) { var first = true cols.forEachIndexed { i, met -> @@ -69,7 +70,7 @@ fun printTable( first = false } val (name, len) = met - val pad = len - ANSI.espacedSize(str) + val pad = len - str.displayLength() if (style.alignLeft) { append(str) padding(pad) @@ -78,7 +79,7 @@ fun printTable( append(str) } } - append("\n") + append('\n') } } print(table) diff --git a/common/src/main/kotlin/db/DbPool.kt b/common/src/main/kotlin/db/DbPool.kt @@ -50,17 +50,13 @@ open class DbPool(cfg: DatabaseConfig, schema: String) : java.io.Closeable { } } - /** Executes a read-only query with automatic retry on serialization errors */ - suspend fun <R> serializableRead(query: String, lambda: PreparedStatement.() -> R): R = conn { conn -> - conn.withStatement(query) { - // TODO explicit read only for better perf ? - retrySerializationError { lambda() } - } - - } - /** Executes a query with automatic retry on serialization errors */ - suspend fun <R> serializableWrite(query: String, lambda: PreparedStatement.() -> R): R = conn { conn -> + suspend fun <R> serializable(query: String, lambda: PreparedStatement.() -> R): R = conn { conn -> + // We could explicitly tell Postgres when a request is read-only, + // but the performance improvement isn't obvious, it doesn't prevent + // stored procedures from modifying the database and it adds a + // round-trip during configuration + conn.withStatement(query) { retrySerializationError { lambda() } } diff --git a/common/src/main/kotlin/db/helpers.kt b/common/src/main/kotlin/db/helpers.kt @@ -45,7 +45,7 @@ suspend fun <T> DbPool.page( ORDER BY $idName ${if (backward) "DESC" else "ASC"} LIMIT ? """ - return serializableRead(pageQuery) { + return serializable(pageQuery) { val pad = bind() setLong(pad + 1, params.start) setInt(pad + 2, abs(params.delta)) diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/ExchangeDAO.kt @@ -92,7 +92,7 @@ class ExchangeDAO(private val db: Database) { req: TransferRequest, bankId: String, timestamp: Instant - ): TransferResult = db.serializableWrite( + ): TransferResult = db.serializable( """ SELECT out_request_uid_reuse diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt @@ -38,7 +38,7 @@ class InitiatedDAO(private val db: Database) { } /** Register a new pending payment in the database */ - suspend fun create(paymentData: InitiatedPayment): PaymentInitiationResult = db.serializableWrite( + suspend fun create(paymentData: InitiatedPayment): PaymentInitiationResult = db.serializable( """ INSERT INTO initiated_outgoing_transactions ( amount @@ -67,7 +67,7 @@ class InitiatedDAO(private val db: Database) { id: Long, timestamp: Instant, orderId: String - ) = db.serializableWrite( + ) = db.serializable( """ UPDATE initiated_outgoing_transactions SET submitted = 'success'::submission_state @@ -89,7 +89,7 @@ class InitiatedDAO(private val db: Database) { id: Long, timestamp: Instant, msg: String? - ) = db.serializableWrite( + ) = db.serializable( """ UPDATE initiated_outgoing_transactions SET submitted = 'transient_failure'::submission_state @@ -106,7 +106,7 @@ class InitiatedDAO(private val db: Database) { } /** Register EBICS log status message */ - suspend fun logMessage(orderId: String, msg: String) = db.serializableWrite( + suspend fun logMessage(orderId: String, msg: String) = db.serializable( """ UPDATE initiated_outgoing_transactions SET failure_message = ? WHERE order_id = ? @@ -118,7 +118,7 @@ class InitiatedDAO(private val db: Database) { } /** Register EBICS log success and return request_uid if found */ - suspend fun logSuccess(orderId: String): String? = db.serializableWrite( + suspend fun logSuccess(orderId: String): String? = db.serializable( """ SELECT request_uid FROM initiated_outgoing_transactions WHERE order_id = ? @@ -129,7 +129,7 @@ class InitiatedDAO(private val db: Database) { } /** Register EBICS log failure and return request_uid and previous message if found */ - suspend fun logFailure(orderId: String): Pair<String, String?>? = db.serializableWrite( + suspend fun logFailure(orderId: String): Pair<String, String?>? = db.serializable( """ UPDATE initiated_outgoing_transactions SET submitted = 'permanent_failure'::submission_state @@ -142,7 +142,7 @@ class InitiatedDAO(private val db: Database) { } /** Register bank status message */ - suspend fun bankMessage(requestUID: String, msg: String) = db.serializableWrite( + suspend fun bankMessage(requestUID: String, msg: String) = db.serializable( """ UPDATE initiated_outgoing_transactions SET failure_message = ? @@ -155,7 +155,7 @@ class InitiatedDAO(private val db: Database) { } /** Register bank failure */ - suspend fun bankFailure(requestUID: String, msg: String) = db.serializableWrite( + suspend fun bankFailure(requestUID: String, msg: String) = db.serializable( """ UPDATE initiated_outgoing_transactions SET submitted = 'permanent_failure'::submission_state @@ -169,7 +169,7 @@ class InitiatedDAO(private val db: Database) { } /** Register reversal */ - suspend fun reversal(requestUID: String, msg: String) = db.serializableWrite( + suspend fun reversal(requestUID: String, msg: String) = db.serializable( """ UPDATE initiated_outgoing_transactions SET submitted = 'permanent_failure'::submission_state @@ -203,7 +203,7 @@ class InitiatedDAO(private val db: Database) { // Then we retry the failed transaction, starting with the oldest by submission time. // This the bad path retrying each failed transaction applying a rotation based on // resubmission time. - return db.serializableRead( + return db.serializable( """ ($selectPart WHERE submitted='unsubmitted' ORDER BY initiation_time) UNION ALL diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt @@ -39,7 +39,7 @@ class PaymentDAO(private val db: Database) { paymentData: OutgoingPayment, wtid: ShortHashCode?, baseUrl: ExchangeUrl?, - ): OutgoingRegistrationResult = db.serializableWrite( + ): OutgoingRegistrationResult = db.serializable( """ SELECT out_tx_id, out_initiated, out_found FROM register_outgoing((?,?)::taler_amount,?,?,?,?,?,?) @@ -77,7 +77,7 @@ class PaymentDAO(private val db: Database) { paymentData: IncomingPayment, bounceAmount: TalerAmount, timestamp: Instant - ): IncomingBounceRegistrationResult = db.serializableWrite( + ): IncomingBounceRegistrationResult = db.serializable( """ SELECT out_found, out_tx_id, out_bounce_id FROM register_incoming_and_bounce((?,?)::taler_amount,?,?,?,?,(?,?)::taler_amount,?) @@ -111,7 +111,7 @@ class PaymentDAO(private val db: Database) { suspend fun registerTalerableIncoming( paymentData: IncomingPayment, reservePub: EddsaPublicKey - ): IncomingRegistrationResult = db.serializableWrite( + ): IncomingRegistrationResult = db.serializable( """ SELECT out_reserve_pub_reuse, out_found, out_tx_id FROM register_incoming_and_talerable((?,?)::taler_amount,?,?,?,?,?) @@ -139,7 +139,7 @@ class PaymentDAO(private val db: Database) { /** Register an incoming payment */ suspend fun registerIncoming( paymentData: IncomingPayment - ): IncomingRegistrationResult.Success = db.serializableWrite( + ): IncomingRegistrationResult.Success = db.serializable( """ SELECT out_found, out_tx_id FROM register_incoming((?,?)::taler_amount,?,?,?,?) @@ -184,7 +184,7 @@ class PaymentDAO(private val db: Database) { } /** List incoming transaction metadata for debugging */ - suspend fun metadataIncoming(): List<IncomingTxMetadata> = db.serializableRead( + suspend fun metadataIncoming(): List<IncomingTxMetadata> = db.serializable( """ SELECT (amount).val as amount_val @@ -212,7 +212,7 @@ class PaymentDAO(private val db: Database) { } /** List outgoing transaction metadata for debugging */ - suspend fun metadataOutgoing(): List<OutgoingTxMetadata> = db.serializableRead( + suspend fun metadataOutgoing(): List<OutgoingTxMetadata> = db.serializable( """ SELECT (amount).val as amount_val @@ -242,7 +242,7 @@ class PaymentDAO(private val db: Database) { } /** List initiated transaction metadata for debugging */ - suspend fun metadataInitiated(): List<InitiatedTxMetadata> = db.serializableRead( + suspend fun metadataInitiated(): List<InitiatedTxMetadata> = db.serializable( """ SELECT (amount).val as amount_val