diff options
author | Vasu Nori <vnori@google.com> | 2009-10-20 15:16:35 -0700 |
---|---|---|
committer | Vasu Nori <vnori@google.com> | 2009-11-20 14:09:24 -0800 |
commit | 5a03f36ef845f73eb4473193dbb0f93dd12a51af (patch) | |
tree | d5791b62e94ca116801c545dc55fa2729b6915cf /core | |
parent | 53e9c126f5fc39459554f0290a2c863f645d397a (diff) | |
download | frameworks_base-5a03f36ef845f73eb4473193dbb0f93dd12a51af.zip frameworks_base-5a03f36ef845f73eb4473193dbb0f93dd12a51af.tar.gz frameworks_base-5a03f36ef845f73eb4473193dbb0f93dd12a51af.tar.bz2 |
maintain cache of statementids returned by sqlite upon compiling a sql stmnt
Diffstat (limited to 'core')
-rw-r--r-- | core/java/android/database/sqlite/SQLiteCompiledSql.java | 111 | ||||
-rw-r--r-- | core/java/android/database/sqlite/SQLiteDatabase.java | 157 | ||||
-rw-r--r-- | core/java/android/database/sqlite/SQLiteDebug.java | 6 | ||||
-rw-r--r-- | core/java/android/database/sqlite/SQLiteProgram.java | 125 | ||||
-rw-r--r-- | core/java/android/database/sqlite/SQLiteQuery.java | 10 | ||||
-rw-r--r-- | core/java/android/database/sqlite/SQLiteStatement.java | 9 | ||||
-rw-r--r-- | core/jni/Android.mk | 1 | ||||
-rw-r--r-- | core/jni/AndroidRuntime.cpp | 2 | ||||
-rw-r--r-- | core/jni/android_database_SQLiteCompiledSql.cpp | 134 | ||||
-rw-r--r-- | core/jni/android_database_SQLiteProgram.cpp | 65 |
10 files changed, 480 insertions, 140 deletions
diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java new file mode 100644 index 0000000..79527b4 --- /dev/null +++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2009 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. + */ + +package android.database.sqlite; + +import android.util.Log; + +/** + * This class encapsulates compilation of sql statement and release of the compiled statement obj. + * Once a sql statement is compiled, it is cached in {@link SQLiteDatabase} + * and it is released in one of the 2 following ways + * 1. when {@link SQLiteDatabase} object is closed. + * 2. dalvikVM wants to reclaim some memory and releases it from the cache in + * {@link SQLiteDatabase}. + */ +/* package */ class SQLiteCompiledSql { + + /** The database this program is compiled against. */ + /* package */ SQLiteDatabase mDatabase; + + /** + * Native linkage, do not modify. This comes from the database. + */ + /* package */ int nHandle = 0; + + /** + * Native linkage, do not modify. When non-0 this holds a reference to a valid + * sqlite3_statement object. It is only updated by the native code, but may be + * checked in this class when the database lock is held to determine if there + * is a valid native-side program or not. + */ + /* package */ int nStatement = 0; + + /* package */ SQLiteCompiledSql(SQLiteDatabase db, String sql) { + mDatabase = db; + this.nHandle = db.mNativeHandle; + compile(sql, true); + } + + /** + * Compiles the given SQL into a SQLite byte code program using sqlite3_prepare_v2(). If + * this method has been called previously without a call to close and forCompilation is set + * to false the previous compilation will be used. Setting forceCompilation to true will + * always re-compile the program and should be done if you pass differing SQL strings to this + * method. + * + * <P>Note: this method acquires the database lock.</P> + * + * @param sql the SQL string to compile + * @param forceCompilation forces the SQL to be recompiled in the event that there is an + * existing compiled SQL program already around + */ + private void compile(String sql, boolean forceCompilation) { + // Only compile if we don't have a valid statement already or the caller has + // explicitly requested a recompile. + if (forceCompilation) { + mDatabase.lock(); + try { + // Note that the native_compile() takes care of destroying any previously + // existing programs before it compiles. + native_compile(sql); + } finally { + mDatabase.unlock(); + } + } + } + + /* package */ void releaseSqlStatement() { + // Note that native_finalize() checks to make sure that nStatement is + // non-null before destroying it. + if (nStatement != 0) { + try { + mDatabase.lock(); + native_finalize(); + nStatement = 0; + } finally { + mDatabase.unlock(); + } + } + } + + /** + * Make sure that the native resource is cleaned up. + */ + @Override + protected void finalize() { + releaseSqlStatement(); + } + + /** + * Compiles SQL into a SQLite program. + * + * <P>The database lock must be held when calling this method. + * @param sql The SQL to compile. + */ + private final native void native_compile(String sql); + private final native void native_finalize(); +} diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index f621483..a07176f 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -16,6 +16,8 @@ package android.database.sqlite; +import com.google.android.collect.Maps; + import android.content.ContentValues; import android.database.Cursor; import android.database.DatabaseUtils; @@ -29,6 +31,7 @@ import android.util.EventLog; import android.util.Log; import java.io.File; +import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; @@ -217,6 +220,34 @@ public class SQLiteDatabase extends SQLiteClosable { private WeakHashMap<SQLiteClosable, Object> mPrograms; + /** + * for each instance of this class, a cache is maintained to store + * the compiled query statement ids returned by sqlite database. + * key = sql statement with "?" for bind args + * value = {@link SQLiteCompiledSql} + * If an application opens the database and keeps it open during its entire life, then + * there will not be an overhead of compilation of sql statements by sqlite. + * + * why is this cache NOT static? because sqlite attaches compiledsql statements to the + * struct created when {@link SQLiteDatabase#openDatabase(String, CursorFactory, int)} is + * invoked. + * + * this cache has an upper limit of mMaxSqlCacheSize (settable by calling the method + * (@link setMaxCacheSize(int)}). its default is 0 - i.e., no caching by default because + * most of the apps don't use "?" syntax in their sql, caching is not useful for them. + */ + private Map<String, SQLiteCompiledSql> mCompiledQueries = Maps.newHashMap(); + private int mMaxSqlCacheSize = 0; // no caching by default + private static final int MAX_SQL_CACHE_SIZE = 1000; + + /** maintain stats about number of cache hits and misses */ + private int mNumCacheHits; + private int mNumCacheMisses; + + /** the following 2 members maintain the time when a database is opened and closed */ + private String mTimeOpened = null; + private String mTimeClosed = null; + private final RuntimeException mLeakedException; // package visible, since callers will access directly to minimize overhead in the case @@ -251,6 +282,9 @@ public class SQLiteDatabase extends SQLiteClosable { @Override protected void onAllReferencesReleased() { if (isOpen()) { + if (SQLiteDebug.DEBUG_SQL_CACHE) { + mTimeClosed = getTime(); + } dbclose(); } } @@ -798,6 +832,13 @@ public class SQLiteDatabase extends SQLiteClosable { program.onAllReferencesReleasedFromContainer(); } } + + // finalize all compiled sql statement objects in compiledQueries cache + synchronized (mCompiledQueries) { + for (SQLiteCompiledSql compiledStatement : mCompiledQueries.values()) { + compiledStatement.releaseSqlStatement(); + } + } } /** @@ -1695,16 +1736,26 @@ public class SQLiteDatabase extends SQLiteClosable { " SQLiteDatabase created and never closed"); mFactory = factory; dbopen(mPath, mFlags); + if (SQLiteDebug.DEBUG_SQL_CACHE) { + mTimeOpened = getTime(); + } mPrograms = new WeakHashMap<SQLiteClosable,Object>(); try { setLocale(Locale.getDefault()); } catch (RuntimeException e) { Log.e(TAG, "Failed to setLocale() when constructing, closing the database", e); dbclose(); + if (SQLiteDebug.DEBUG_SQL_CACHE) { + mTimeClosed = getTime(); + } throw e; } } + private String getTime() { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ").format(System.currentTimeMillis()); + } + /** * return whether the DB is opened as read only. * @return true if DB is opened as read only @@ -1733,6 +1784,112 @@ public class SQLiteDatabase extends SQLiteClosable { return mPath; } + /** + * set the max size of the compiled sql cache for this database after purging the cache. + * (size of the cache = number of compiled-sql-statements stored in the cache) + * + * synchronized because we don't want t threads to change cache size at the same time. + * @param cacheSize the size of the cache. can be (0 to MAX_SQL_CACHE_SIZE) + */ + public void setMaxSqlCacheSize(int cacheSize) { + synchronized(mCompiledQueries) { + resetCompiledSqlCache(); + mMaxSqlCacheSize = (cacheSize > MAX_SQL_CACHE_SIZE) ? MAX_SQL_CACHE_SIZE + : (cacheSize < 0) ? 0 : cacheSize; + } + } + + /** + * remove everything from the compiled sql cache + */ + public void resetCompiledSqlCache() { + synchronized(mCompiledQueries) { + mCompiledQueries.clear(); + } + } + + /** + * adds the given sql and its compiled-statement-id-returned-by-sqlite to the + * cache of compiledQueries attached to 'this'. + * + * if there is already a {@link SQLiteCompiledSql} in compiledQueries for the given sql, + * the new {@link SQLiteCompiledSql} object is NOT inserted into the cache (i.e.,the current + * mapping is NOT replaced with the new mapping). + * + * @return true if the given obj is added to cache. false otherwise. + */ + /* package */ boolean addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) { + if (mMaxSqlCacheSize == 0) { + // for this database, there is no cache of compiled sql. + if (SQLiteDebug.DEBUG_SQL_CACHE) { + Log.v(TAG, "|NOT adding_sql_to_cache|" + getPath() + "|" + sql); + } + return false; + } + + SQLiteCompiledSql compiledSql = null; + synchronized(mCompiledQueries) { + // don't insert the new mapping if a mapping already exists + compiledSql = mCompiledQueries.get(sql); + if (compiledSql != null) { + return false; + } + // add this <sql, compiledStatement> to the cache + if (mCompiledQueries.size() == mMaxSqlCacheSize) { + /* reached max cachesize. before adding new entry, remove an entry from the + * cache. we don't want to wipe out the entire cache because of this: + * GCing {@link SQLiteCompiledSql} requires call to sqlite3_finalize + * JNI method. If entire cache is wiped out, it could be cause a big GC activity + * just because a (rogue) process is using the cache incorrectly. + */ + Set<String> keySet = mCompiledQueries.keySet(); + for (String s : keySet) { + mCompiledQueries.remove(s); + break; + } + } + compiledSql = new SQLiteCompiledSql(this, sql); + mCompiledQueries.put(sql, compiledSql); + } + if (SQLiteDebug.DEBUG_SQL_CACHE) { + Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" + mCompiledQueries.size() + "|" + + sql); + } + return true; + } + + /** + * from the compiledQueries cache, returns the compiled-statement-id for the given sql. + * returns null, if not found in the cache. + */ + /* package */ SQLiteCompiledSql getCompiledStatementForSql(String sql) { + SQLiteCompiledSql compiledStatement = null; + boolean cacheHit; + synchronized(mCompiledQueries) { + if (mMaxSqlCacheSize == 0) { + // for this database, there is no cache of compiled sql. + if (SQLiteDebug.DEBUG_SQL_CACHE) { + Log.v(TAG, "|cache NOT found|" + getPath()); + } + return null; + } + cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null; + } + if (cacheHit) { + mNumCacheHits++; + } else { + mNumCacheMisses++; + } + + if (SQLiteDebug.DEBUG_SQL_CACHE) { + Log.v(TAG, "|cache_stats|" + + getPath() + "|" + mCompiledQueries.size() + + "|" + mNumCacheHits + "|" + mNumCacheMisses + + "|" + cacheHit + "|" + mTimeOpened + "|" + mTimeClosed + "|" + sql); + } + return compiledStatement; + } + /* package */ void logTimeStat(boolean read, long begin, long end) { EventLog.writeEvent(EVENT_DB_OPERATION, mPath, read ? 0 : 1, end - begin); } diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java index 84d8879..d4d3059 100644 --- a/core/java/android/database/sqlite/SQLiteDebug.java +++ b/core/java/android/database/sqlite/SQLiteDebug.java @@ -32,6 +32,12 @@ public final class SQLiteDebug { Log.isLoggable("SQLiteStatements", Log.VERBOSE); /** + * Controls the printing of compiled-sql-statement cache stats. + */ + public static final boolean DEBUG_SQL_CACHE = + Log.isLoggable("SQLiteCompiledSql", Log.VERBOSE); + + /** * Controls the stack trace reporting of active cursors being * finalized. */ diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index 9e85452..0418324 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -27,6 +27,9 @@ public abstract class SQLiteProgram extends SQLiteClosable { /** The database this program is compiled against. */ protected SQLiteDatabase mDatabase; + /** The SQL used to create this query */ + /* package */ final String mSql; + /** * Native linkage, do not modify. This comes from the database and should not be modified * in here or in the native code. @@ -34,87 +37,88 @@ public abstract class SQLiteProgram extends SQLiteClosable { protected int nHandle = 0; /** - * Native linkage, do not modify. When non-0 this holds a reference to a valid - * sqlite3_statement object. It is only updated by the native code, but may be - * checked in this class when the database lock is held to determine if there - * is a valid native-side program or not. + * the compiledSql object for the given sql statement. */ - protected int nStatement = 0; + private SQLiteCompiledSql compiledSql; + private boolean myCompiledSqlIsInCache; /** - * Used to find out where a cursor was allocated in case it never got - * released. + * compiledSql statement id is populated with the corresponding object from the above + * member compiledSql. + * this member is used by the native_bind_* methods */ - private StackTraceElement[] mStackTraceElements; - + protected int nStatement = 0; + /* package */ SQLiteProgram(SQLiteDatabase db, String sql) { if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { - mStackTraceElements = new Exception().getStackTrace(); + Log.d(TAG, "processing sql: " + sql); } - + mDatabase = db; + mSql = sql; db.acquireReference(); db.addSQLiteClosable(this); this.nHandle = db.mNativeHandle; - compile(sql, false); - } - + + compiledSql = db.getCompiledStatementForSql(sql); + if (compiledSql == null) { + // create a new compiled-sql obj + compiledSql = new SQLiteCompiledSql(db, sql); + + // add it to the cache of compiled-sqls + myCompiledSqlIsInCache = db.addToCompiledQueries(sql, compiledSql); + } else { + myCompiledSqlIsInCache = true; + } + nStatement = compiledSql.nStatement; + } + @Override protected void onAllReferencesReleased() { - // Note that native_finalize() checks to make sure that nStatement is - // non-null before destroying it. - native_finalize(); + // release the compiled sql statement used by me if it is NOT in cache + if (!myCompiledSqlIsInCache) { + compiledSql.releaseSqlStatement(); + compiledSql = null; // so that GC doesn't call finalize() on it + } mDatabase.releaseReference(); mDatabase.removeSQLiteClosable(this); } - + @Override - protected void onAllReferencesReleasedFromContainer(){ - // Note that native_finalize() checks to make sure that nStatement is - // non-null before destroying it. - native_finalize(); - mDatabase.releaseReference(); + protected void onAllReferencesReleasedFromContainer() { + // release the compiled sql statement used by me if it is NOT in cache + if (!myCompiledSqlIsInCache) { + compiledSql.releaseSqlStatement(); + compiledSql = null; // so that GC doesn't call finalize() on it + } + mDatabase.releaseReference(); } /** * Returns a unique identifier for this program. - * + * * @return a unique identifier for this program */ public final int getUniqueId() { - return nStatement; + return compiledSql.nStatement; + } + + /* package */ String getSqlString() { + return mSql; } /** - * Compiles the given SQL into a SQLite byte code program using sqlite3_prepare_v2(). If - * this method has been called previously without a call to close and forCompilation is set - * to false the previous compilation will be used. Setting forceCompilation to true will - * always re-compile the program and should be done if you pass differing SQL strings to this - * method. - * - * <P>Note: this method acquires the database lock.</P> + * @deprecated use this.compiledStatement.compile instead * * @param sql the SQL string to compile * @param forceCompilation forces the SQL to be recompiled in the event that there is an * existing compiled SQL program already around */ + @Deprecated protected void compile(String sql, boolean forceCompilation) { - // Only compile if we don't have a valid statement already or the caller has - // explicitly requested a recompile. - if (nStatement == 0 || forceCompilation) { - mDatabase.lock(); - try { - // Note that the native_compile() takes care of destroying any previously - // existing programs before it compiles. - acquireReference(); - native_compile(sql); - } finally { - releaseReference(); - mDatabase.unlock(); - } - } - } - + // TODO is there a need for this? + } + /** * Bind a NULL value to this statement. The value remains bound until * {@link #clearBindings} is called. @@ -221,37 +225,18 @@ public abstract class SQLiteProgram extends SQLiteClosable { releaseReference(); } finally { mDatabase.unlock(); - } - } - - /** - * Make sure that the native resource is cleaned up. - */ - @Override - protected void finalize() { - if (nStatement != 0) { - if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { - String message = "Finalizing " + this + - " that has not been closed"; - - Log.d(TAG, message + "\nThis cursor was created in:"); - for (StackTraceElement ste : mStackTraceElements) { - Log.d(TAG, " " + ste); - } - } - // when in finalize() it is already removed from weakhashmap - // so it is safe to not removed itself from db - onAllReferencesReleasedFromContainer(); } } /** * Compiles SQL into a SQLite program. - * + * * <P>The database lock must be held when calling this method. * @param sql The SQL to compile. */ + @Deprecated protected final native void native_compile(String sql); + @Deprecated protected final native void native_finalize(); protected final native void native_bind_null(int index); diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java index cdd9f86..94b950e 100644 --- a/core/java/android/database/sqlite/SQLiteQuery.java +++ b/core/java/android/database/sqlite/SQLiteQuery.java @@ -30,9 +30,6 @@ public class SQLiteQuery extends SQLiteProgram { /** The index of the unbound OFFSET parameter */ private int mOffsetIndex; - /** The SQL used to create this query */ - private String mQuery; - /** Args to bind on requery */ private String[] mBindArgs; @@ -49,7 +46,6 @@ public class SQLiteQuery extends SQLiteProgram { super(db, query); mOffsetIndex = offsetIndex; - mQuery = query; mBindArgs = bindArgs; } @@ -77,7 +73,7 @@ public class SQLiteQuery extends SQLiteProgram { // Logging if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { - Log.d(TAG, "fillWindow(): " + mQuery); + Log.d(TAG, "fillWindow(): " + mSql); } if (logStats) { mDatabase.logTimeStat(true /* read */, startTime, @@ -133,7 +129,7 @@ public class SQLiteQuery extends SQLiteProgram { @Override public String toString() { - return "SQLiteQuery: " + mQuery; + return "SQLiteQuery: " + mSql; } @Override @@ -153,7 +149,7 @@ public class SQLiteQuery extends SQLiteProgram { super.bindString(i + 1, mBindArgs[i]); } } catch (SQLiteMisuseException e) { - StringBuilder errMsg = new StringBuilder("mQuery " + mQuery); + StringBuilder errMsg = new StringBuilder("mSql " + mSql); for (int i = 0; i < len; i++) { errMsg.append(" "); errMsg.append(mBindArgs[i]); diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java index 5889ad9..a81c9a6 100644 --- a/core/java/android/database/sqlite/SQLiteStatement.java +++ b/core/java/android/database/sqlite/SQLiteStatement.java @@ -29,8 +29,6 @@ public class SQLiteStatement extends SQLiteProgram { private static final String TAG = "SQLiteStatement"; - private final String mSql; - /** * Don't use SQLiteStatement constructor directly, please use * {@link SQLiteDatabase#compileStatement(String)} @@ -39,11 +37,6 @@ public class SQLiteStatement extends SQLiteProgram */ /* package */ SQLiteStatement(SQLiteDatabase db, String sql) { super(db, sql); - if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { - mSql = sql; - } else { - mSql = null; - } } /** @@ -67,7 +60,7 @@ public class SQLiteStatement extends SQLiteProgram if (logStats) { mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime()); } - } finally { + } finally { releaseReference(); mDatabase.unlock(); } diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 015268b..b9042f9 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -33,6 +33,7 @@ LOCAL_SRC_FILES:= \ android_opengl_GLES11.cpp \ android_opengl_GLES11Ext.cpp \ android_database_CursorWindow.cpp \ + android_database_SQLiteCompiledSql.cpp \ android_database_SQLiteDebug.cpp \ android_database_SQLiteDatabase.cpp \ android_database_SQLiteProgram.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index f61e247..d069d7d 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -115,6 +115,7 @@ extern int register_android_view_Display(JNIEnv* env); extern int register_android_view_Surface(JNIEnv* env); extern int register_android_view_ViewRoot(JNIEnv* env); extern int register_android_database_CursorWindow(JNIEnv* env); +extern int register_android_database_SQLiteCompiledSql(JNIEnv* env); extern int register_android_database_SQLiteDatabase(JNIEnv* env); extern int register_android_database_SQLiteDebug(JNIEnv* env); extern int register_android_database_SQLiteProgram(JNIEnv* env); @@ -1205,6 +1206,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_graphics_NativeUtils), REG_JNI(register_android_database_CursorWindow), + REG_JNI(register_android_database_SQLiteCompiledSql), REG_JNI(register_android_database_SQLiteDatabase), REG_JNI(register_android_database_SQLiteDebug), REG_JNI(register_android_database_SQLiteProgram), diff --git a/core/jni/android_database_SQLiteCompiledSql.cpp b/core/jni/android_database_SQLiteCompiledSql.cpp new file mode 100644 index 0000000..8d1c39e --- /dev/null +++ b/core/jni/android_database_SQLiteCompiledSql.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2006-2008 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 "Cursor" + +#include <jni.h> +#include <JNIHelp.h> +#include <android_runtime/AndroidRuntime.h> + +#include <sqlite3.h> + +#include <utils/Log.h> + +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "sqlite3_exception.h" + + +namespace android { + +static jfieldID gHandleField; +static jfieldID gStatementField; + + +#define GET_STATEMENT(env, object) \ + (sqlite3_stmt *)env->GetIntField(object, gStatementField) +#define GET_HANDLE(env, object) \ + (sqlite3 *)env->GetIntField(object, gHandleField) + + +sqlite3_stmt * compile(JNIEnv* env, jobject object, + sqlite3 * handle, jstring sqlString) +{ + int err; + jchar const * sql; + jsize sqlLen; + sqlite3_stmt * statement = GET_STATEMENT(env, object); + + // Make sure not to leak the statement if it already exists + if (statement != NULL) { + sqlite3_finalize(statement); + env->SetIntField(object, gStatementField, 0); + } + + // Compile the SQL + sql = env->GetStringChars(sqlString, NULL); + sqlLen = env->GetStringLength(sqlString); + err = sqlite3_prepare16_v2(handle, sql, sqlLen * 2, &statement, NULL); + env->ReleaseStringChars(sqlString, sql); + + if (err == SQLITE_OK) { + // Store the statement in the Java object for future calls + LOGV("Prepared statement %p on %p", statement, handle); + env->SetIntField(object, gStatementField, (int)statement); + return statement; + } else { + // Error messages like 'near ")": syntax error' are not + // always helpful enough, so construct an error string that + // includes the query itself. + const char *query = env->GetStringUTFChars(sqlString, NULL); + char *message = (char*) malloc(strlen(query) + 50); + if (message) { + strcpy(message, ", while compiling: "); // less than 50 chars + strcat(message, query); + } + env->ReleaseStringUTFChars(sqlString, query); + throw_sqlite3_exception(env, handle, message); + free(message); + return NULL; + } +} + +static void native_compile(JNIEnv* env, jobject object, jstring sqlString) +{ + compile(env, object, GET_HANDLE(env, object), sqlString); +} + +static void native_finalize(JNIEnv* env, jobject object) +{ + int err; + sqlite3_stmt * statement = GET_STATEMENT(env, object); + + if (statement != NULL) { + sqlite3_finalize(statement); + env->SetIntField(object, gStatementField, 0); + } +} + +static JNINativeMethod sMethods[] = +{ + /* name, signature, funcPtr */ + {"native_compile", "(Ljava/lang/String;)V", (void *)native_compile}, + {"native_finalize", "()V", (void *)native_finalize}, +}; + +int register_android_database_SQLiteCompiledSql(JNIEnv * env) +{ + jclass clazz; + + clazz = env->FindClass("android/database/sqlite/SQLiteCompiledSql"); + if (clazz == NULL) { + LOGE("Can't find android/database/sqlite/SQLiteCompiledSql"); + return -1; + } + + gHandleField = env->GetFieldID(clazz, "nHandle", "I"); + gStatementField = env->GetFieldID(clazz, "nStatement", "I"); + + if (gHandleField == NULL || gStatementField == NULL) { + LOGE("Error locating fields"); + return -1; + } + + return AndroidRuntime::registerNativeMethods(env, + "android/database/sqlite/SQLiteCompiledSql", sMethods, NELEM(sMethods)); +} + +} // namespace android diff --git a/core/jni/android_database_SQLiteProgram.cpp b/core/jni/android_database_SQLiteProgram.cpp index 7bda004..32018eb 100644 --- a/core/jni/android_database_SQLiteProgram.cpp +++ b/core/jni/android_database_SQLiteProgram.cpp @@ -43,52 +43,12 @@ static jfieldID gStatementField; #define GET_HANDLE(env, object) \ (sqlite3 *)env->GetIntField(object, gHandleField) - -sqlite3_stmt * compile(JNIEnv* env, jobject object, - sqlite3 * handle, jstring sqlString) -{ - int err; - jchar const * sql; - jsize sqlLen; - sqlite3_stmt * statement = GET_STATEMENT(env, object); - - // Make sure not to leak the statement if it already exists - if (statement != NULL) { - sqlite3_finalize(statement); - env->SetIntField(object, gStatementField, 0); - } - - // Compile the SQL - sql = env->GetStringChars(sqlString, NULL); - sqlLen = env->GetStringLength(sqlString); - err = sqlite3_prepare16_v2(handle, sql, sqlLen * 2, &statement, NULL); - env->ReleaseStringChars(sqlString, sql); - - if (err == SQLITE_OK) { - // Store the statement in the Java object for future calls - LOGV("Prepared statement %p on %p", statement, handle); - env->SetIntField(object, gStatementField, (int)statement); - return statement; - } else { - // Error messages like 'near ")": syntax error' are not - // always helpful enough, so construct an error string that - // includes the query itself. - const char *query = env->GetStringUTFChars(sqlString, NULL); - char *message = (char*) malloc(strlen(query) + 50); - if (message) { - strcpy(message, ", while compiling: "); // less than 50 chars - strcat(message, query); - } - env->ReleaseStringUTFChars(sqlString, query); - throw_sqlite3_exception(env, handle, message); - free(message); - return NULL; - } -} - static void native_compile(JNIEnv* env, jobject object, jstring sqlString) { - compile(env, object, GET_HANDLE(env, object), sqlString); + char buf[32]; + sprintf(buf, "android_database_SQLiteProgram->native_compile() not implemented"); + throw_sqlite3_exception(env, GET_HANDLE(env, object), buf); + return; } static void native_bind_null(JNIEnv* env, jobject object, @@ -164,7 +124,7 @@ static void native_bind_blob(JNIEnv* env, jobject object, jsize sqlLen; sqlite3_stmt * statement= GET_STATEMENT(env, object); - jint len = env->GetArrayLength(value); + jint len = env->GetArrayLength(value); jbyte * bytes = env->GetByteArrayElements(value, NULL); err = sqlite3_bind_blob(statement, index, bytes, len, SQLITE_TRANSIENT); @@ -192,27 +152,22 @@ static void native_clear_bindings(JNIEnv* env, jobject object) static void native_finalize(JNIEnv* env, jobject object) { - int err; - sqlite3_stmt * statement = GET_STATEMENT(env, object); - - if (statement != NULL) { - sqlite3_finalize(statement); - env->SetIntField(object, gStatementField, 0); - } + char buf[32]; + sprintf(buf, "android_database_SQLiteProgram->native_finalize() not implemented"); + throw_sqlite3_exception(env, GET_HANDLE(env, object), buf); + return; } static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ - {"native_compile", "(Ljava/lang/String;)V", (void *)native_compile}, {"native_bind_null", "(I)V", (void *)native_bind_null}, {"native_bind_long", "(IJ)V", (void *)native_bind_long}, {"native_bind_double", "(ID)V", (void *)native_bind_double}, {"native_bind_string", "(ILjava/lang/String;)V", (void *)native_bind_string}, - {"native_bind_blob", "(I[B)V", (void *)native_bind_blob}, + {"native_bind_blob", "(I[B)V", (void *)native_bind_blob}, {"native_clear_bindings", "()V", (void *)native_clear_bindings}, - {"native_finalize", "()V", (void *)native_finalize}, }; int register_android_database_SQLiteProgram(JNIEnv * env) |