Merge "Port the SQLite locale setting code to Java."

This commit is contained in:
Jeff Brown
2012-03-15 16:07:08 -07:00
committed by Android (Google) Code Review
6 changed files with 104 additions and 158 deletions

View File

@@ -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 {

View File

@@ -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);
}
}

View File

@@ -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.

View File

@@ -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.
*

View File

@@ -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);
}
}

View File

@@ -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;
}
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 @@ 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",