Revert "caching code retooled to reduce locking + handle SMP"
This reverts commit 992f7d52fa.
Change-Id: Ia5b789d1b2195d6ce43baffe24296c857f9b30f6
This commit is contained in:
@@ -99,7 +99,7 @@ import java.util.Random;
|
||||
poolObj = mPool.get(0);
|
||||
} else {
|
||||
for (int i = 0; i < mMaxPoolSize; i++) {
|
||||
if (mPool.get(i).mDb.mCache.isSqlInStatementCache(sql)) {
|
||||
if (mPool.get(i).mDb.isSqlInStatementCache(sql)) {
|
||||
poolObj = mPool.get(i);
|
||||
break;
|
||||
}
|
||||
@@ -125,8 +125,7 @@ import java.util.Random;
|
||||
// there are free connections available. pick one
|
||||
// preferably a connection caching the pre-compiled statement of the given SQL
|
||||
for (int i = 0; i < poolSize; i++) {
|
||||
if (mPool.get(i).isFree() &&
|
||||
mPool.get(i).mDb.mCache.isSqlInStatementCache(sql)) {
|
||||
if (mPool.get(i).isFree() && mPool.get(i).mDb.isSqlInStatementCache(sql)) {
|
||||
poolObj = mPool.get(i);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* For each instance of {@link SQLiteDatabase}, this class maintains a LRU cache to store
|
||||
* the compiled query statement ids returned by sqlite database.
|
||||
*<p>
|
||||
*<ul>
|
||||
* <li>key = SQL statement with "?" for bind args</li>
|
||||
* <li>value = {@link SQLiteCompiledSql}</li>
|
||||
*</ul>
|
||||
* 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.
|
||||
*<p>
|
||||
* Why is this cache NOT static? because sqlite attaches compiled-sql statements to the
|
||||
* database connections.
|
||||
*<p>
|
||||
* This cache has an upper limit of mMaxSqlCacheSize (settable by calling the method
|
||||
* (@link #setMaxSqlCacheSize(int)}).
|
||||
*/
|
||||
/* package */ class SQLiteCache {
|
||||
private static final String TAG = "SQLiteCache";
|
||||
|
||||
/** The {@link SQLiteDatabase} instance this cache is attached to */
|
||||
private final SQLiteDatabase mDatabase;
|
||||
|
||||
/** Default statement-cache size per database connection ( = instance of this class) */
|
||||
private int mMaxSqlCacheSize = 25;
|
||||
|
||||
/** The LRU cache */
|
||||
private final Map<String, SQLiteCompiledSql> mCompiledQueries =
|
||||
new LinkedHashMap<String, SQLiteCompiledSql>(mMaxSqlCacheSize + 1, 0.75f, true) {
|
||||
@Override
|
||||
public boolean removeEldestEntry(Map.Entry<String, SQLiteCompiledSql> eldest) {
|
||||
// eldest = least-recently used entry
|
||||
// if it needs to be removed to accommodate a new entry,
|
||||
// close {@link SQLiteCompiledSql} represented by this entry, if not in use
|
||||
// and then let it be removed from the Map.
|
||||
mDatabase.verifyLockOwner();
|
||||
if (this.size() <= mMaxSqlCacheSize) {
|
||||
// cache is not full. nothing needs to be removed
|
||||
return false;
|
||||
}
|
||||
// cache is full. eldest will be removed.
|
||||
eldest.getValue().releaseIfNotInUse();
|
||||
// return true, so that this entry is removed automatically by the caller.
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/** Maintains whether or not cacheFullWarning has been logged */
|
||||
private boolean mCacheFullWarning;
|
||||
|
||||
/** The following 2 members maintain stats about cache hits and misses */
|
||||
private int mNumCacheHits;
|
||||
private int mNumCacheMisses;
|
||||
|
||||
/**
|
||||
* Constructor used by {@link SQLiteDatabase}.
|
||||
* @param db
|
||||
*/
|
||||
/* package */ SQLiteCache(SQLiteDatabase db) {
|
||||
mDatabase = db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given SQL and its compiled-statement to the cache, if the given SQL statement
|
||||
* doesn't already exist in cache.
|
||||
*
|
||||
* @return true if added to cache. false otherwise.
|
||||
*/
|
||||
/* package */ synchronized boolean addToCompiledQueries(String sql,
|
||||
SQLiteCompiledSql compiledStatement) {
|
||||
if (mCompiledQueries.containsKey(sql)) {
|
||||
// already exists.
|
||||
return false;
|
||||
}
|
||||
|
||||
/* add the given SQLiteCompiledSql compiledStatement to cache.
|
||||
* no need to worry about the cache size - because {@link #mCompiledQueries}
|
||||
* self-limits its size to {@link #mMaxSqlCacheSize}.
|
||||
*/
|
||||
mCompiledQueries.put(sql, compiledStatement);
|
||||
|
||||
// need to log a warning to say that the cache is full?
|
||||
if (!isCacheFullWarningLogged() && mCompiledQueries.size() == mMaxSqlCacheSize) {
|
||||
/*
|
||||
* cache size of {@link #mMaxSqlCacheSize} is not enough for this app.
|
||||
* log a warning.
|
||||
* chances are it is NOT using ? for bindargs - or cachesize is too small.
|
||||
*/
|
||||
Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " +
|
||||
mDatabase.getPath() +
|
||||
". Use setMaxSqlCacheSize() in SQLiteDatabase to increase cachesize. ");
|
||||
setCacheFullWarningLogged();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the compiled-statement for the given SQL statement, if the entry exists in cache
|
||||
* and is free to use. Returns null otherwise.
|
||||
* <p>
|
||||
* If a compiled-sql statement is returned for the caller, it is reserved for the caller.
|
||||
* So, don't use this method unless the caller needs to acquire the object.
|
||||
*/
|
||||
/* package */ synchronized SQLiteCompiledSql getCompiledStatementForSql(String sql) {
|
||||
SQLiteCompiledSql compiledStatement = mCompiledQueries.get(sql);
|
||||
if (compiledStatement == null) {
|
||||
mNumCacheMisses++;
|
||||
return null;
|
||||
}
|
||||
mNumCacheHits++;
|
||||
// reserve it for the caller, if it is not already in use
|
||||
if (!compiledStatement.acquire()) {
|
||||
// couldn't acquire it since it is already in use. bug in app?
|
||||
if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
|
||||
Log.w(TAG, "Possible bug: Either using the same SQL in 2 threads at " +
|
||||
" the same time, or previous instance of this SQL statement was " +
|
||||
"never close()d. " + compiledStatement.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return compiledStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given statement is in cache, it is released back to cache and it is made available for
|
||||
* others to use.
|
||||
* <p>
|
||||
* return true if the statement is put back in cache, false otherwise (false = the statement
|
||||
* is NOT in cache)
|
||||
*/
|
||||
/* package */ synchronized boolean releaseBackToCache(SQLiteCompiledSql stmt) {
|
||||
if (!mCompiledQueries.containsValue(stmt)) {
|
||||
return false;
|
||||
}
|
||||
// it is in cache. release it from the caller, make it available for others to use
|
||||
stmt.free();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* releases all compiled-sql statements in the cache.
|
||||
*/
|
||||
/* package */ synchronized void dealloc() {
|
||||
for (SQLiteCompiledSql stmt : mCompiledQueries.values()) {
|
||||
stmt.setState(SQLiteCompiledSql.NSTATE_CACHE_DEALLOC);
|
||||
stmt.releaseFromDatabase();
|
||||
}
|
||||
mCompiledQueries.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* see documentation on {@link SQLiteDatabase#setMaxSqlCacheSize(int)}.
|
||||
*/
|
||||
/* package */ synchronized void setMaxSqlCacheSize(int cacheSize) {
|
||||
if (cacheSize > SQLiteDatabase.MAX_SQL_CACHE_SIZE || cacheSize < 0) {
|
||||
throw new IllegalStateException("expected value between 0 and " +
|
||||
SQLiteDatabase.MAX_SQL_CACHE_SIZE);
|
||||
} else if (cacheSize < mMaxSqlCacheSize) {
|
||||
throw new IllegalStateException("cannot set cacheSize to a value less than the value " +
|
||||
"set with previous setMaxSqlCacheSize() call.");
|
||||
}
|
||||
mMaxSqlCacheSize = cacheSize;
|
||||
}
|
||||
|
||||
/* package */ synchronized boolean isSqlInStatementCache(String sql) {
|
||||
return mCompiledQueries.containsKey(sql);
|
||||
}
|
||||
|
||||
private synchronized boolean isCacheFullWarningLogged() {
|
||||
return mCacheFullWarning;
|
||||
}
|
||||
|
||||
private synchronized void setCacheFullWarningLogged() {
|
||||
mCacheFullWarning = true;
|
||||
}
|
||||
/* package */ synchronized int getCacheHitNum() {
|
||||
return mNumCacheHits;
|
||||
}
|
||||
/* package */ synchronized int getCacheMissNum() {
|
||||
return mNumCacheMisses;
|
||||
}
|
||||
/* package */ synchronized int getCachesize() {
|
||||
return mCompiledQueries.size();
|
||||
}
|
||||
|
||||
// only for testing
|
||||
/* package */ synchronized Set<String> getKeys() {
|
||||
return mCompiledQueries.keySet();
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package android.database.sqlite;
|
||||
|
||||
import android.database.DatabaseUtils;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
@@ -25,19 +24,19 @@ import android.util.Log;
|
||||
* and it is released in one of the 2 following ways
|
||||
* 1. when {@link SQLiteDatabase} object is closed.
|
||||
* 2. if this is not cached in {@link SQLiteDatabase}, {@link android.database.Cursor#close()}
|
||||
* releases this obj.
|
||||
* releaases this obj.
|
||||
*/
|
||||
/* package */ class SQLiteCompiledSql {
|
||||
|
||||
private static final String TAG = "SQLiteCompiledSql";
|
||||
|
||||
/** The database this program is compiled against. */
|
||||
/* package */ final SQLiteDatabase mDatabase;
|
||||
/* package */ SQLiteDatabase mDatabase;
|
||||
|
||||
/**
|
||||
* Native linkage, do not modify. This comes from the database.
|
||||
*/
|
||||
/* package */ final int nHandle;
|
||||
/* package */ int nHandle = 0;
|
||||
|
||||
/**
|
||||
* Native linkage, do not modify. When non-0 this holds a reference to a valid
|
||||
@@ -47,53 +46,52 @@ import android.util.Log;
|
||||
*/
|
||||
/* package */ int nStatement = 0;
|
||||
|
||||
/** the following 3 members are for debugging purposes */
|
||||
private final String mSqlStmt;
|
||||
private final Throwable mStackTrace;
|
||||
private int nState = 0;
|
||||
/** values the above member can take */
|
||||
private static final int NSTATE_CACHEABLE = 64;
|
||||
private static final int NSTATE_IN_CACHE = 32;
|
||||
private static final int NSTATE_INUSE = 16;
|
||||
private static final int NSTATE_INUSE_RESETMASK = 0x0f;
|
||||
/* package */ static final int NSTATE_CLOSE_NOOP = 1;
|
||||
private static final int NSTATE_EVICTED_FROM_CACHE = 2;
|
||||
/* package */ static final int NSTATE_CACHE_DEALLOC = 4;
|
||||
private static final int NSTATE_IN_FINALIZER_Q = 8;
|
||||
/** the following are for debugging purposes */
|
||||
private String mSqlStmt = null;
|
||||
private Throwable mStackTrace = null;
|
||||
|
||||
private SQLiteCompiledSql(SQLiteDatabase db, String sql) {
|
||||
db.verifyDbIsOpen();
|
||||
db.verifyLockOwner();
|
||||
/** when in cache and is in use, this member is set */
|
||||
private boolean mInUse = false;
|
||||
|
||||
/* package */ SQLiteCompiledSql(SQLiteDatabase db, String sql) {
|
||||
if (!db.isOpen()) {
|
||||
throw new IllegalStateException("database " + db.getPath() + " already closed");
|
||||
}
|
||||
mDatabase = db;
|
||||
mSqlStmt = sql;
|
||||
mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
|
||||
nHandle = db.mNativeHandle;
|
||||
native_compile(sql);
|
||||
this.nHandle = db.mNativeHandle;
|
||||
compile(sql, true);
|
||||
}
|
||||
|
||||
/* package */ static SQLiteCompiledSql get(SQLiteDatabase db, String sql, int type) {
|
||||
// only CRUD statements are cached.
|
||||
if (type != DatabaseUtils.STATEMENT_SELECT && type != DatabaseUtils.STATEMENT_UPDATE) {
|
||||
return new SQLiteCompiledSql(db, sql);
|
||||
/**
|
||||
* 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) {
|
||||
mDatabase.verifyLockOwner();
|
||||
// Only compile if we don't have a valid statement already or the caller has
|
||||
// explicitly requested a recompile.
|
||||
if (forceCompilation) {
|
||||
// Note that the native_compile() takes care of destroying any previously
|
||||
// existing programs before it compiles.
|
||||
native_compile(sql);
|
||||
}
|
||||
// the given SQL statement is cacheable.
|
||||
SQLiteCompiledSql stmt = db.mCache.getCompiledStatementForSql(sql);
|
||||
if (stmt != null) {
|
||||
return stmt;
|
||||
}
|
||||
// either the entry doesn't exist in cache or the one in cache is currently in use.
|
||||
// try to add it to cache and let cache worry about what copy to keep
|
||||
stmt = new SQLiteCompiledSql(db, sql);
|
||||
stmt.nState |= NSTATE_CACHEABLE |
|
||||
((db.mCache.addToCompiledQueries(sql, stmt)) ? NSTATE_IN_CACHE : 0);
|
||||
return stmt;
|
||||
}
|
||||
|
||||
/* package */ synchronized void releaseFromDatabase() {
|
||||
/* package */ void releaseSqlStatement() {
|
||||
// Note that native_finalize() checks to make sure that nStatement is
|
||||
// non-null before destroying it.
|
||||
if (nStatement != 0) {
|
||||
nState |= NSTATE_IN_FINALIZER_Q;
|
||||
mDatabase.finalizeStatementLater(nStatement);
|
||||
nStatement = 0;
|
||||
}
|
||||
@@ -103,47 +101,20 @@ import android.util.Log;
|
||||
* returns true if acquire() succeeds. false otherwise.
|
||||
*/
|
||||
/* package */ synchronized boolean acquire() {
|
||||
if ((nState & NSTATE_INUSE) > 0 ) {
|
||||
// this object is already in use
|
||||
if (mInUse) {
|
||||
// someone already has acquired it.
|
||||
return false;
|
||||
}
|
||||
nState |= NSTATE_INUSE;
|
||||
mInUse = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* package */ synchronized void free() {
|
||||
nState &= NSTATE_INUSE_RESETMASK;
|
||||
/* package */ synchronized void release() {
|
||||
mInUse = false;
|
||||
}
|
||||
|
||||
/* package */ void release(int type) {
|
||||
if (type != DatabaseUtils.STATEMENT_SELECT && type != DatabaseUtils.STATEMENT_UPDATE) {
|
||||
// it is not cached. release its memory from the database.
|
||||
releaseFromDatabase();
|
||||
return;
|
||||
}
|
||||
// if in cache, reset its in-use flag
|
||||
if (!mDatabase.mCache.releaseBackToCache(this)) {
|
||||
// not in cache. release its memory from the database.
|
||||
releaseFromDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ synchronized void releaseIfNotInUse() {
|
||||
nState |= NSTATE_EVICTED_FROM_CACHE;
|
||||
// if it is not in use, release its memory from the database
|
||||
if ((nState & NSTATE_INUSE) == 0) {
|
||||
releaseFromDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
// only for testing purposes
|
||||
/* package */ synchronized boolean isInUse() {
|
||||
return (nState & NSTATE_INUSE) > 0;
|
||||
}
|
||||
|
||||
/* package */ synchronized SQLiteCompiledSql setState(int val) {
|
||||
nState = nState & val;
|
||||
return this; // for chaining
|
||||
return mInUse;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,18 +125,11 @@ import android.util.Log;
|
||||
try {
|
||||
if (nStatement == 0) return;
|
||||
// finalizer should NEVER get called
|
||||
// but if the database itself is not closed and is GC'ed, then
|
||||
// all sub-objects attached to the database could end up getting GC'ed too.
|
||||
// in that case, don't print any warning.
|
||||
if ((nState & NSTATE_INUSE) == 0) {
|
||||
// no need to print warning
|
||||
} else {
|
||||
int len = mSqlStmt.length();
|
||||
Log.w(TAG, "Releasing SQL statement in finalizer. " +
|
||||
"Could be due to close() not being called on the cursor or on the database. " +
|
||||
toString(), mStackTrace);
|
||||
}
|
||||
releaseFromDatabase();
|
||||
int len = mSqlStmt.length();
|
||||
Log.w(TAG, "Releasing statement in a finalizer. Please ensure " +
|
||||
"that you explicitly call close() on your cursor: " +
|
||||
mSqlStmt.substring(0, (len > 100) ? 100 : len), mStackTrace);
|
||||
releaseSqlStatement();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
@@ -176,27 +140,6 @@ import android.util.Log;
|
||||
StringBuilder buff = new StringBuilder();
|
||||
buff.append(" nStatement=");
|
||||
buff.append(nStatement);
|
||||
if ((nState & NSTATE_CACHEABLE) > 0) {
|
||||
buff.append(",cacheable");
|
||||
}
|
||||
if ((nState & NSTATE_IN_CACHE) > 0) {
|
||||
buff.append(",cached");
|
||||
}
|
||||
if ((nState & NSTATE_INUSE) > 0) {
|
||||
buff.append(",in_use");
|
||||
}
|
||||
if ((nState & NSTATE_CLOSE_NOOP) > 0) {
|
||||
buff.append(",no_op_close");
|
||||
}
|
||||
if ((nState & NSTATE_EVICTED_FROM_CACHE) > 0) {
|
||||
buff.append(",evicted_from_cache");
|
||||
}
|
||||
if ((nState & NSTATE_CACHE_DEALLOC) > 0) {
|
||||
buff.append(",dealloc_cache");
|
||||
}
|
||||
if ((nState & NSTATE_IN_FINALIZER_Q) > 0) {
|
||||
buff.append(",in dbFInalizerQ");
|
||||
}
|
||||
buff.append(", db=");
|
||||
buff.append(mDatabase.getPath());
|
||||
buff.append(", db_connectionNum=");
|
||||
|
||||
@@ -41,6 +41,7 @@ import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
@@ -259,11 +260,60 @@ public class SQLiteDatabase extends SQLiteClosable {
|
||||
private final WeakHashMap<SQLiteClosable, Object> mPrograms;
|
||||
|
||||
/**
|
||||
* Absolute max value that can be set by {@link #setMaxSqlCacheSize(int)}.
|
||||
* Size of each prepared-statement is between 1K - 6K, depending on the complexity of the
|
||||
* for each instance of this class, a LRU 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 #setMaxSqlCacheSize(int)}).
|
||||
*/
|
||||
// default statement-cache size per database connection ( = instance of this class)
|
||||
private int mMaxSqlCacheSize = 25;
|
||||
/* package */ final Map<String, SQLiteCompiledSql> mCompiledQueries =
|
||||
new LinkedHashMap<String, SQLiteCompiledSql>(mMaxSqlCacheSize + 1, 0.75f, true) {
|
||||
@Override
|
||||
public boolean removeEldestEntry(Map.Entry<String, SQLiteCompiledSql> eldest) {
|
||||
// eldest = least-recently used entry
|
||||
// if it needs to be removed to accommodate a new entry,
|
||||
// close {@link SQLiteCompiledSql} represented by this entry, if not in use
|
||||
// and then let it be removed from the Map.
|
||||
// when this is called, the caller must be trying to add a just-compiled stmt
|
||||
// to cache; i.e., caller should already have acquired database lock AND
|
||||
// the lock on mCompiledQueries. do as assert of these two 2 facts.
|
||||
verifyLockOwner();
|
||||
if (this.size() <= mMaxSqlCacheSize) {
|
||||
// cache is not full. nothing needs to be removed
|
||||
return false;
|
||||
}
|
||||
// cache is full. eldest will be removed.
|
||||
SQLiteCompiledSql entry = eldest.getValue();
|
||||
if (!entry.isInUse()) {
|
||||
// this {@link SQLiteCompiledSql} is not in use. release it.
|
||||
entry.releaseSqlStatement();
|
||||
}
|
||||
// return true, so that this entry is removed automatically by the caller.
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* absolute max value that can be set by {@link #setMaxSqlCacheSize(int)}
|
||||
* size of each prepared-statement is between 1K - 6K, depending on the complexity of the
|
||||
* SQL statement & schema.
|
||||
*/
|
||||
public static final int MAX_SQL_CACHE_SIZE = 100;
|
||||
private int mCacheFullWarnings;
|
||||
private static final int MAX_WARNINGS_ON_CACHESIZE_CONDITION = 1;
|
||||
|
||||
/** maintain stats about number of cache hits and misses */
|
||||
private int mNumCacheHits;
|
||||
private int mNumCacheMisses;
|
||||
|
||||
/** Used to find out where this object was created in case it never got closed. */
|
||||
private final Throwable mStackTrace;
|
||||
@@ -272,9 +322,6 @@ public class SQLiteDatabase extends SQLiteClosable {
|
||||
private static final String LOG_SLOW_QUERIES_PROPERTY = "db.log.slow_query_threshold";
|
||||
private final int mSlowQueryThreshold;
|
||||
|
||||
/** the LRU cache */
|
||||
/* package */ final SQLiteCache mCache = new SQLiteCache(this);
|
||||
|
||||
/** stores the list of statement ids that need to be finalized by sqlite */
|
||||
private final ArrayList<Integer> mClosedStatementIds = new ArrayList<Integer>();
|
||||
|
||||
@@ -1040,7 +1087,7 @@ public class SQLiteDatabase extends SQLiteClosable {
|
||||
* to be closed. sqlite doesn't let a database close if there are
|
||||
* any unfinalized statements - such as the compiled-sql objects in mCompiledQueries.
|
||||
*/
|
||||
mCache.dealloc();
|
||||
deallocCachedSqlStatements();
|
||||
|
||||
Iterator<Map.Entry<SQLiteClosable, Object>> iter = mPrograms.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
@@ -2043,20 +2090,113 @@ public class SQLiteDatabase extends SQLiteClosable {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================================================================
|
||||
*
|
||||
* The following methods deal with compiled-sql cache
|
||||
* ============================================================================
|
||||
*/
|
||||
/**
|
||||
* Adds the given SQL and its compiled-statement-id-returned-by-sqlite to the
|
||||
* cache of compiledQueries attached to 'this'.
|
||||
* <p>
|
||||
* 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).
|
||||
*/
|
||||
/* package */ void addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) {
|
||||
synchronized(mCompiledQueries) {
|
||||
// don't insert the new mapping if a mapping already exists
|
||||
if (mCompiledQueries.containsKey(sql)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCompiledQueries.size() == mMaxSqlCacheSize) {
|
||||
/*
|
||||
* cache size of {@link #mMaxSqlCacheSize} is not enough for this app.
|
||||
* log a warning.
|
||||
* chances are it is NOT using ? for bindargs - or cachesize is too small.
|
||||
*/
|
||||
if (++mCacheFullWarnings == MAX_WARNINGS_ON_CACHESIZE_CONDITION) {
|
||||
Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " +
|
||||
getPath() + ". Use setMaxSqlCacheSize() to increase cachesize. ");
|
||||
}
|
||||
}
|
||||
/* add the given SQLiteCompiledSql compiledStatement to cache.
|
||||
* no need to worry about the cache size - because {@link #mCompiledQueries}
|
||||
* self-limits its size to {@link #mMaxSqlCacheSize}.
|
||||
*/
|
||||
mCompiledQueries.put(sql, compiledStatement);
|
||||
if (SQLiteDebug.DEBUG_SQL_CACHE) {
|
||||
Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" +
|
||||
mCompiledQueries.size() + "|" + sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** package-level access for testing purposes */
|
||||
/* package */ void deallocCachedSqlStatements() {
|
||||
synchronized (mCompiledQueries) {
|
||||
for (SQLiteCompiledSql compiledSql : mCompiledQueries.values()) {
|
||||
compiledSql.releaseSqlStatement();
|
||||
}
|
||||
mCompiledQueries.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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 + "|" + sql);
|
||||
}
|
||||
return compiledStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum size of the prepared-statement cache for this database.
|
||||
* (size of the cache = number of compiled-sql-statements stored in the cache).
|
||||
*<p>
|
||||
* Maximum cache size can ONLY be increased from its current size (default = 25).
|
||||
* Maximum cache size can ONLY be increased from its current size (default = 10).
|
||||
* If this method is called with smaller size than the current maximum value,
|
||||
* then IllegalStateException is thrown.
|
||||
*<p>
|
||||
* This method is thread-safe.
|
||||
*
|
||||
* @param cacheSize the size of the cache. can be (0 to {@link #MAX_SQL_CACHE_SIZE})
|
||||
* @throws IllegalStateException if input cacheSize > {@link #MAX_SQL_CACHE_SIZE} or
|
||||
* the value set with previous setMaxSqlCacheSize() call.
|
||||
*/
|
||||
public void setMaxSqlCacheSize(int cacheSize) {
|
||||
mCache.setMaxSqlCacheSize(cacheSize);
|
||||
public synchronized void setMaxSqlCacheSize(int cacheSize) {
|
||||
if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) {
|
||||
throw new IllegalStateException("expected value between 0 and " + MAX_SQL_CACHE_SIZE);
|
||||
} else if (cacheSize < mMaxSqlCacheSize) {
|
||||
throw new IllegalStateException("cannot set cacheSize to a value less than the value " +
|
||||
"set with previous setMaxSqlCacheSize() call.");
|
||||
}
|
||||
mMaxSqlCacheSize = cacheSize;
|
||||
}
|
||||
|
||||
/* package */ boolean isSqlInStatementCache(String sql) {
|
||||
synchronized (mCompiledQueries) {
|
||||
return mCompiledQueries.containsKey(sql);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void finalizeStatementLater(int id) {
|
||||
@@ -2212,19 +2352,17 @@ public class SQLiteDatabase extends SQLiteClosable {
|
||||
*
|
||||
* @param size the value the connection handle pool size should be set to.
|
||||
*/
|
||||
public void setConnectionPoolSize(int size) {
|
||||
synchronized(this) {
|
||||
if (mConnectionPool == null) {
|
||||
throw new IllegalStateException("connection pool not enabled");
|
||||
}
|
||||
int i = mConnectionPool.getMaxPoolSize();
|
||||
if (size < i) {
|
||||
throw new IllegalArgumentException(
|
||||
"cannot set max pool size to a value less than the current max value(=" +
|
||||
i + ")");
|
||||
}
|
||||
mConnectionPool.setMaxPoolSize(size);
|
||||
public synchronized void setConnectionPoolSize(int size) {
|
||||
if (mConnectionPool == null) {
|
||||
throw new IllegalStateException("connection pool not enabled");
|
||||
}
|
||||
int i = mConnectionPool.getMaxPoolSize();
|
||||
if (size < i) {
|
||||
throw new IllegalArgumentException(
|
||||
"cannot set max pool size to a value less than the current max value(=" +
|
||||
i + ")");
|
||||
}
|
||||
mConnectionPool.setMaxPoolSize(size);
|
||||
}
|
||||
|
||||
/* package */ SQLiteDatabase createPoolConnection(short connectionNum) {
|
||||
@@ -2340,16 +2478,16 @@ public class SQLiteDatabase extends SQLiteClosable {
|
||||
}
|
||||
if (pageCount > 0) {
|
||||
dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
|
||||
lookasideUsed, db.mCache.getCacheHitNum(),
|
||||
db.mCache.getCacheMissNum(), db.mCache.getCachesize()));
|
||||
lookasideUsed, db.mNumCacheHits, db.mNumCacheMisses,
|
||||
db.mCompiledQueries.size()));
|
||||
}
|
||||
}
|
||||
// if there are pooled connections, return the cache stats for them also.
|
||||
if (db.mConnectionPool != null) {
|
||||
for (SQLiteDatabase pDb : db.mConnectionPool.getConnectionList()) {
|
||||
dbStatsList.add(new DbStats("(pooled # " + pDb.mConnectionNum + ") "
|
||||
+ lastnode, 0, 0, 0, pDb.mCache.getCacheHitNum(),
|
||||
pDb.mCache.getCacheMissNum(), pDb.mCache.getCachesize()));
|
||||
+ lastnode, 0, 0, 0, pDb.mNumCacheHits, pDb.mNumCacheMisses,
|
||||
pDb.mCompiledQueries.size()));
|
||||
}
|
||||
}
|
||||
} catch (SQLiteException e) {
|
||||
|
||||
@@ -110,6 +110,42 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
}
|
||||
}
|
||||
|
||||
private void compileSql() {
|
||||
// only cache CRUD statements
|
||||
if (mStatementType != DatabaseUtils.STATEMENT_SELECT &&
|
||||
mStatementType != DatabaseUtils.STATEMENT_UPDATE) {
|
||||
mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
|
||||
nStatement = mCompiledSql.nStatement;
|
||||
// since it is not in the cache, no need to acquire() it.
|
||||
return;
|
||||
}
|
||||
|
||||
mCompiledSql = mDatabase.getCompiledStatementForSql(mSql);
|
||||
if (mCompiledSql == null) {
|
||||
// create a new compiled-sql obj
|
||||
mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
|
||||
|
||||
// add it to the cache of compiled-sqls
|
||||
// but before adding it and thus making it available for anyone else to use it,
|
||||
// make sure it is acquired by me.
|
||||
mCompiledSql.acquire();
|
||||
mDatabase.addToCompiledQueries(mSql, mCompiledSql);
|
||||
} else {
|
||||
// it is already in compiled-sql cache.
|
||||
// try to acquire the object.
|
||||
if (!mCompiledSql.acquire()) {
|
||||
int last = mCompiledSql.nStatement;
|
||||
// the SQLiteCompiledSql in cache is in use by some other SQLiteProgram object.
|
||||
// we can't have two different SQLiteProgam objects can't share the same
|
||||
// CompiledSql object. create a new one.
|
||||
// finalize it when I am done with it in "this" object.
|
||||
mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
|
||||
// since it is not in the cache, no need to acquire() it.
|
||||
}
|
||||
}
|
||||
nStatement = mCompiledSql.nStatement;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAllReferencesReleased() {
|
||||
release();
|
||||
@@ -127,7 +163,16 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
if (mCompiledSql == null) {
|
||||
return;
|
||||
}
|
||||
mCompiledSql.release(mStatementType);
|
||||
synchronized(mDatabase.mCompiledQueries) {
|
||||
if (!mDatabase.mCompiledQueries.containsValue(mCompiledSql)) {
|
||||
// it is NOT in compiled-sql cache. i.e., responsibility of
|
||||
// releasing this statement is on me.
|
||||
mCompiledSql.releaseSqlStatement();
|
||||
} else {
|
||||
// it is in compiled-sql cache. reset its CompiledSql#mInUse flag
|
||||
mCompiledSql.release();
|
||||
}
|
||||
}
|
||||
mCompiledSql = null;
|
||||
nStatement = 0;
|
||||
}
|
||||
@@ -288,9 +333,6 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
synchronized (this) {
|
||||
mBindArgs = null;
|
||||
if (nHandle == 0 || !mDatabase.isOpen()) {
|
||||
if (mCompiledSql != null) {
|
||||
mCompiledSql.setState(SQLiteCompiledSql.NSTATE_CLOSE_NOOP);
|
||||
}
|
||||
return;
|
||||
}
|
||||
releaseReference();
|
||||
@@ -307,8 +349,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
|
||||
/* package */ synchronized void compileAndbindAllArgs() {
|
||||
if (nStatement == 0) {
|
||||
// SQL statement is not compiled yet. compile it now.
|
||||
mCompiledSql = SQLiteCompiledSql.get(mDatabase, mSql, mStatementType);
|
||||
nStatement = mCompiledSql.nStatement;
|
||||
compileSql();
|
||||
}
|
||||
if (mBindArgs == null) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user