/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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" /* uncomment the next line to force-enable logging of all statements */ // #define DB_LOG_STATEMENTS 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 #ifndef DB_LOG_STATEMENTS IF_LOGV() #endif { 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