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
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 @@
/**
* 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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
/**
* 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 @@
* 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 @@
}
}
-// 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;
- }
+ const char* locale = env->GetStringUTFChars(localeStr, NULL);
+ int err = register_localized_collators(connection->db, locale, UTF16_STORAGE);
+ env->ReleaseStringUTFChars(localeStr, locale);
- 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;
- }
- }
-
- // 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 @@
(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",