diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
commit | 54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch) | |
tree | 35051494d2af230dce54d6b31c6af8fc24091316 /core/jni/android_database_SQLiteDatabase.cpp | |
download | frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.zip frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.gz frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.bz2 |
Initial Contribution
Diffstat (limited to 'core/jni/android_database_SQLiteDatabase.cpp')
-rw-r--r-- | core/jni/android_database_SQLiteDatabase.cpp | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp new file mode 100644 index 0000000..66f0118 --- /dev/null +++ b/core/jni/android_database_SQLiteDatabase.cpp @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2006-2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "Database" + +#include <utils/Log.h> +#include <utils/String8.h> +#include <utils/String16.h> + +#include <jni.h> +#include <JNIHelp.h> +#include <android_runtime/AndroidRuntime.h> + +#include <sqlite3.h> +#include <sqlite3_android.h> +#include <string.h> +#include <utils.h> +#include <ctype.h> + +#include <stdio.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <string.h> +#include <netdb.h> +#include <sys/ioctl.h> + +#include "sqlite3_exception.h" + +#define UTF16_STORAGE 0 +#define INVALID_VERSION -1 +#define SQLITE_SOFT_HEAP_LIMIT (4 * 1024 * 1024) +#define ANDROID_TABLE "android_metadata" + +namespace android { + +enum { + OPEN_READWRITE = 0x00000000, + OPEN_READONLY = 0x00000001, + OPEN_READ_MASK = 0x00000001, + NO_LOCALIZED_COLLATORS = 0x00000010, + CREATE_IF_NECESSARY = 0x10000000 +}; + +static jfieldID offset_db_handle; + +/* public native void dbopen(String path, int flags, String locale); */ +static void dbopen(JNIEnv* env, jobject object, jstring pathString, jint flags) +{ + int err; + sqlite3 * handle = NULL; + sqlite3_stmt * statement = NULL; + char const * path8 = env->GetStringUTFChars(pathString, NULL); + int sqliteFlags; + + // convert our flags into the sqlite flags + if (flags & CREATE_IF_NECESSARY) { + sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + } else if (flags & OPEN_READONLY) { + sqliteFlags = SQLITE_OPEN_READONLY; + } else { + sqliteFlags = SQLITE_OPEN_READWRITE; + } + + err = sqlite3_open_v2(path8, &handle, sqliteFlags, NULL); + if (err != SQLITE_OK) { + LOGE("sqlite3_open_v2(\"%s\", &handle, %d, NULL) failed\n", path8, sqliteFlags); + throw_sqlite3_exception(env, handle); + goto done; + } + + // The soft heap limit prevents the page cache allocations from growing + // beyond the given limit, no matter what the max page cache sizes are + // set to. The limit does not, as of 3.5.0, affect any other allocations. + sqlite3_soft_heap_limit(SQLITE_SOFT_HEAP_LIMIT); + + // Set the default busy handler to retry for 1000ms and then return SQLITE_BUSY + err = sqlite3_busy_timeout(handle, 1000 /* ms */); + if (err != SQLITE_OK) { + LOGE("sqlite3_busy_timeout(handle, 1000) failed for \"%s\"\n", path8); + throw_sqlite3_exception(env, handle); + goto done; + } + +#ifdef DB_INTEGRITY_CHECK + static const char* integritySql = "pragma integrity_check(1);"; + err = sqlite3_prepare_v2(handle, integritySql, -1, &statement, NULL); + if (err != SQLITE_OK) { + LOGE("sqlite_prepare_v2(handle, \"%s\") failed for \"%s\"\n", integritySql, path8); + throw_sqlite3_exception(env, handle); + goto done; + } + + // first is OK or error message + err = sqlite3_step(statement); + if (err != SQLITE_ROW) { + LOGE("integrity check failed for \"%s\"\n", integritySql, path8); + throw_sqlite3_exception(env, handle); + goto done; + } else { + const char *text = (const char*)sqlite3_column_text(statement, 0); + if (strcmp(text, "ok") != 0) { + LOGE("integrity check failed for \"%s\": %s\n", integritySql, path8, text); + jniThrowException(env, "android/database/sqlite/SQLiteDatabaseCorruptException", text); + goto done; + } + } +#endif + + err = register_android_functions(handle, UTF16_STORAGE); + if (err) { + throw_sqlite3_exception(env, handle); + goto done; + } + + LOGV("Opened '%s' - %p\n", path8, handle); + env->SetIntField(object, offset_db_handle, (int) handle); + handle = NULL; // The caller owns the handle now. + +done: + // Release allocated resources + if (path8 != NULL) env->ReleaseStringUTFChars(pathString, path8); + if (statement != NULL) sqlite3_finalize(statement); + if (handle != NULL) sqlite3_close(handle); +} + +/* public native void close(); */ +static void dbclose(JNIEnv* env, jobject object) +{ + sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); + + if (handle != NULL) { + LOGV("Closing database: handle=%p\n", handle); + int result = sqlite3_close(handle); + if (result == SQLITE_OK) { + LOGV("Closed %p\n", handle); + env->SetIntField(object, offset_db_handle, 0); + } else { + // This can happen if sub-objects aren't closed first. Make sure the caller knows. + throw_sqlite3_exception(env, handle); + LOGE("sqlite3_close(%p) failed: %d\n", handle, result); + } + } +} + +/* public native void native_execSQL(String sql); */ +static void native_execSQL(JNIEnv* env, jobject object, jstring sqlString) +{ + int err; + int stepErr; + sqlite3_stmt * statement = NULL; + sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); + jchar const * sql = env->GetStringChars(sqlString, NULL); + jsize sqlLen = env->GetStringLength(sqlString); + + if (sql == NULL || sqlLen == 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", "You must supply an SQL string"); + return; + } + + err = sqlite3_prepare16_v2(handle, sql, sqlLen * 2, &statement, NULL); + + env->ReleaseStringChars(sqlString, sql); + + if (err != SQLITE_OK) { + char const * sql8 = env->GetStringUTFChars(sqlString, NULL); + LOGE("Failure %d (%s) on %p when preparing '%s'.\n", err, sqlite3_errmsg(handle), handle, sql8); + throw_sqlite3_exception(env, handle, sql8); + env->ReleaseStringUTFChars(sqlString, sql8); + return; + } + + stepErr = sqlite3_step(statement); + err = sqlite3_finalize(statement); + + if (stepErr != SQLITE_DONE) { + if (stepErr == SQLITE_ROW) { + throw_sqlite3_exception(env, "Queries cannot be performed using execSQL(), use query() instead."); + } else { + char const * sql8 = env->GetStringUTFChars(sqlString, NULL); + LOGE("Failure %d (%s) on %p when executing '%s'\n", err, sqlite3_errmsg(handle), handle, sql8); + throw_sqlite3_exception(env, handle, sql8); + env->ReleaseStringUTFChars(sqlString, sql8); + + } + } else IF_LOGV() { + char const * sql8 = env->GetStringUTFChars(sqlString, NULL); + LOGV("Success on %p when executing '%s'\n", handle, sql8); + env->ReleaseStringUTFChars(sqlString, sql8); + } +} + +/* native long lastInsertRow(); */ +static jlong lastInsertRow(JNIEnv* env, jobject object) +{ + sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); + + return sqlite3_last_insert_rowid(handle); +} + +/* native int lastChangeCount(); */ +static jint lastChangeCount(JNIEnv* env, jobject object) +{ + sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); + + return sqlite3_changes(handle); +} + +/* set locale in the android_metadata table, install localized collators, and rebuild indexes */ +static void native_setLocale(JNIEnv* env, jobject object, jstring localeString, jint flags) +{ + if ((flags & NO_LOCALIZED_COLLATORS)) return; + + int err; + char const* locale8 = env->GetStringUTFChars(localeString, NULL); + sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); + sqlite3_stmt* stmt = NULL; + char** meta = NULL; + int rowCount, colCount; + char* dbLocale = NULL; + + // create the table, if necessary and possible + if (!(flags & OPEN_READONLY)) { + static const char *createSql ="CREATE TABLE IF NOT EXISTS " ANDROID_TABLE " (locale TEXT)"; + err = sqlite3_exec(handle, createSql, NULL, NULL, NULL); + if (err != SQLITE_OK) { + LOGE("CREATE TABLE " ANDROID_TABLE " failed\n"); + throw_sqlite3_exception(env, handle); + goto done; + } + } + + // try to read from the table + static const char *selectSql = "SELECT locale FROM " ANDROID_TABLE " LIMIT 1"; + err = sqlite3_get_table(handle, selectSql, &meta, &rowCount, &colCount, NULL); + if (err != SQLITE_OK) { + LOGE("SELECT locale FROM " ANDROID_TABLE " failed\n"); + throw_sqlite3_exception(env, handle); + goto done; + } + + dbLocale = (rowCount >= 1) ? meta[1 * colCount + 0] : NULL; + + if (dbLocale != NULL && !strcmp(dbLocale, locale8)) { + // database locale is the same as the desired locale; set up the collators and go + err = register_localized_collators(handle, locale8, UTF16_STORAGE); + if (err != SQLITE_OK) throw_sqlite3_exception(env, handle); + goto done; // no database changes needed + } + + if ((flags & OPEN_READONLY)) { + // read-only database, so we're going to have to put up with whatever we got + err = register_localized_collators(handle, dbLocale ? dbLocale : locale8, UTF16_STORAGE); + if (err != SQLITE_OK) throw_sqlite3_exception(env, handle); + goto done; + } + + // need to update android_metadata and indexes atomically, so use a transaction... + err = sqlite3_exec(handle, "BEGIN TRANSACTION", NULL, NULL, NULL); + if (err != SQLITE_OK) { + LOGE("BEGIN TRANSACTION failed setting locale\n"); + throw_sqlite3_exception(env, handle); + goto done; + } + + err = register_localized_collators(handle, dbLocale ? dbLocale : locale8, UTF16_STORAGE); + if (err != SQLITE_OK) { + LOGE("register_localized_collators() failed setting locale\n"); + throw_sqlite3_exception(env, handle); + goto done; + } + + err = sqlite3_exec(handle, "DELETE FROM " ANDROID_TABLE, NULL, NULL, NULL); + if (err != SQLITE_OK) { + LOGE("DELETE failed setting locale\n"); + throw_sqlite3_exception(env, handle); + goto rollback; + } + + static const char *sql = "INSERT INTO " ANDROID_TABLE " (locale) VALUES(?);"; + err = sqlite3_prepare_v2(handle, sql, -1, &stmt, NULL); + if (err != SQLITE_OK) { + LOGE("sqlite3_prepare_v2(\"%s\") failed\n", sql); + throw_sqlite3_exception(env, handle); + goto rollback; + } + + err = sqlite3_bind_text(stmt, 1, locale8, -1, SQLITE_TRANSIENT); + if (err != SQLITE_OK) { + LOGE("sqlite3_bind_text() failed setting locale\n"); + throw_sqlite3_exception(env, handle); + goto rollback; + } + + err = sqlite3_step(stmt); + if (err != SQLITE_OK && err != SQLITE_DONE) { + LOGE("sqlite3_step(\"%s\") failed setting locale\n", sql); + throw_sqlite3_exception(env, handle); + goto rollback; + } + + err = sqlite3_exec(handle, "REINDEX LOCALIZED", NULL, NULL, NULL); + if (err != SQLITE_OK) { + LOGE("REINDEX LOCALIZED failed\n"); + throw_sqlite3_exception(env, handle); + goto rollback; + } + + // all done, yay! + err = sqlite3_exec(handle, "COMMIT TRANSACTION", NULL, NULL, NULL); + if (err != SQLITE_OK) { + LOGE("COMMIT TRANSACTION failed setting locale\n"); + throw_sqlite3_exception(env, handle); + goto done; + } + +rollback: + sqlite3_exec(handle, "ROLLBACK TRANSACTION", NULL, NULL, NULL); + +done: + if (locale8 != NULL) env->ReleaseStringUTFChars(localeString, locale8); + if (stmt != NULL) sqlite3_finalize(stmt); + if (meta != NULL) sqlite3_free_table(meta); +} + +static jint native_releaseMemory(JNIEnv *env, jobject clazz) +{ + // Attempt to release as much memory from the + return sqlite3_release_memory(SQLITE_SOFT_HEAP_LIMIT); +} + +static JNINativeMethod sMethods[] = +{ + /* name, signature, funcPtr */ + {"dbopen", "(Ljava/lang/String;I)V", (void *)dbopen}, + {"dbclose", "()V", (void *)dbclose}, + {"native_execSQL", "(Ljava/lang/String;)V", (void *)native_execSQL}, + {"lastInsertRow", "()J", (void *)lastInsertRow}, + {"lastChangeCount", "()I", (void *)lastChangeCount}, + {"native_setLocale", "(Ljava/lang/String;I)V", (void *)native_setLocale}, + {"releaseMemory", "()I", (void *)native_releaseMemory}, +}; + +int register_android_database_SQLiteDatabase(JNIEnv *env) +{ + jclass clazz; + + clazz = env->FindClass("android/database/sqlite/SQLiteDatabase"); + if (clazz == NULL) { + LOGE("Can't find android/database/sqlite/SQLiteDatabase\n"); + return -1; + } + + offset_db_handle = env->GetFieldID(clazz, "mNativeHandle", "I"); + if (offset_db_handle == NULL) { + LOGE("Can't find SQLiteDatabase.mNativeHandle\n"); + return -1; + } + + return AndroidRuntime::registerNativeMethods(env, "android/database/sqlite/SQLiteDatabase", sMethods, NELEM(sMethods)); +} + +/* throw a SQLiteException with a message appropriate for the error in handle */ +void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle) { + throw_sqlite3_exception(env, handle, NULL); +} + +/* throw a SQLiteException with the given message */ +void throw_sqlite3_exception(JNIEnv* env, const char* message) { + throw_sqlite3_exception(env, NULL, message); +} + +/* throw a SQLiteException with a message appropriate for the error in handle + concatenated with the given message + */ +void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message) { + if (handle) { + throw_sqlite3_exception(env, sqlite3_errcode(handle), + sqlite3_errmsg(handle), message); + } else { + // we use SQLITE_OK so that a generic SQLiteException is thrown; + // any code not specified in the switch statement below would do. + throw_sqlite3_exception(env, SQLITE_OK, "unknown error", message); + } +} + +/* throw a SQLiteException for a given error code */ +void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message) { + if (errcode == SQLITE_DONE) { + throw_sqlite3_exception(env, errcode, NULL, message); + } else { + char temp[20]; + sprintf(temp, "error code %d", errcode); + throw_sqlite3_exception(env, errcode, temp, message); + } +} + +/* throw a SQLiteException for a given error code, sqlite3message, and + user message + */ +void throw_sqlite3_exception(JNIEnv* env, int errcode, + const char* sqlite3Message, const char* message) { + const char* exceptionClass; + switch (errcode) { + case SQLITE_IOERR: + exceptionClass = "android/database/sqlite/SQLiteDiskIOException"; + break; + case SQLITE_CORRUPT: + exceptionClass = "android/database/sqlite/SQLiteDatabaseCorruptException"; + break; + case SQLITE_CONSTRAINT: + exceptionClass = "android/database/sqlite/SQLiteConstraintException"; + break; + case SQLITE_ABORT: + exceptionClass = "android/database/sqlite/SQLiteAbortException"; + break; + case SQLITE_DONE: + exceptionClass = "android/database/sqlite/SQLiteDoneException"; + break; + case SQLITE_FULL: + exceptionClass = "android/database/sqlite/SQLiteFullException"; + break; + case SQLITE_MISUSE: + exceptionClass = "android/database/sqlite/SQLiteMisuseException"; + break; + default: + exceptionClass = "android/database/sqlite/SQLiteException"; + break; + } + + if (sqlite3Message != NULL && message != NULL) { + char* fullMessage = (char *)malloc(strlen(sqlite3Message) + strlen(message) + 3); + if (fullMessage != NULL) { + strcpy(fullMessage, sqlite3Message); + strcat(fullMessage, ": "); + strcat(fullMessage, message); + jniThrowException(env, exceptionClass, fullMessage); + free(fullMessage); + } else { + jniThrowException(env, exceptionClass, sqlite3Message); + } + } else if (sqlite3Message != NULL) { + jniThrowException(env, exceptionClass, sqlite3Message); + } else { + jniThrowException(env, exceptionClass, message); + } +} + + +} // namespace android |