From e2f735b375f916991ee0e08783ea1070d2631afa Mon Sep 17 00:00:00 2001 From: Nik Karbaum Date: Wed, 29 Apr 2026 15:56:20 +0200 Subject: [PATCH] Surface native sqlite errors --- .../database/sqlite/SQLite/Database.java | 32 +++++++++- ios/Plugin/Utils/UtilsSQLCipher.swift | 59 +++++++++++++++---- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/android/src/main/java/com/getcapacitor/community/database/sqlite/SQLite/Database.java b/android/src/main/java/com/getcapacitor/community/database/sqlite/SQLite/Database.java index 1a24837a..309f7f2c 100644 --- a/android/src/main/java/com/getcapacitor/community/database/sqlite/SQLite/Database.java +++ b/android/src/main/java/com/getcapacitor/community/database/sqlite/SQLite/Database.java @@ -236,6 +236,7 @@ public String getUrl() { */ public void open() throws Exception { int curVersion; + String openPhase = "init"; String password = ""; if (_encrypted && (_mode.equals("secret") || _mode.equals("encryption") || _mode.equals("decryption"))) { @@ -273,17 +274,20 @@ public void open() throws Exception { } try { if (!isNCDB() && !this._readOnly) { + openPhase = "openOrCreateDatabase"; _db = SQLiteDatabase.openOrCreateDatabase(_file, password, null, null); } else { + openPhase = "openDatabase"; _db = SQLiteDatabase.openDatabase(String.valueOf(_file), password, null, SQLiteDatabase.OPEN_READONLY, null); } if (_db != null) { if (_db.isOpen()) { // set the Foreign Key Pragma ON + openPhase = "setForeignKeyConstraintsEnabled"; try { _db.setForeignKeyConstraintsEnabled(true); } catch (IllegalStateException e) { - String msg = "Failed in setForeignKeyConstraintsEnabled " + e.getMessage(); + String msg = buildOpenErrorMessage(openPhase, e); Log.v(TAG, msg); close(); _db = null; @@ -293,10 +297,11 @@ public void open() throws Exception { _isOpen = true; return; } + openPhase = "getVersion"; try { curVersion = _db.getVersion(); // default 0 } catch (IllegalStateException e) { - String msg = "Failed in get/setVersion " + e.getMessage(); + String msg = buildOpenErrorMessage(openPhase, e); Log.v(TAG, msg); close(); _db = null; @@ -304,6 +309,7 @@ public void open() throws Exception { } if (_version > curVersion && _vUpgObject != null && _vUpgObject.size() > 0) { // if (_vUpgObject != null && _vUpgObject.size() > 0) { + openPhase = "onUpgrade"; try { this._uFile.copyFile(_context, _dbName, "backup-" + _dbName); @@ -341,13 +347,33 @@ public void open() throws Exception { throw new Exception("No database returned"); } } catch (Exception e) { - String msg = "Error in creating the database" + e.getMessage(); + String msg = buildOpenErrorMessage(openPhase, e); _isOpen = false; _db = null; throw new Exception(msg); } } + private String buildOpenErrorMessage(String phase, Exception error) { + String nativeMessage = error.getMessage() != null ? error.getMessage() : error.toString(); + if (nativeMessage.startsWith("Error in creating the database phase=")) { + return nativeMessage; + } + + return ( + "Error in creating the database phase=" + + phase + + " database=" + + _dbName + + " file=" + + _file.getAbsolutePath() + + " errorClass=" + + error.getClass().getName() + + " message=" + + nativeMessage + ); + } + /** * Close Method * diff --git a/ios/Plugin/Utils/UtilsSQLCipher.swift b/ios/Plugin/Utils/UtilsSQLCipher.swift index 772bf438..52a02e3e 100644 --- a/ios/Plugin/Utils/UtilsSQLCipher.swift +++ b/ios/Plugin/Utils/UtilsSQLCipher.swift @@ -176,35 +176,74 @@ class UtilsSQLCipher { """ if sqlite3_exec(mDB, keyStatementString, nil, nil, nil) != SQLITE_OK { - let msg: String = "Wrong Secret" + let msg: String = sqliteErrorMessage( + mDB: mDB, + phase: "pragma_key", + database: filename, + fallback: "Wrong Secret") throw UtilsSQLCipherError .openOrCreateDatabase(message: msg) } } - let retB: Bool = checkDB(mDB: mDB) - if !retB { - let msg: String = "Cannot open the DB" + if let msg = checkDB(mDB: mDB, database: filename) { throw UtilsSQLCipherError .openOrCreateDatabase(message: msg) } return mDB } else { - let message: String = "open_v2 failed" + let message: String = sqliteErrorMessage( + mDB: mDB, + phase: "sqlite3_open_v2", + database: filename, + fallback: "open_v2 failed") throw UtilsSQLCipherError .openOrCreateDatabase(message: message) } } + private class func sqliteErrorMessage(mDB: OpaquePointer?, + phase: String, + database: String, + fallback: String, + resultCode: Int32? = nil, + execMessage: String? = nil) -> String { + guard let db = mDB else { + return "\(fallback) phase=\(phase) database=\(database)" + } + + let code = resultCode ?? sqlite3_errcode(db) + let extendedCode = sqlite3_extended_errcode(db) + let message = String(cString: sqlite3_errmsg(db)) + let execMessagePart = execMessage.map { " execMessage=\($0)" } ?? "" + + return "\(fallback) phase=\(phase) database=\(database) " + + "code=\(code) extendedCode=\(extendedCode) message=\(message)" + + execMessagePart + } + // MARK: - CheckDB - class func checkDB(mDB: OpaquePointer?) -> Bool { - var ret: Bool = false + class func checkDB(mDB: OpaquePointer?, database: String) -> String? { var stmt: String = "SELECT count(*) FROM " stmt.append("sqlite_master;") - if sqlite3_exec(mDB, stmt, nil, nil, nil) == SQLITE_OK { - ret = true + var errorMessage: UnsafeMutablePointer? + let resultCode = sqlite3_exec(mDB, stmt, nil, nil, &errorMessage) + if resultCode == SQLITE_OK { + return nil } - return ret + + let execMessage = errorMessage.map { String(cString: $0) } + if let errorMessage = errorMessage { + sqlite3_free(errorMessage) + } + + return sqliteErrorMessage( + mDB: mDB, + phase: "checkDB", + database: database, + fallback: "Cannot open the DB", + resultCode: resultCode, + execMessage: execMessage) } // MARK: - ChangePassword