schema.kt (4384B)
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 org.postgresql.ds.* 23 import org.postgresql.jdbc.PgConnection 24 import java.sql.Connection 25 import kotlin.io.path.Path 26 import kotlin.io.path.exists 27 import kotlin.io.path.readText 28 29 /** 30 * Only runs versioning.sql if the _v schema is not found. 31 * 32 * @param conn database connection 33 * @param cfg database configuration 34 */ 35 private fun maybeApplyV(conn: PgConnection, cfg: DatabaseConfig) { 36 conn.transaction { 37 val checkVSchema = conn.talerStatement( 38 "SELECT schema_name FROM information_schema.schemata WHERE schema_name = '_v'" 39 ) 40 if (!checkVSchema.executeQueryCheck()) { 41 logger.debug("_v schema not found, applying versioning.sql") 42 val sqlVersioning = Path("${cfg.sqlDir}/versioning.sql").readText() 43 conn.execSQLUpdate(sqlVersioning) 44 } 45 } 46 } 47 48 private fun migrationsPath(sqlFilePrefix: String): Sequence<String> = sequence { 49 for (n in 1..9999) { 50 val padded = n.toString().padStart(4, '0') 51 yield("$sqlFilePrefix-$padded") 52 } 53 } 54 55 // sqlFilePrefix is, for example, "libeufin-bank" or "libeufin-nexus" (no trailing dash). 56 private fun initializeDatabaseTables(conn: PgConnection, cfg: DatabaseConfig, sqlFilePrefix: String) { 57 logger.info("doing DB initialization, sqldir ${cfg.sqlDir}") 58 maybeApplyV(conn, cfg) 59 conn.transaction { 60 val checkStmt = conn.talerStatement("SELECT EXISTS(SELECT FROM _v.patches where patch_name = ?)") 61 62 for (patchName in migrationsPath(sqlFilePrefix)) { 63 checkStmt.bind(patchName) 64 val applied = checkStmt.one { it.getBoolean(1) } 65 if (applied) { 66 logger.debug("patch $patchName already applied") 67 continue 68 } 69 70 val path = Path("${cfg.sqlDir}/$patchName.sql") 71 if (!path.exists()) { 72 logger.debug("path {} doesn't exist anymore, stopping", path) 73 break 74 } 75 logger.info("applying patch $path") 76 conn.execSQLUpdate(path.readText()) 77 } 78 val sqlProcedures = Path("${cfg.sqlDir}/$sqlFilePrefix-procedures.sql") 79 if (!sqlProcedures.exists()) { 80 logger.warn("no procedures.sql for the SQL collection: $sqlFilePrefix") 81 return@transaction 82 } 83 logger.info("run procedure.sql") 84 conn.execSQLUpdate(sqlProcedures.readText()) 85 } 86 } 87 88 internal fun checkMigrations(conn: PgConnection, cfg: DatabaseConfig, sqlFilePrefix: String) { 89 val checkStmt = conn.talerStatement("SELECT EXISTS(SELECT FROM _v.patches where patch_name = ?)") 90 91 for (patchName in migrationsPath(sqlFilePrefix)) { 92 checkStmt.bind(patchName) 93 val path = Path("${cfg.sqlDir}/$patchName.sql") 94 if (!path.exists()) break 95 val applied = checkStmt.one { it.getBoolean(1) } 96 if (!applied) { 97 throw Exception("patch $patchName not applied, run '$sqlFilePrefix dbinit'") 98 } 99 } 100 } 101 102 // sqlFilePrefix is, for example, "libeufin-bank" or "libeufin-nexus" (no trailing dash). 103 private fun resetDatabaseTables(conn: PgConnection, cfg: DatabaseConfig, sqlFilePrefix: String) { 104 logger.info("reset DB, sqldir ${cfg.sqlDir}") 105 val sqlDrop = Path("${cfg.sqlDir}/$sqlFilePrefix-drop.sql").readText() 106 conn.execSQLUpdate(sqlDrop) 107 } 108 109 fun PGSimpleDataSource.dbInit(cfg: DatabaseConfig, sqlFilePrefix: String, reset: Boolean) { 110 pgConnection().use { conn -> 111 if (reset) { 112 resetDatabaseTables(conn, cfg, sqlFilePrefix) 113 } 114 initializeDatabaseTables(conn, cfg, sqlFilePrefix) 115 } 116 }