Bug 740218 - Support transactions in sqlitebridge and use them. r=gcp,rnewman,lucasr

This commit is contained in:
Wes Johnston 2012-04-09 10:08:37 -07:00
parent 4d4fa0c4c5
commit 65c6b15f26
7 changed files with 284 additions and 47 deletions

View File

@ -152,17 +152,14 @@ public class FormHistoryProvider extends GeckoProvider {
if (!values.containsKey(FormHistory.GUID)) {
return;
}
String guid = values.getAsString(FormHistory.GUID);
try {
if (guid == null) {
db.delete(TABLE_DELETED_FORM_HISTORY, WHERE_GUID_IS_NULL, null);
return;
}
String[] args = new String[] { guid };
db.delete(TABLE_DELETED_FORM_HISTORY, WHERE_GUID_IS_VALUE, args);
} catch(SQLiteBridgeException ex) {
Log.w(getLogTag(), "Error removing entry with GUID " + guid, ex);
if (guid == null) {
db.delete(TABLE_DELETED_FORM_HISTORY, WHERE_GUID_IS_NULL, null);
return;
}
String[] args = new String[] { guid };
db.delete(TABLE_DELETED_FORM_HISTORY, WHERE_GUID_IS_VALUE, args);
}
@Override

View File

@ -10,6 +10,8 @@ import java.io.IOException;
import java.lang.IllegalArgumentException;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Random;
import org.mozilla.gecko.GeckoApp;
@ -59,6 +61,30 @@ public abstract class GeckoProvider extends ContentProvider {
private HashMap<String, SQLiteBridge> mDatabasePerProfile;
protected Context mContext = null;
@Override
public void shutdown() {
if (mDatabasePerProfile == null)
return;
Collection<SQLiteBridge> bridges = mDatabasePerProfile.values();
Iterator<SQLiteBridge> it = bridges.iterator();
while (it.hasNext()) {
SQLiteBridge bridge = it.next();
if (bridge != null) {
try {
bridge.close();
} catch (Exception ex) { }
}
}
mDatabasePerProfile = null;
}
public void finalize() {
shutdown();
}
protected void setLogTag(String aLogTag) {
mLogTag = aLogTag;
}
@ -91,11 +117,14 @@ public abstract class GeckoProvider extends ContentProvider {
String resourcePath = context.getPackageResourcePath();
GeckoAppShell.loadSQLiteLibs(context, resourcePath);
GeckoAppShell.loadNSSLibs(context, resourcePath);
bridge = new SQLiteBridge(databasePath);
bridge = SQLiteBridge.openDatabase(databasePath, null, 0);
int version = bridge.getVersion();
Log.i(mLogTag, version + " == " + mDBVersion);
dbNeedsSetup = version != mDBVersion;
} catch(SQLiteBridgeException ex) {
} catch (SQLiteBridgeException ex) {
// close the database
if (bridge != null)
bridge.close();
// this will throw if the database can't be found
// we should attempt to set it up if Gecko is running
dbNeedsSetup = true;
@ -117,7 +146,8 @@ public abstract class GeckoProvider extends ContentProvider {
bridge = null;
initGecko();
}
mDatabasePerProfile.put(databasePath, bridge);
if (bridge != null)
mDatabasePerProfile.put(databasePath, bridge);
return bridge;
}
@ -204,6 +234,7 @@ public abstract class GeckoProvider extends ContentProvider {
deleted = db.delete(getTable(uri), selection, selectionArgs);
} catch (SQLiteBridgeException ex) {
Log.e(mLogTag, "Error deleting record", ex);
throw ex;
}
return deleted;
@ -222,17 +253,65 @@ public abstract class GeckoProvider extends ContentProvider {
setupDefaults(uri, values);
onPreInsert(values, uri, db);
boolean useTransaction = !db.inTransaction();
try {
if (useTransaction) {
db.beginTransaction();
}
// onPreInsert does a check for the item in the deleted table in some cases
// so we put it inside this transaction
onPreInsert(values, uri, db);
id = db.insert(getTable(uri), null, values);
} catch(SQLiteBridgeException ex) {
if (useTransaction) {
db.setTransactionSuccessful();
}
} catch (SQLiteBridgeException ex) {
Log.e(mLogTag, "Error inserting in db", ex);
throw ex;
} finally {
if (useTransaction) {
db.endTransaction();
}
}
return ContentUris.withAppendedId(uri, id);
}
@Override
public int bulkInsert(Uri uri, ContentValues[] allValues) {
final SQLiteBridge db = getDatabase(uri);
// If we can not get a SQLiteBridge instance, its likely that the database
// has not been set up and Gecko is not running. We return 0 and expect
// callers to try again later
if (db == null)
return 0;
long id = -1;
int rowsAdded = 0;
String table = getTable(uri);
try {
db.beginTransaction();
for (ContentValues initialValues : allValues) {
ContentValues values = new ContentValues(initialValues);
setupDefaults(uri, values);
onPreInsert(values, uri, db);
id = db.insert(table, null, values);
rowsAdded++;
}
db.setTransactionSuccessful();
} catch (SQLiteBridgeException ex) {
Log.e(mLogTag, "Error inserting in db", ex);
throw ex;
} finally {
db.endTransaction();
}
return rowsAdded;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
@ -249,8 +328,9 @@ public abstract class GeckoProvider extends ContentProvider {
try {
updated = db.update(getTable(uri), values, selection, selectionArgs);
} catch(SQLiteBridgeException ex) {
} catch (SQLiteBridgeException ex) {
Log.e(mLogTag, "Error updating table", ex);
throw ex;
}
return updated;
@ -275,6 +355,7 @@ public abstract class GeckoProvider extends ContentProvider {
onPostQuery(cursor, uri, db);
} catch (SQLiteBridgeException ex) {
Log.e(mLogTag, "Error querying database", ex);
throw ex;
}
return cursor;

View File

@ -217,16 +217,12 @@ public class PasswordsProvider extends GeckoProvider {
public void onPreInsert(ContentValues values, Uri uri, SQLiteBridge db) {
if (values.containsKey(Passwords.GUID)) {
String guid = values.getAsString(Passwords.GUID);
try {
if (guid == null) {
db.delete(TABLE_DELETED_PASSWORDS, WHERE_GUID_IS_NULL, null);
return;
}
String[] args = new String[] { guid };
db.delete(TABLE_DELETED_PASSWORDS, WHERE_GUID_IS_VALUE, args);
} catch(SQLiteBridgeException ex) {
Log.w(getLogTag(), "Error removing entry with GUID " + guid, ex);
if (guid == null) {
db.delete(TABLE_DELETED_PASSWORDS, WHERE_GUID_IS_NULL, null);
return;
}
String[] args = new String[] { guid };
db.delete(TABLE_DELETED_PASSWORDS, WHERE_GUID_IS_VALUE, args);
}
if (values.containsKey(Passwords.ENCRYPTED_PASSWORD)) {

View File

@ -8,14 +8,17 @@ import org.mozilla.gecko.sqlite.SQLiteBridgeException;
import org.mozilla.gecko.sqlite.MatrixBlobCursor;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.DatabaseErrorHandler;
import android.text.TextUtils;
import android.util.Log;
import java.lang.String;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import android.util.Log;
import java.util.Map.Entry;
import java.util.Set;
@ -27,12 +30,17 @@ import java.util.Set;
public class SQLiteBridge {
private static final String LOGTAG = "SQLiteBridge";
// Path to the database. We reopen it every query.
// Path to the database. If this database was not opened with openDatabase, we reopen it every query.
private String mDb;
// pointer to the database if it was opened with openDatabase
protected long mDbPointer = 0;
// Values remembered after a query.
private long[] mQueryResults;
private boolean mTransactionSuccess = false;
private boolean mInTransaction = false;
private static final int RESULT_INSERT_ROW_ID = 0;
private static final int RESULT_ROWS_CHANGED = 1;
@ -41,6 +49,13 @@ public class SQLiteBridge {
String[] aParams,
long[] aUpdateResult)
throws SQLiteBridgeException;
private static native MatrixBlobCursor sqliteCallWithDb(long aDb, String aQuery,
String[] aParams,
long[] aUpdateResult)
throws SQLiteBridgeException;
private static native long openDatabase(String aDb)
throws SQLiteBridgeException;
private static native void closeDatabase(long aDb);
// Takes the path to the database we want to access.
public SQLiteBridge(String aDb) throws SQLiteBridgeException {
@ -204,10 +219,92 @@ public class SQLiteBridge {
// are not supported.
private Cursor internalQuery(String aQuery, String[] aParams)
throws SQLiteBridgeException {
mQueryResults = new long[2];
if (isOpen()) {
return sqliteCallWithDb(mDbPointer, aQuery, aParams, mQueryResults);
}
return sqliteCall(mDb, aQuery, aParams, mQueryResults);
}
// nop, provided for API compatibility with SQLiteDatabase.
public void close() { }
/*
* The second two parameters here are just provided for compatbility with SQLiteDatabase
* Support for them is not currently implemented
*/
public static SQLiteBridge openDatabase(String path, SQLiteDatabase.CursorFactory factory, int flags)
throws SQLiteException {
SQLiteBridge bridge = null;
try {
bridge = new SQLiteBridge(path);
bridge.mDbPointer = bridge.openDatabase(path);
} catch(SQLiteBridgeException ex) {
// catch and rethrow as a SQLiteException to match SQLiteDatabase
throw new SQLiteException(ex.getMessage());
}
return bridge;
}
public void close() {
if (isOpen()) {
closeDatabase(mDbPointer);
}
mDbPointer = 0;
}
public boolean isOpen() {
return mDbPointer > 0;
}
public void beginTransaction() throws SQLiteBridgeException {
if (inTransaction()) {
throw new SQLiteBridgeException("Nested transactions are not supported");
}
execSQL("BEGIN EXCLUSIVE");
mTransactionSuccess = false;
mInTransaction = true;
}
public void beginTransactionNonExclusive() throws SQLiteBridgeException {
if (inTransaction()) {
throw new SQLiteBridgeException("Nested transactions are not supported");
}
execSQL("BEGIN IMMEDIATE");
mTransactionSuccess = false;
mInTransaction = true;
}
public void endTransaction() {
if (!inTransaction())
return;
try {
if (mTransactionSuccess) {
execSQL("COMMIT TRANSACTION");
} else {
execSQL("ROLLBACK TRANSACTION");
}
} catch(SQLiteBridgeException ex) {
Log.e(LOGTAG, "Error ending transaction", ex);
}
mInTransaction = false;
mTransactionSuccess = false;
}
public void setTransactionSuccessful() throws SQLiteBridgeException {
if (!inTransaction()) {
throw new SQLiteBridgeException("setTransactionSuccessful called outside a transaction");
}
mTransactionSuccess = true;
}
public boolean inTransaction() {
return mInTransaction;
}
public void finalize() {
if (isOpen()) {
Log.e(LOGTAG, "Bridge finalized without closing the database");
close();
}
}
}

View File

@ -37,11 +37,11 @@
package org.mozilla.gecko.sqlite;
public class SQLiteBridgeException extends Exception {
public class SQLiteBridgeException extends RuntimeException {
static final long serialVersionUID = 1L;
public SQLiteBridgeException() {}
public SQLiteBridgeException(String msg) {
super(msg);
}
}
}

View File

@ -168,27 +168,94 @@ Java_org_mozilla_gecko_sqlite_SQLiteBridge_sqliteCall(JNIEnv* jenv, jclass,
{
JNI_Setup(jenv);
int rc;
jobject jCursor = NULL;
const char* dbPath;
sqlite3 *db;
char* errorMsg;
dbPath = jenv->GetStringUTFChars(jDb, NULL);
rc = f_sqlite3_open(dbPath, &db);
jenv->ReleaseStringUTFChars(jDb, dbPath);
if (rc != SQLITE_OK) {
asprintf(&errorMsg, "Can't open database: %s\n", f_sqlite3_errmsg(db));
LOG("Error in SQLiteBridge: %s\n", errorMsg);
JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", errorMsg);
free(errorMsg);
} else {
jCursor = sqliteInternalCall(jenv, db, jQuery, jParams, jQueryRes);
}
f_sqlite3_close(db);
return jCursor;
}
extern "C" NS_EXPORT jobject JNICALL
Java_org_mozilla_gecko_sqlite_SQLiteBridge_sqliteCallWithDb(JNIEnv* jenv, jclass,
jlong jDb,
jstring jQuery,
jobjectArray jParams,
jlongArray jQueryRes)
{
JNI_Setup(jenv);
jobject jCursor = NULL;
sqlite3 *db = (sqlite3*)jDb;
jCursor = sqliteInternalCall(jenv, db, jQuery, jParams, jQueryRes);
return jCursor;
}
extern "C" NS_EXPORT jlong JNICALL
Java_org_mozilla_gecko_sqlite_SQLiteBridge_openDatabase(JNIEnv* jenv, jclass,
jstring jDb)
{
JNI_Setup(jenv);
int rc;
const char* dbPath;
sqlite3 *db;
char* errorMsg;
dbPath = jenv->GetStringUTFChars(jDb, NULL);
rc = f_sqlite3_open(dbPath, &db);
jenv->ReleaseStringUTFChars(jDb, dbPath);
if (rc != SQLITE_OK) {
asprintf(&errorMsg, "Can't open database: %s\n", f_sqlite3_errmsg(db));
LOG("Error in SQLiteBridge: %s\n", errorMsg);
JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", errorMsg);
free(errorMsg);
}
return (jlong)db;
}
extern "C" NS_EXPORT void JNICALL
Java_org_mozilla_gecko_sqlite_SQLiteBridge_closeDatabase(JNIEnv* jenv, jclass,
jlong jDb)
{
JNI_Setup(jenv);
sqlite3 *db = (sqlite3*)jDb;
f_sqlite3_close(db);
}
static jobject
sqliteInternalCall(JNIEnv* jenv,
sqlite3 *db,
jstring jQuery,
jobjectArray jParams,
jlongArray jQueryRes)
{
JNI_Setup(jenv);
jobject jCursor = NULL;
char* errorMsg;
jsize numPars = 0;
const char* queryStr;
queryStr = jenv->GetStringUTFChars(jQuery, NULL);
const char* dbPath;
dbPath = jenv->GetStringUTFChars(jDb, NULL);
const char *pzTail;
sqlite3_stmt *ppStmt;
sqlite3 *db;
int rc;
rc = f_sqlite3_open(dbPath, &db);
jenv->ReleaseStringUTFChars(jDb, dbPath);
if (rc != SQLITE_OK) {
asprintf(&errorMsg, "Can't open database: %s\n", f_sqlite3_errmsg(db));
goto error_close;
}
const char* queryStr;
queryStr = jenv->GetStringUTFChars(jQuery, NULL);
rc = f_sqlite3_prepare_v2(db, queryStr, -1, &ppStmt, &pzTail);
if (rc != SQLITE_OK || ppStmt == NULL) {
@ -353,11 +420,9 @@ Java_org_mozilla_gecko_sqlite_SQLiteBridge_sqliteCall(JNIEnv* jenv, jclass,
goto error_close;
}
f_sqlite3_close(db);
return jCursor;
error_close:
f_sqlite3_close(db);
LOG("Error in SQLiteBridge: %s\n", errorMsg);
JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", errorMsg);
free(errorMsg);

View File

@ -40,6 +40,7 @@
#include "sqlite3.h"
void setup_sqlite_functions(void *sqlite_handle);
static jobject sqliteInternalCall(JNIEnv* jenv, sqlite3 *db, jstring jQuery, jobjectArray jParams, jlongArray jQueryRes);
#define SQLITE_WRAPPER(name, return_type, args...) \
typedef return_type (*name ## _t)(args); \