@@ -4,74 +4,90 @@ import li.angu.challengeplugin.ChallengePluginPlugin
44import java.io.File
55import java.sql.Connection
66import java.sql.DriverManager
7+ import java.sql.ResultSet
78import java.sql.SQLException
89import java.util.logging.Level
910
1011class DatabaseDriver (private val plugin : ChallengePluginPlugin ) {
11-
12+
1213 private val databaseFile = File (plugin.dataFolder, " challenges.db" )
1314 private var connection: Connection ? = null
1415 private val migrationManager = MigrationManager (this , plugin)
15-
16+
1617 /* *
1718 * Initialize the database connection and run migrations
19+ * Returns false if initialization fails, which should abort plugin startup
1820 */
1921 fun initialize (): Boolean {
2022 try {
2123 // Ensure data folder exists
2224 plugin.dataFolder.mkdirs()
23-
25+
26+ plugin.logger.info(" Plugin data folder: ${plugin.dataFolder.absolutePath} " )
27+ plugin.logger.info(" Database file path: ${databaseFile.absolutePath} " )
28+ plugin.logger.info(" Database file exists: ${databaseFile.exists()} " )
29+ if (databaseFile.exists()) {
30+ plugin.logger.info(" Database file size: ${databaseFile.length()} bytes" )
31+ }
32+
2433 // Load SQLite JDBC driver
2534 Class .forName(" org.sqlite.JDBC" )
26-
35+
2736 // Create connection
28- connection = DriverManager .getConnection(" jdbc:sqlite:${databaseFile.absolutePath} " )
37+ val connectionString = " jdbc:sqlite:${databaseFile.absolutePath} "
38+ plugin.logger.info(" Connecting to database with: $connectionString " )
39+ connection = DriverManager .getConnection(connectionString)
2940
41+ plugin.logger.info(" Database connection established: ${connection != null } " )
42+ plugin.logger.info(" Database connection valid: ${connection?.isValid(5 )} " )
43+
3044 // Enable foreign keys
31- connection?.createStatement()?.execute(" PRAGMA foreign_keys = ON" )
32-
33- // Run migrations
45+ connection?.createStatement()?.use { stmt ->
46+ stmt.execute(" PRAGMA foreign_keys = ON" )
47+ plugin.logger.info(" Foreign keys enabled" )
48+
49+ // Test if we can read basic SQLite info
50+ val result = stmt.executeQuery(" PRAGMA database_list" )
51+ while (result.next()) {
52+ plugin.logger.info(" Database: name=${result.getString(" name" )} , file=${result.getString(" file" )} " )
53+ }
54+ result.close()
55+ }
56+
57+ // Run migrations - this will throw an exception if any migration fails
3458 migrationManager.runMigrations()
35-
59+
3660 plugin.logger.info(" Database initialized successfully at: ${databaseFile.absolutePath} " )
3761 return true
38-
62+
3963 } catch (e: Exception ) {
40- plugin.logger.log(Level .SEVERE , " Failed to initialize database" , e)
64+ plugin.logger.log(Level .SEVERE , " Failed to initialize database - plugin startup aborted" , e)
65+ // Close connection if it was opened
66+ connection?.close()
67+ connection = null
4168 return false
4269 }
4370 }
44-
71+
72+ /* *
73+ * Get the database file
74+ */
75+ fun getDatabaseFile (): File = databaseFile
76+
4577 /* *
4678 * Get the current database connection
4779 */
4880 fun getConnection (): Connection ? {
49- try {
50- // Check if connection is valid
51- if (connection?.isValid(5 ) == true ) {
52- return connection
53- }
54-
55- // Reconnect if connection is invalid
56- plugin.logger.warning(" Database connection invalid, attempting to reconnect..." )
57- connection = DriverManager .getConnection(" jdbc:sqlite:${databaseFile.absolutePath} " )
58- connection?.createStatement()?.execute(" PRAGMA foreign_keys = ON" )
59-
60- return connection
61-
62- } catch (e: SQLException ) {
63- plugin.logger.log(Level .SEVERE , " Failed to get database connection" , e)
64- return null
65- }
81+ return connection
6682 }
67-
83+
6884 /* *
69- * Execute a query and return the result
85+ * Execute a query and process results immediately (SAFE - resources are properly managed)
7086 */
71- fun executeQuery (sql : String , vararg params : Any ): QueryResult {
87+ fun < T > executeQuery (sql : String , vararg params : Any , processor : ( ResultSet ) -> T ): T ? {
7288 return try {
73- val conn = getConnection() ? : return QueryResult .error( " No database connection " )
74-
89+ val conn = getConnection() ? : return null
90+
7591 conn.prepareStatement(sql).use { statement ->
7692 // Set parameters
7793 params.forEachIndexed { index, param ->
@@ -84,23 +100,24 @@ class DatabaseDriver(private val plugin: ChallengePluginPlugin) {
84100 else -> statement.setObject(index + 1 , param)
85101 }
86102 }
87-
88- val resultSet = statement.executeQuery()
89- QueryResult .success(resultSet)
103+
104+ statement.executeQuery().use { resultSet ->
105+ processor(resultSet)
106+ }
90107 }
91108 } catch (e: SQLException ) {
92109 plugin.logger.log(Level .SEVERE , " Failed to execute query: $sql " , e)
93- QueryResult .error(e.message ? : " Unknown SQL error " )
110+ null
94111 }
95112 }
96-
113+
97114 /* *
98115 * Execute an update (INSERT, UPDATE, DELETE) and return affected rows
99116 */
100117 fun executeUpdate (sql : String , vararg params : Any ): Int {
101118 return try {
102119 val conn = getConnection() ? : return - 1
103-
120+
104121 conn.prepareStatement(sql).use { statement ->
105122 // Set parameters
106123 params.forEachIndexed { index, param ->
@@ -113,24 +130,24 @@ class DatabaseDriver(private val plugin: ChallengePluginPlugin) {
113130 else -> statement.setObject(index + 1 , param)
114131 }
115132 }
116-
133+
117134 statement.executeUpdate()
118135 }
119136 } catch (e: SQLException ) {
120137 plugin.logger.log(Level .SEVERE , " Failed to execute update: $sql " , e)
121138 - 1
122139 }
123140 }
124-
141+
125142 /* *
126143 * Execute a batch of statements in a transaction
127144 */
128145 fun executeBatch (statements : List <String >): Boolean {
129146 return try {
130147 val conn = getConnection() ? : return false
131-
148+
132149 conn.autoCommit = false
133-
150+
134151 try {
135152 statements.forEach { sql ->
136153 conn.createStatement().execute(sql)
@@ -148,35 +165,101 @@ class DatabaseDriver(private val plugin: ChallengePluginPlugin) {
148165 false
149166 }
150167 }
151-
168+
169+ /* *
170+ * Execute a batch of parameterized statements in a transaction
171+ */
172+ fun executeTransaction (operations : List <Pair <String , Array <Any ?>>>): Boolean {
173+ return try {
174+ val conn = getConnection() ? : return false
175+
176+ plugin.logger.info(" Starting database transaction with ${operations.size} operations" )
177+ conn.autoCommit = false
178+
179+ try {
180+ operations.forEachIndexed { index, (sql, params) ->
181+ plugin.logger.info(" Executing operation $index : $sql " )
182+ plugin.logger.info(" Parameters: ${params.joinToString(" , " )} " )
183+
184+ conn.prepareStatement(sql).use { statement ->
185+ params.forEachIndexed { paramIndex, param ->
186+ when (param) {
187+ null -> statement.setNull(paramIndex + 1 , java.sql.Types .NULL )
188+ is String -> statement.setString(paramIndex + 1 , param)
189+ is Int -> statement.setInt(paramIndex + 1 , param)
190+ is Long -> statement.setLong(paramIndex + 1 , param)
191+ is Boolean -> statement.setBoolean(paramIndex + 1 , param)
192+ is Double -> statement.setDouble(paramIndex + 1 , param)
193+ else -> statement.setObject(paramIndex + 1 , param)
194+ }
195+ }
196+ val rowsAffected = statement.executeUpdate()
197+ plugin.logger.info(" Operation $index affected $rowsAffected rows" )
198+ }
199+ }
200+
201+ plugin.logger.info(" Committing transaction" )
202+ conn.commit()
203+ plugin.logger.info(" Transaction committed successfully" )
204+ true
205+ } catch (e: SQLException ) {
206+ plugin.logger.severe(" Transaction failed, rolling back: ${e.message} " )
207+ conn.rollback()
208+ throw e
209+ } finally {
210+ conn.autoCommit = true
211+ }
212+ } catch (e: SQLException ) {
213+ plugin.logger.log(Level .SEVERE , " Failed to execute parameterized batch" , e)
214+ false
215+ }
216+ }
217+
152218 /* *
153219 * Check if a table exists
154220 */
155221 fun tableExists (tableName : String ): Boolean {
156222 return try {
157- val result = executeQuery(
223+ executeQuery(
158224 " SELECT name FROM sqlite_master WHERE type='table' AND name=?" ,
159225 tableName
160- )
161-
162- result.resultSet?.use { rs ->
226+ ) { rs ->
163227 rs.next()
164228 } ? : false
165-
166229 } catch (e: Exception ) {
167230 false
168231 }
169232 }
170-
233+
234+ /* *
235+ * Force synchronization to disk
236+ */
237+ fun sync () {
238+ try {
239+ plugin.logger.info(" Database sync requested for file: ${databaseFile.absolutePath} " )
240+ plugin.logger.info(" Database file exists: ${databaseFile.exists()} , size: ${if (databaseFile.exists()) databaseFile.length() else 0 } bytes" )
241+
242+ // Execute PRAGMA synchronous to ensure data is written
243+ getConnection()?.createStatement()?.use { stmt ->
244+ stmt.execute(" PRAGMA synchronous = FULL" )
245+ stmt.execute(" PRAGMA wal_checkpoint(FULL)" )
246+ plugin.logger.info(" Executed PRAGMA synchronous and WAL checkpoint" )
247+ }
248+ } catch (e: Exception ) {
249+ plugin.logger.warning(" Error during database sync: ${e.message} " )
250+ }
251+ }
252+
171253 /* *
172254 * Close the database connection
173255 */
174256 fun close () {
175257 try {
258+ sync() // Ensure everything is written to disk before closing
176259 connection?.close()
177260 plugin.logger.info(" Database connection closed" )
178261 } catch (e: SQLException ) {
179262 plugin.logger.log(Level .WARNING , " Error closing database connection" , e)
180263 }
181264 }
182- }
265+ }
0 commit comments