maintain cache of statementids returned by sqlite upon compiling a sql stmnt
This commit is contained in:
@@ -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>
|
||||
|
||||
111
core/java/android/database/sqlite/SQLiteCompiledSql.java
Normal file
111
core/java/android/database/sqlite/SQLiteCompiledSql.java
Normal 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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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),
|
||||
|
||||
134
core/jni/android_database_SQLiteCompiledSql.cpp
Normal file
134
core/jni/android_database_SQLiteCompiledSql.cpp
Normal 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
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user