maintain cache of statementids returned by sqlite upon compiling a sql stmnt

This commit is contained in:
Vasu Nori
2009-10-20 15:16:35 -07:00
parent 53e9c126f5
commit 5a03f36ef8
11 changed files with 508 additions and 144 deletions

View File

@@ -50825,6 +50825,17 @@
<exception name="SQLException" type="android.database.SQLException">
</exception>
</method>
<method name="resetCompiledSqlCache"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="setLocale"
return="void"
abstract="false"
@@ -50851,6 +50862,19 @@
<parameter name="lockingEnabled" type="boolean">
</parameter>
</method>
<method name="setMaxSqlCacheSize"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="cacheSize" type="int">
</parameter>
</method>
<method name="setMaximumSize"
return="long"
abstract="false"
@@ -51411,7 +51435,7 @@
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
deprecated="deprecated"
visibility="protected"
>
<parameter name="sql" type="java.lang.String">
@@ -51510,7 +51534,7 @@
synchronized="false"
static="false"
final="true"
deprecated="not deprecated"
deprecated="deprecated"
visibility="protected"
>
<parameter name="sql" type="java.lang.String">
@@ -51523,7 +51547,7 @@
synchronized="false"
static="false"
final="true"
deprecated="not deprecated"
deprecated="deprecated"
visibility="protected"
>
</method>

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -31,6 +31,12 @@ public final class SQLiteDebug {
public static final boolean DEBUG_SQL_STATEMENTS =
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.

View File

@@ -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.
*/
private SQLiteCompiledSql compiledSql;
private boolean myCompiledSqlIsInCache;
/**
* compiledSql statement id is populated with the corresponding object from the above
* member compiledSql.
* this member is used by the native_bind_* methods
*/
protected int nStatement = 0;
/**
* Used to find out where a cursor was allocated in case it never got
* released.
*/
private StackTraceElement[] mStackTraceElements;
/* 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);

View File

@@ -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]);

View File

@@ -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();
}

View File

@@ -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 \

View File

@@ -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),

View File

@@ -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

View File

@@ -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)