Skip to content

Commit 1d9f742

Browse files
author
Jeff Brown
committed
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
1 parent 5571ffd commit 1d9f742

File tree

6 files changed

+104
-158
lines changed

6 files changed

+104
-158
lines changed

api/current.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7226,6 +7226,7 @@ package android.database {
72267226
public class SQLException extends java.lang.RuntimeException {
72277227
ctor public SQLException();
72287228
ctor public SQLException(java.lang.String);
7229+
ctor public SQLException(java.lang.String, java.lang.Throwable);
72297230
}
72307231

72317232
public class StaleDataException extends java.lang.RuntimeException {
@@ -7403,6 +7404,7 @@ package android.database.sqlite {
74037404
public class SQLiteException extends android.database.SQLException {
74047405
ctor public SQLiteException();
74057406
ctor public SQLiteException(java.lang.String);
7407+
ctor public SQLiteException(java.lang.String, java.lang.Throwable);
74067408
}
74077409

74087410
public class SQLiteFullException extends android.database.sqlite.SQLiteException {

core/java/android/database/SQLException.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@
1919
/**
2020
* An exception that indicates there was an error with SQL parsing or execution.
2121
*/
22-
public class SQLException extends RuntimeException
23-
{
24-
public SQLException() {}
22+
public class SQLException extends RuntimeException {
23+
public SQLException() {
24+
}
2525

26-
public SQLException(String error)
27-
{
26+
public SQLException(String error) {
2827
super(error);
2928
}
29+
30+
public SQLException(String error, Throwable cause) {
31+
super(error, cause);
32+
}
3033
}

core/java/android/database/sqlite/SQLiteConnection.java

Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
9999
private final SQLiteDatabaseConfiguration mConfiguration;
100100
private final int mConnectionId;
101101
private final boolean mIsPrimaryConnection;
102+
private final boolean mIsReadOnlyConnection;
102103
private final PreparedStatementCache mPreparedStatementCache;
103104
private PreparedStatement mPreparedStatementPool;
104105

@@ -111,7 +112,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
111112
private boolean mOnlyAllowReadOnlyOperations;
112113

113114
// The number of times attachCancellationSignal has been called.
114-
// Because SQLite statement execution can be re-entrant, we keep track of how many
115+
// Because SQLite statement execution can be reentrant, we keep track of how many
115116
// times we have attempted to attach a cancellation signal to the connection so that
116117
// we can ensure that we detach the signal at the right time.
117118
private int mCancellationSignalAttachCount;
@@ -121,7 +122,7 @@ private static native int nativeOpen(String path, int openFlags, String label,
121122
private static native void nativeClose(int connectionPtr);
122123
private static native void nativeRegisterCustomFunction(int connectionPtr,
123124
SQLiteCustomFunction function);
124-
private static native void nativeSetLocale(int connectionPtr, String locale);
125+
private static native void nativeRegisterLocalizedCollators(int connectionPtr, String locale);
125126
private static native int nativePrepareStatement(int connectionPtr, String sql);
126127
private static native void nativeFinalizeStatement(int connectionPtr, int statementPtr);
127128
private static native int nativeGetParameterCount(int connectionPtr, int statementPtr);
@@ -163,6 +164,7 @@ private SQLiteConnection(SQLiteConnectionPool pool,
163164
mConfiguration = new SQLiteDatabaseConfiguration(configuration);
164165
mConnectionId = connectionId;
165166
mIsPrimaryConnection = primaryConnection;
167+
mIsReadOnlyConnection = (configuration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
166168
mPreparedStatementCache = new PreparedStatementCache(
167169
mConfiguration.maxSqlCacheSize);
168170
mCloseGuard.open("close");
@@ -237,45 +239,102 @@ private void dispose(boolean finalized) {
237239
}
238240

239241
private void setPageSize() {
240-
if (!mConfiguration.isInMemoryDb()) {
241-
execute("PRAGMA page_size=" + SQLiteGlobal.getDefaultPageSize(), null, null);
242+
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
243+
final long newValue = SQLiteGlobal.getDefaultPageSize();
244+
long value = executeForLong("PRAGMA page_size", null, null);
245+
if (value != newValue) {
246+
execute("PRAGMA page_size=" + newValue, null, null);
247+
}
242248
}
243249
}
244250

245251
private void setAutoCheckpointInterval() {
246-
if (!mConfiguration.isInMemoryDb()) {
247-
executeForLong("PRAGMA wal_autocheckpoint=" + SQLiteGlobal.getWALAutoCheckpoint(),
248-
null, null);
252+
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
253+
final long newValue = SQLiteGlobal.getWALAutoCheckpoint();
254+
long value = executeForLong("PRAGMA wal_autocheckpoint", null, null);
255+
if (value != newValue) {
256+
executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null);
257+
}
249258
}
250259
}
251260

252261
private void setJournalSizeLimit() {
253-
if (!mConfiguration.isInMemoryDb()) {
254-
executeForLong("PRAGMA journal_size_limit=" + SQLiteGlobal.getJournalSizeLimit(),
255-
null, null);
262+
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
263+
final long newValue = SQLiteGlobal.getJournalSizeLimit();
264+
long value = executeForLong("PRAGMA journal_size_limit", null, null);
265+
if (value != newValue) {
266+
executeForLong("PRAGMA journal_size_limit=" + newValue, null, null);
267+
}
256268
}
257269
}
258270

259271
private void setSyncModeFromConfiguration() {
260-
if (!mConfiguration.isInMemoryDb()) {
261-
execute("PRAGMA synchronous=" + mConfiguration.syncMode, null, null);
272+
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
273+
final String newValue = mConfiguration.syncMode;
274+
String value = executeForString("PRAGMA synchronous", null, null);
275+
if (!value.equalsIgnoreCase(newValue)) {
276+
execute("PRAGMA synchronous=" + newValue, null, null);
277+
}
262278
}
263279
}
264280

265281
private void setJournalModeFromConfiguration() {
266-
if (!mConfiguration.isInMemoryDb()) {
267-
String result = executeForString("PRAGMA journal_mode=" + mConfiguration.journalMode,
268-
null, null);
269-
if (!result.equalsIgnoreCase(mConfiguration.journalMode)) {
270-
Log.e(TAG, "setting journal_mode to " + mConfiguration.journalMode
271-
+ " failed for db: " + mConfiguration.label
272-
+ " (on pragma set journal_mode, sqlite returned:" + result);
282+
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
283+
final String newValue = mConfiguration.journalMode;
284+
String value = executeForString("PRAGMA journal_mode", null, null);
285+
if (!value.equalsIgnoreCase(newValue)) {
286+
value = executeForString("PRAGMA journal_mode=" + newValue, null, null);
287+
if (!value.equalsIgnoreCase(newValue)) {
288+
Log.e(TAG, "setting journal_mode to " + newValue
289+
+ " failed for db: " + mConfiguration.label
290+
+ " (on pragma set journal_mode, sqlite returned:" + value);
291+
}
273292
}
274293
}
275294
}
276295

277296
private void setLocaleFromConfiguration() {
278-
nativeSetLocale(mConnectionPtr, mConfiguration.locale.toString());
297+
if ((mConfiguration.openFlags & SQLiteDatabase.NO_LOCALIZED_COLLATORS) != 0) {
298+
return;
299+
}
300+
301+
// Register the localized collators.
302+
final String newLocale = mConfiguration.locale.toString();
303+
nativeRegisterLocalizedCollators(mConnectionPtr, newLocale);
304+
305+
// If the database is read-only, we cannot modify the android metadata table
306+
// or existing indexes.
307+
if (mIsReadOnlyConnection) {
308+
return;
309+
}
310+
311+
try {
312+
// Ensure the android metadata table exists.
313+
execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null);
314+
315+
// Check whether the locale was actually changed.
316+
final String oldLocale = executeForString("SELECT locale FROM android_metadata "
317+
+ "UNION SELECT NULL ORDER BY locale DESC LIMIT 1", null, null);
318+
if (oldLocale != null && oldLocale.equals(newLocale)) {
319+
return;
320+
}
321+
322+
// Go ahead and update the indexes using the new locale.
323+
execute("BEGIN", null, null);
324+
boolean success = false;
325+
try {
326+
execute("DELETE FROM android_metadata", null, null);
327+
execute("INSERT INTO android_metadata (locale) VALUES(?)",
328+
new Object[] { newLocale }, null);
329+
execute("REINDEX LOCALIZED", null, null);
330+
success = true;
331+
} finally {
332+
execute(success ? "COMMIT" : "ROLLBACK", null, null);
333+
}
334+
} catch (RuntimeException ex) {
335+
throw new SQLiteException("Failed to change locale for db '" + mConfiguration.label
336+
+ "' to '" + newLocale + "'.", ex);
337+
}
279338
}
280339

281340
// Called by SQLiteConnectionPool only.

core/java/android/database/sqlite/SQLiteDatabase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1718,7 +1718,7 @@ public final String getPath() {
17181718

17191719
/**
17201720
* Sets the locale for this database. Does nothing if this database has
1721-
* the NO_LOCALIZED_COLLATORS flag set or was opened read only.
1721+
* the {@link #NO_LOCALIZED_COLLATORS} flag set or was opened read only.
17221722
*
17231723
* @param locale The new locale.
17241724
*

core/java/android/database/sqlite/SQLiteException.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,14 @@
2222
* A SQLite exception that indicates there was an error with SQL parsing or execution.
2323
*/
2424
public class SQLiteException extends SQLException {
25-
public SQLiteException() {}
25+
public SQLiteException() {
26+
}
2627

2728
public SQLiteException(String error) {
2829
super(error);
2930
}
31+
32+
public SQLiteException(String error, Throwable cause) {
33+
super(error, cause);
34+
}
3035
}

core/jni/android_database_SQLiteConnection.cpp

Lines changed: 8 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@
3636

3737
#include "android_database_SQLiteCommon.h"
3838

39+
// Set to 1 to use UTF16 storage for localized indexes.
3940
#define UTF16_STORAGE 0
40-
#define ANDROID_TABLE "android_metadata"
4141

4242
namespace android {
4343

@@ -245,139 +245,16 @@ static void nativeRegisterCustomFunction(JNIEnv* env, jclass clazz, jint connect
245245
}
246246
}
247247

248-
// Set locale in the android_metadata table, install localized collators, and rebuild indexes
249-
static void nativeSetLocale(JNIEnv* env, jclass clazz, jint connectionPtr, jstring localeStr) {
248+
static void nativeRegisterLocalizedCollators(JNIEnv* env, jclass clazz, jint connectionPtr,
249+
jstring localeStr) {
250250
SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
251251

252-
if (connection->openFlags & SQLiteConnection::NO_LOCALIZED_COLLATORS) {
253-
// We should probably throw IllegalStateException but the contract for
254-
// setLocale says that we just do nothing. Oh well.
255-
return;
256-
}
257-
258-
int err;
259-
char const* locale = env->GetStringUTFChars(localeStr, NULL);
260-
sqlite3_stmt* stmt = NULL;
261-
char** meta = NULL;
262-
int rowCount, colCount;
263-
char* dbLocale = NULL;
264-
265-
// create the table, if necessary and possible
266-
if (!(connection->openFlags & SQLiteConnection::OPEN_READONLY)) {
267-
err = sqlite3_exec(connection->db,
268-
"CREATE TABLE IF NOT EXISTS " ANDROID_TABLE " (locale TEXT)",
269-
NULL, NULL, NULL);
270-
if (err != SQLITE_OK) {
271-
ALOGE("CREATE TABLE " ANDROID_TABLE " failed");
272-
throw_sqlite3_exception(env, connection->db);
273-
goto done;
274-
}
275-
}
252+
const char* locale = env->GetStringUTFChars(localeStr, NULL);
253+
int err = register_localized_collators(connection->db, locale, UTF16_STORAGE);
254+
env->ReleaseStringUTFChars(localeStr, locale);
276255

277-
// try to read from the table
278-
err = sqlite3_get_table(connection->db,
279-
"SELECT locale FROM " ANDROID_TABLE " LIMIT 1",
280-
&meta, &rowCount, &colCount, NULL);
281256
if (err != SQLITE_OK) {
282-
ALOGE("SELECT locale FROM " ANDROID_TABLE " failed");
283257
throw_sqlite3_exception(env, connection->db);
284-
goto done;
285-
}
286-
287-
dbLocale = (rowCount >= 1) ? meta[colCount] : NULL;
288-
289-
if (dbLocale != NULL && !strcmp(dbLocale, locale)) {
290-
// database locale is the same as the desired locale; set up the collators and go
291-
err = register_localized_collators(connection->db, locale, UTF16_STORAGE);
292-
if (err != SQLITE_OK) {
293-
throw_sqlite3_exception(env, connection->db);
294-
}
295-
goto done; // no database changes needed
296-
}
297-
298-
if (connection->openFlags & SQLiteConnection::OPEN_READONLY) {
299-
// read-only database, so we're going to have to put up with whatever we got
300-
// For registering new index. Not for modifing the read-only database.
301-
err = register_localized_collators(connection->db, locale, UTF16_STORAGE);
302-
if (err != SQLITE_OK) {
303-
throw_sqlite3_exception(env, connection->db);
304-
}
305-
goto done;
306-
}
307-
308-
// need to update android_metadata and indexes atomically, so use a transaction...
309-
err = sqlite3_exec(connection->db, "BEGIN TRANSACTION", NULL, NULL, NULL);
310-
if (err != SQLITE_OK) {
311-
ALOGE("BEGIN TRANSACTION failed setting locale");
312-
throw_sqlite3_exception(env, connection->db);
313-
goto done;
314-
}
315-
316-
err = register_localized_collators(connection->db, locale, UTF16_STORAGE);
317-
if (err != SQLITE_OK) {
318-
ALOGE("register_localized_collators() failed setting locale");
319-
throw_sqlite3_exception(env, connection->db);
320-
goto rollback;
321-
}
322-
323-
err = sqlite3_exec(connection->db, "DELETE FROM " ANDROID_TABLE, NULL, NULL, NULL);
324-
if (err != SQLITE_OK) {
325-
ALOGE("DELETE failed setting locale");
326-
throw_sqlite3_exception(env, connection->db);
327-
goto rollback;
328-
}
329-
330-
static const char *sql = "INSERT INTO " ANDROID_TABLE " (locale) VALUES(?);";
331-
err = sqlite3_prepare_v2(connection->db, sql, -1, &stmt, NULL);
332-
if (err != SQLITE_OK) {
333-
ALOGE("sqlite3_prepare_v2(\"%s\") failed", sql);
334-
throw_sqlite3_exception(env, connection->db);
335-
goto rollback;
336-
}
337-
338-
err = sqlite3_bind_text(stmt, 1, locale, -1, SQLITE_TRANSIENT);
339-
if (err != SQLITE_OK) {
340-
ALOGE("sqlite3_bind_text() failed setting locale");
341-
throw_sqlite3_exception(env, connection->db);
342-
goto rollback;
343-
}
344-
345-
err = sqlite3_step(stmt);
346-
if (err != SQLITE_OK && err != SQLITE_DONE) {
347-
ALOGE("sqlite3_step(\"%s\") failed setting locale", sql);
348-
throw_sqlite3_exception(env, connection->db);
349-
goto rollback;
350-
}
351-
352-
err = sqlite3_exec(connection->db, "REINDEX LOCALIZED", NULL, NULL, NULL);
353-
if (err != SQLITE_OK) {
354-
ALOGE("REINDEX LOCALIZED failed");
355-
throw_sqlite3_exception(env, connection->db);
356-
goto rollback;
357-
}
358-
359-
// all done, yay!
360-
err = sqlite3_exec(connection->db, "COMMIT TRANSACTION", NULL, NULL, NULL);
361-
if (err != SQLITE_OK) {
362-
ALOGE("COMMIT TRANSACTION failed setting locale");
363-
throw_sqlite3_exception(env, connection->db);
364-
goto done;
365-
}
366-
367-
rollback:
368-
if (err != SQLITE_OK) {
369-
sqlite3_exec(connection->db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
370-
}
371-
372-
done:
373-
if (stmt) {
374-
sqlite3_finalize(stmt);
375-
}
376-
if (meta) {
377-
sqlite3_free_table(meta);
378-
}
379-
if (locale) {
380-
env->ReleaseStringUTFChars(localeStr, locale);
381258
}
382259
}
383260

@@ -898,8 +775,8 @@ static JNINativeMethod sMethods[] =
898775
(void*)nativeClose },
899776
{ "nativeRegisterCustomFunction", "(ILandroid/database/sqlite/SQLiteCustomFunction;)V",
900777
(void*)nativeRegisterCustomFunction },
901-
{ "nativeSetLocale", "(ILjava/lang/String;)V",
902-
(void*)nativeSetLocale },
778+
{ "nativeRegisterLocalizedCollators", "(ILjava/lang/String;)V",
779+
(void*)nativeRegisterLocalizedCollators },
903780
{ "nativePrepareStatement", "(ILjava/lang/String;)I",
904781
(void*)nativePrepareStatement },
905782
{ "nativeFinalizeStatement", "(II)V",

0 commit comments

Comments
 (0)