diff options
author | Jeff Brown <jeffbrown@google.com> | 2012-03-15 14:32:32 -0700 |
---|---|---|
committer | Jeff Brown <jeffbrown@google.com> | 2012-03-15 16:02:58 -0700 |
commit | 1d9f742e001ed8280fa93fd9ba0b1125ce6d00ae (patch) | |
tree | a6e96a60b39568bdda7ae284f1b8f013ce15a833 | |
parent | 5571ffdd9d419e3b1e050501e6f8dbfb04159b5d (diff) | |
download | frameworks_base-1d9f742e001ed8280fa93fd9ba0b1125ce6d00ae.zip frameworks_base-1d9f742e001ed8280fa93fd9ba0b1125ce6d00ae.tar.gz frameworks_base-1d9f742e001ed8280fa93fd9ba0b1125ce6d00ae.tar.bz2 |
Port the SQLite locale setting code to Java.
Make the database opening code more robust in the case of
read-only database connections.
Check whether a PRAGMA needs to be issues before doing it.
Mostly it's harmless but it can grab a transaction on the
database unnecessarily.
Change-Id: Iab2cdc96c785e767f82966b00597e19337163f2f
-rw-r--r-- | api/current.txt | 2 | ||||
-rw-r--r-- | core/java/android/database/SQLException.java | 13 | ||||
-rw-r--r-- | core/java/android/database/sqlite/SQLiteConnection.java | 99 | ||||
-rw-r--r-- | core/java/android/database/sqlite/SQLiteDatabase.java | 2 | ||||
-rw-r--r-- | core/java/android/database/sqlite/SQLiteException.java | 7 | ||||
-rw-r--r-- | core/jni/android_database_SQLiteConnection.cpp | 139 |
6 files changed, 104 insertions, 158 deletions
diff --git a/api/current.txt b/api/current.txt index 93d9d44..c6c29a4 100644 --- a/api/current.txt +++ b/api/current.txt @@ -7226,6 +7226,7 @@ package android.database { public class SQLException extends java.lang.RuntimeException { ctor public SQLException(); ctor public SQLException(java.lang.String); + ctor public SQLException(java.lang.String, java.lang.Throwable); } public class StaleDataException extends java.lang.RuntimeException { @@ -7403,6 +7404,7 @@ package android.database.sqlite { public class SQLiteException extends android.database.SQLException { ctor public SQLiteException(); ctor public SQLiteException(java.lang.String); + ctor public SQLiteException(java.lang.String, java.lang.Throwable); } public class SQLiteFullException extends android.database.sqlite.SQLiteException { diff --git a/core/java/android/database/SQLException.java b/core/java/android/database/SQLException.java index 0386af0..3402026 100644 --- a/core/java/android/database/SQLException.java +++ b/core/java/android/database/SQLException.java @@ -19,12 +19,15 @@ package android.database; /** * An exception that indicates there was an error with SQL parsing or execution. */ -public class SQLException extends RuntimeException -{ - public SQLException() {} +public class SQLException extends RuntimeException { + public SQLException() { + } - public SQLException(String error) - { + public SQLException(String error) { super(error); } + + public SQLException(String error, Throwable cause) { + super(error, cause); + } } diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index 0db3e4f..e2c222b 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -99,6 +99,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private final SQLiteDatabaseConfiguration mConfiguration; private final int mConnectionId; private final boolean mIsPrimaryConnection; + private final boolean mIsReadOnlyConnection; private final PreparedStatementCache mPreparedStatementCache; private PreparedStatement mPreparedStatementPool; @@ -111,7 +112,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private boolean mOnlyAllowReadOnlyOperations; // The number of times attachCancellationSignal has been called. - // Because SQLite statement execution can be re-entrant, we keep track of how many + // Because SQLite statement execution can be reentrant, we keep track of how many // times we have attempted to attach a cancellation signal to the connection so that // we can ensure that we detach the signal at the right time. private int mCancellationSignalAttachCount; @@ -121,7 +122,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private static native void nativeClose(int connectionPtr); private static native void nativeRegisterCustomFunction(int connectionPtr, SQLiteCustomFunction function); - private static native void nativeSetLocale(int connectionPtr, String locale); + private static native void nativeRegisterLocalizedCollators(int connectionPtr, String locale); private static native int nativePrepareStatement(int connectionPtr, String sql); private static native void nativeFinalizeStatement(int connectionPtr, int statementPtr); private static native int nativeGetParameterCount(int connectionPtr, int statementPtr); @@ -163,6 +164,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen mConfiguration = new SQLiteDatabaseConfiguration(configuration); mConnectionId = connectionId; mIsPrimaryConnection = primaryConnection; + mIsReadOnlyConnection = (configuration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0; mPreparedStatementCache = new PreparedStatementCache( mConfiguration.maxSqlCacheSize); mCloseGuard.open("close"); @@ -237,45 +239,102 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } private void setPageSize() { - if (!mConfiguration.isInMemoryDb()) { - execute("PRAGMA page_size=" + SQLiteGlobal.getDefaultPageSize(), null, null); + if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { + final long newValue = SQLiteGlobal.getDefaultPageSize(); + long value = executeForLong("PRAGMA page_size", null, null); + if (value != newValue) { + execute("PRAGMA page_size=" + newValue, null, null); + } } } private void setAutoCheckpointInterval() { - if (!mConfiguration.isInMemoryDb()) { - executeForLong("PRAGMA wal_autocheckpoint=" + SQLiteGlobal.getWALAutoCheckpoint(), - null, null); + if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { + final long newValue = SQLiteGlobal.getWALAutoCheckpoint(); + long value = executeForLong("PRAGMA wal_autocheckpoint", null, null); + if (value != newValue) { + executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null); + } } } private void setJournalSizeLimit() { - if (!mConfiguration.isInMemoryDb()) { - executeForLong("PRAGMA journal_size_limit=" + SQLiteGlobal.getJournalSizeLimit(), - null, null); + if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { + final long newValue = SQLiteGlobal.getJournalSizeLimit(); + long value = executeForLong("PRAGMA journal_size_limit", null, null); + if (value != newValue) { + executeForLong("PRAGMA journal_size_limit=" + newValue, null, null); + } } } private void setSyncModeFromConfiguration() { - if (!mConfiguration.isInMemoryDb()) { - execute("PRAGMA synchronous=" + mConfiguration.syncMode, null, null); + if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { + final String newValue = mConfiguration.syncMode; + String value = executeForString("PRAGMA synchronous", null, null); + if (!value.equalsIgnoreCase(newValue)) { + execute("PRAGMA synchronous=" + newValue, null, null); + } } } private void setJournalModeFromConfiguration() { - if (!mConfiguration.isInMemoryDb()) { - String result = executeForString("PRAGMA journal_mode=" + mConfiguration.journalMode, - null, null); - if (!result.equalsIgnoreCase(mConfiguration.journalMode)) { - Log.e(TAG, "setting journal_mode to " + mConfiguration.journalMode - + " failed for db: " + mConfiguration.label - + " (on pragma set journal_mode, sqlite returned:" + result); + if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { + final String newValue = mConfiguration.journalMode; + String value = executeForString("PRAGMA journal_mode", null, null); + if (!value.equalsIgnoreCase(newValue)) { + value = executeForString("PRAGMA journal_mode=" + newValue, null, null); + if (!value.equalsIgnoreCase(newValue)) { + Log.e(TAG, "setting journal_mode to " + newValue + + " failed for db: " + mConfiguration.label + + " (on pragma set journal_mode, sqlite returned:" + value); + } } } } private void setLocaleFromConfiguration() { - nativeSetLocale(mConnectionPtr, mConfiguration.locale.toString()); + if ((mConfiguration.openFlags & SQLiteDatabase.NO_LOCALIZED_COLLATORS) != 0) { + return; + } + + // Register the localized collators. + final String newLocale = mConfiguration.locale.toString(); + nativeRegisterLocalizedCollators(mConnectionPtr, newLocale); + + // If the database is read-only, we cannot modify the android metadata table + // or existing indexes. + if (mIsReadOnlyConnection) { + return; + } + + try { + // Ensure the android metadata table exists. + execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null); + + // Check whether the locale was actually changed. + final String oldLocale = executeForString("SELECT locale FROM android_metadata " + + "UNION SELECT NULL ORDER BY locale DESC LIMIT 1", null, null); + if (oldLocale != null && oldLocale.equals(newLocale)) { + return; + } + + // Go ahead and update the indexes using the new locale. + execute("BEGIN", null, null); + boolean success = false; + try { + execute("DELETE FROM android_metadata", null, null); + execute("INSERT INTO android_metadata (locale) VALUES(?)", + new Object[] { newLocale }, null); + execute("REINDEX LOCALIZED", null, null); + success = true; + } finally { + execute(success ? "COMMIT" : "ROLLBACK", null, null); + } + } catch (RuntimeException ex) { + throw new SQLiteException("Failed to change locale for db '" + mConfiguration.label + + "' to '" + newLocale + "'.", ex); + } } // Called by SQLiteConnectionPool only. diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index d41b484..bf32ea7 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -1718,7 +1718,7 @@ public final class SQLiteDatabase extends SQLiteClosable { /** * Sets the locale for this database. Does nothing if this database has - * the NO_LOCALIZED_COLLATORS flag set or was opened read only. + * the {@link #NO_LOCALIZED_COLLATORS} flag set or was opened read only. * * @param locale The new locale. * diff --git a/core/java/android/database/sqlite/SQLiteException.java b/core/java/android/database/sqlite/SQLiteException.java index 3a97bfb..a1d9c9f 100644 --- a/core/java/android/database/sqlite/SQLiteException.java +++ b/core/java/android/database/sqlite/SQLiteException.java @@ -22,9 +22,14 @@ import android.database.SQLException; * A SQLite exception that indicates there was an error with SQL parsing or execution. */ public class SQLiteException extends SQLException { - public SQLiteException() {} + public SQLiteException() { + } public SQLiteException(String error) { super(error); } + + public SQLiteException(String error, Throwable cause) { + super(error, cause); + } } diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp index c8f911f..fca5f20 100644 --- a/core/jni/android_database_SQLiteConnection.cpp +++ b/core/jni/android_database_SQLiteConnection.cpp @@ -36,8 +36,8 @@ #include "android_database_SQLiteCommon.h" +// Set to 1 to use UTF16 storage for localized indexes. #define UTF16_STORAGE 0 -#define ANDROID_TABLE "android_metadata" namespace android { @@ -245,139 +245,16 @@ static void nativeRegisterCustomFunction(JNIEnv* env, jclass clazz, jint connect } } -// Set locale in the android_metadata table, install localized collators, and rebuild indexes -static void nativeSetLocale(JNIEnv* env, jclass clazz, jint connectionPtr, jstring localeStr) { +static void nativeRegisterLocalizedCollators(JNIEnv* env, jclass clazz, jint connectionPtr, + jstring localeStr) { SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr); - if (connection->openFlags & SQLiteConnection::NO_LOCALIZED_COLLATORS) { - // We should probably throw IllegalStateException but the contract for - // setLocale says that we just do nothing. Oh well. - return; - } - - int err; - char const* locale = env->GetStringUTFChars(localeStr, NULL); - sqlite3_stmt* stmt = NULL; - char** meta = NULL; - int rowCount, colCount; - char* dbLocale = NULL; - - // create the table, if necessary and possible - if (!(connection->openFlags & SQLiteConnection::OPEN_READONLY)) { - err = sqlite3_exec(connection->db, - "CREATE TABLE IF NOT EXISTS " ANDROID_TABLE " (locale TEXT)", - NULL, NULL, NULL); - if (err != SQLITE_OK) { - ALOGE("CREATE TABLE " ANDROID_TABLE " failed"); - throw_sqlite3_exception(env, connection->db); - goto done; - } - } + const char* locale = env->GetStringUTFChars(localeStr, NULL); + int err = register_localized_collators(connection->db, locale, UTF16_STORAGE); + env->ReleaseStringUTFChars(localeStr, locale); - // try to read from the table - err = sqlite3_get_table(connection->db, - "SELECT locale FROM " ANDROID_TABLE " LIMIT 1", - &meta, &rowCount, &colCount, NULL); if (err != SQLITE_OK) { - ALOGE("SELECT locale FROM " ANDROID_TABLE " failed"); throw_sqlite3_exception(env, connection->db); - goto done; - } - - dbLocale = (rowCount >= 1) ? meta[colCount] : NULL; - - if (dbLocale != NULL && !strcmp(dbLocale, locale)) { - // database locale is the same as the desired locale; set up the collators and go - err = register_localized_collators(connection->db, locale, UTF16_STORAGE); - if (err != SQLITE_OK) { - throw_sqlite3_exception(env, connection->db); - } - goto done; // no database changes needed - } - - if (connection->openFlags & SQLiteConnection::OPEN_READONLY) { - // read-only database, so we're going to have to put up with whatever we got - // For registering new index. Not for modifing the read-only database. - err = register_localized_collators(connection->db, locale, UTF16_STORAGE); - if (err != SQLITE_OK) { - throw_sqlite3_exception(env, connection->db); - } - goto done; - } - - // need to update android_metadata and indexes atomically, so use a transaction... - err = sqlite3_exec(connection->db, "BEGIN TRANSACTION", NULL, NULL, NULL); - if (err != SQLITE_OK) { - ALOGE("BEGIN TRANSACTION failed setting locale"); - throw_sqlite3_exception(env, connection->db); - goto done; - } - - err = register_localized_collators(connection->db, locale, UTF16_STORAGE); - if (err != SQLITE_OK) { - ALOGE("register_localized_collators() failed setting locale"); - throw_sqlite3_exception(env, connection->db); - goto rollback; - } - - err = sqlite3_exec(connection->db, "DELETE FROM " ANDROID_TABLE, NULL, NULL, NULL); - if (err != SQLITE_OK) { - ALOGE("DELETE failed setting locale"); - throw_sqlite3_exception(env, connection->db); - goto rollback; - } - - static const char *sql = "INSERT INTO " ANDROID_TABLE " (locale) VALUES(?);"; - err = sqlite3_prepare_v2(connection->db, sql, -1, &stmt, NULL); - if (err != SQLITE_OK) { - ALOGE("sqlite3_prepare_v2(\"%s\") failed", sql); - throw_sqlite3_exception(env, connection->db); - goto rollback; - } - - err = sqlite3_bind_text(stmt, 1, locale, -1, SQLITE_TRANSIENT); - if (err != SQLITE_OK) { - ALOGE("sqlite3_bind_text() failed setting locale"); - throw_sqlite3_exception(env, connection->db); - goto rollback; - } - - err = sqlite3_step(stmt); - if (err != SQLITE_OK && err != SQLITE_DONE) { - ALOGE("sqlite3_step(\"%s\") failed setting locale", sql); - throw_sqlite3_exception(env, connection->db); - goto rollback; - } - - err = sqlite3_exec(connection->db, "REINDEX LOCALIZED", NULL, NULL, NULL); - if (err != SQLITE_OK) { - ALOGE("REINDEX LOCALIZED failed"); - throw_sqlite3_exception(env, connection->db); - goto rollback; - } - - // all done, yay! - err = sqlite3_exec(connection->db, "COMMIT TRANSACTION", NULL, NULL, NULL); - if (err != SQLITE_OK) { - ALOGE("COMMIT TRANSACTION failed setting locale"); - throw_sqlite3_exception(env, connection->db); - goto done; - } - -rollback: - if (err != SQLITE_OK) { - sqlite3_exec(connection->db, "ROLLBACK TRANSACTION", NULL, NULL, NULL); - } - -done: - if (stmt) { - sqlite3_finalize(stmt); - } - if (meta) { - sqlite3_free_table(meta); - } - if (locale) { - env->ReleaseStringUTFChars(localeStr, locale); } } @@ -898,8 +775,8 @@ static JNINativeMethod sMethods[] = (void*)nativeClose }, { "nativeRegisterCustomFunction", "(ILandroid/database/sqlite/SQLiteCustomFunction;)V", (void*)nativeRegisterCustomFunction }, - { "nativeSetLocale", "(ILjava/lang/String;)V", - (void*)nativeSetLocale }, + { "nativeRegisterLocalizedCollators", "(ILjava/lang/String;)V", + (void*)nativeRegisterLocalizedCollators }, { "nativePrepareStatement", "(ILjava/lang/String;)I", (void*)nativePrepareStatement }, { "nativeFinalizeStatement", "(II)V", |