Merge "Port the SQLite locale setting code to Java."
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user