Revert "caching code retooled to reduce locking + handle SMP"
This reverts commit 992f7d52fa.
Change-Id: Ia5b789d1b2195d6ce43baffe24296c857f9b30f6
This commit is contained in:
@@ -65918,7 +65918,7 @@
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
synchronized="true"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
@@ -65957,7 +65957,7 @@
|
||||
return="void"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
synchronized="true"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,176 +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.content.Context;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteDatabaseTest.ClassToTestSqlCompilationAndCaching;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class SQLiteCacheTest extends AndroidTestCase {
|
||||
private SQLiteDatabase mDatabase;
|
||||
private File mDatabaseFile;
|
||||
private static final String TABLE_NAME = "testCache";
|
||||
private SQLiteCache mCache;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
|
||||
mDatabaseFile = new File(dbDir, "test.db");
|
||||
if (mDatabaseFile.exists()) {
|
||||
mDatabaseFile.delete();
|
||||
}
|
||||
mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
|
||||
assertNotNull(mDatabase);
|
||||
mCache = mDatabase.mCache;
|
||||
assertNotNull(mCache);
|
||||
|
||||
// create a test table
|
||||
mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
mDatabase.lock();
|
||||
// flush the above statement from cache and close all the pending statements to be released
|
||||
mCache.dealloc();
|
||||
mDatabase.closePendingStatements();
|
||||
mDatabase.unlock();
|
||||
assertEquals(0, mDatabase.getQueuedUpStmtList().size());
|
||||
mDatabase.close();
|
||||
mDatabaseFile.delete();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* do N+1 queries - and when the 0th entry is removed from LRU cache due to the
|
||||
* insertion of (N+1)th entry, make sure 0th entry is closed
|
||||
*/
|
||||
@SmallTest
|
||||
public void testLruCaching() {
|
||||
mDatabase.disableWriteAheadLogging();
|
||||
// set cache size
|
||||
int N = 25;
|
||||
mDatabase.setMaxSqlCacheSize(N);
|
||||
|
||||
ArrayList<Integer> stmtObjs = new ArrayList<Integer>();
|
||||
ArrayList<String> sqlStrings = new ArrayList<String>();
|
||||
int stmt0 = 0;
|
||||
for (int i = 0; i < N+1; i++) {
|
||||
String s = "insert into " + TABLE_NAME + " values(" + i + ",?);";
|
||||
sqlStrings.add(s);
|
||||
ClassToTestSqlCompilationAndCaching c =
|
||||
ClassToTestSqlCompilationAndCaching.create(mDatabase, s);
|
||||
int n = c.getSqlStatementId();
|
||||
stmtObjs.add(i, n);
|
||||
if (i == 0) {
|
||||
// save the statementId of this obj. we want to make sure it is thrown out of
|
||||
// the cache at the end of this test.
|
||||
stmt0 = n;
|
||||
}
|
||||
c.close();
|
||||
}
|
||||
assertEquals(N, mCache.getCachesize());
|
||||
// is 0'th entry out of the cache? it should be in the list of statementIds
|
||||
// corresponding to the pre-compiled sql statements to be finalized.
|
||||
assertTrue(mDatabase.getQueuedUpStmtList().contains(stmt0));
|
||||
for (int i = 1; i < N+1; i++) {
|
||||
SQLiteCompiledSql compSql =
|
||||
mDatabase.mCache.getCompiledStatementForSql(sqlStrings.get(i));
|
||||
assertNotNull(compSql);
|
||||
assertTrue(stmtObjs.contains(compSql.nStatement));
|
||||
}
|
||||
assertEquals(N, mCache.getCachesize());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache should only have Select / Insert / Update / Delete / Replace.
|
||||
*/
|
||||
@SmallTest
|
||||
public void testCachingOfCRUDstatementsOnly() {
|
||||
ClassToTestSqlCompilationAndCaching c;
|
||||
// do some CRUD sql
|
||||
int crudSqlNum = 7 * 4;
|
||||
mDatabase.setMaxSqlCacheSize(crudSqlNum);
|
||||
for (int i = 0; i < crudSqlNum / 4; i++) {
|
||||
c= ClassToTestSqlCompilationAndCaching.create(mDatabase,
|
||||
"insert into " + TABLE_NAME + " values(" + i + ",?);");
|
||||
c.close();
|
||||
c = ClassToTestSqlCompilationAndCaching.create(mDatabase,
|
||||
"update " + TABLE_NAME + " set i = " + i);
|
||||
c.close();
|
||||
c = ClassToTestSqlCompilationAndCaching.create(mDatabase,
|
||||
"select * from " + TABLE_NAME + " where i = " + i);
|
||||
c.close();
|
||||
c= ClassToTestSqlCompilationAndCaching.create(mDatabase,
|
||||
"delete from " + TABLE_NAME + " where i = " + i);
|
||||
c.close();
|
||||
}
|
||||
// do some non-CRUD sql
|
||||
c = ClassToTestSqlCompilationAndCaching.create(mDatabase,
|
||||
"create table j (i int);");
|
||||
c.close();
|
||||
c = ClassToTestSqlCompilationAndCaching.create(mDatabase,
|
||||
"pragma database_list;");
|
||||
c.close();
|
||||
c = ClassToTestSqlCompilationAndCaching.create(mDatabase,
|
||||
"begin transaction;");
|
||||
c.close();
|
||||
c = ClassToTestSqlCompilationAndCaching.create(mDatabase,
|
||||
"commit;");
|
||||
c.close();
|
||||
c = ClassToTestSqlCompilationAndCaching.create(mDatabase,
|
||||
"attach database \"blah\" as blah_db;");
|
||||
c.close();
|
||||
// cache size should be crudSqlNum
|
||||
assertEquals(crudSqlNum, mCache.getCachesize());
|
||||
// everything in the cache should be a CRUD sql statement
|
||||
for (String s : mCache.getKeys()) {
|
||||
int type = DatabaseUtils.getSqlStatementType(s);
|
||||
assertTrue(type == DatabaseUtils.STATEMENT_SELECT ||
|
||||
type == DatabaseUtils.STATEMENT_UPDATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* calling SQLiteCache.getCompiledStatementForSql() should reserve the cached-entry
|
||||
* for the caller, if the entry exists
|
||||
*/
|
||||
@SmallTest
|
||||
public void testGetShouldReserveEntry() {
|
||||
String sql = "insert into " + TABLE_NAME + " values(1,?);";
|
||||
ClassToTestSqlCompilationAndCaching c =
|
||||
ClassToTestSqlCompilationAndCaching.create(mDatabase, sql);
|
||||
c.close();
|
||||
SQLiteCompiledSql compiledSql = mCache.getCompiledStatementForSql(sql);
|
||||
assertNotNull(compiledSql);
|
||||
assertTrue(compiledSql.isInUse());
|
||||
// get entry for the same sql again. should get null and a warning in log
|
||||
assertNull(mCache.getCompiledStatementForSql(sql));
|
||||
compiledSql.free();
|
||||
assertFalse(compiledSql.isInUse());
|
||||
}
|
||||
}
|
||||
@@ -1,99 +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.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteDatabaseTest.ClassToTestSqlCompilationAndCaching;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class SQLiteCompiledSqlTest extends AndroidTestCase {
|
||||
private SQLiteDatabase mDatabase;
|
||||
private File mDatabaseFile;
|
||||
private static final String TABLE_NAME = "testCache";
|
||||
private SQLiteCache mCache;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
|
||||
mDatabaseFile = new File(dbDir, "sqlitecursor_test.db");
|
||||
if (mDatabaseFile.exists()) {
|
||||
mDatabaseFile.delete();
|
||||
}
|
||||
mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
|
||||
assertNotNull(mDatabase);
|
||||
mCache = mDatabase.mCache;
|
||||
assertNotNull(mCache);
|
||||
|
||||
// create a test table
|
||||
mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
mDatabase.close();
|
||||
mDatabaseFile.delete();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* releaseIfNotInUse() should release only if it is not in use
|
||||
*/
|
||||
@SmallTest
|
||||
public void testReleaseIfNotInUse() {
|
||||
ClassToTestSqlCompilationAndCaching c;
|
||||
// do some CRUD sql
|
||||
int crudSqlNum = 20 * 4;
|
||||
mDatabase.setMaxSqlCacheSize(crudSqlNum);
|
||||
for (int i = 0; i < crudSqlNum / 4; i++) {
|
||||
c = ClassToTestSqlCompilationAndCaching.create(mDatabase,
|
||||
"insert into " + TABLE_NAME + " values(" + i + ",?);");
|
||||
c.close();
|
||||
c = ClassToTestSqlCompilationAndCaching.create(mDatabase,
|
||||
"update " + TABLE_NAME + " set i = " + i);
|
||||
c.close();
|
||||
c = ClassToTestSqlCompilationAndCaching.create(mDatabase,
|
||||
"select * from " + TABLE_NAME + " where i = " + i);
|
||||
c.close();
|
||||
c = ClassToTestSqlCompilationAndCaching.create(mDatabase,
|
||||
"delete from " + TABLE_NAME + " where i = " + i);
|
||||
c.close();
|
||||
}
|
||||
assertEquals(crudSqlNum, mCache.getCachesize());
|
||||
String sql = "insert into " + TABLE_NAME + " values(1,?);";
|
||||
SQLiteCompiledSql compiledSql = mCache.getCompiledStatementForSql(sql);
|
||||
assertNotNull(compiledSql);
|
||||
assertTrue(compiledSql.isInUse());
|
||||
// the object is in use. try to release it
|
||||
compiledSql.releaseIfNotInUse();
|
||||
// compiledSql should not be released yet
|
||||
int stmtId = compiledSql.nStatement;
|
||||
assertTrue(stmtId > 0);
|
||||
// free the object and call releaseIfNotInUse() again - and it should work this time
|
||||
compiledSql.free();
|
||||
assertFalse(compiledSql.isInUse());
|
||||
compiledSql.releaseIfNotInUse();
|
||||
assertEquals(0, compiledSql.nStatement);
|
||||
assertTrue(mDatabase.getQueuedUpStmtList().contains(stmtId));
|
||||
}
|
||||
}
|
||||
@@ -447,6 +447,42 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testLruCachingOfSqliteCompiledSqlObjs() {
|
||||
createTableAndClearCache();
|
||||
// set cache size
|
||||
int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
|
||||
mDatabase.setMaxSqlCacheSize(N);
|
||||
|
||||
// do N+1 queries - and when the 0th entry is removed from LRU cache due to the
|
||||
// insertion of (N+1)th entry, make sure 0th entry is closed
|
||||
ArrayList<Integer> stmtObjs = new ArrayList<Integer>();
|
||||
ArrayList<String> sqlStrings = new ArrayList<String>();
|
||||
int stmt0 = 0;
|
||||
for (int i = 0; i < N+1; i++) {
|
||||
String s = "insert into test values(" + i + ",?);";
|
||||
sqlStrings.add(s);
|
||||
ClassToTestSqlCompilationAndCaching c =
|
||||
ClassToTestSqlCompilationAndCaching.create(mDatabase, s);
|
||||
int n = c.getSqlStatementId();
|
||||
stmtObjs.add(i, n);
|
||||
if (i == 0) {
|
||||
// save the statementId of this obj. we want to make sure it is thrown out of
|
||||
// the cache at the end of this test.
|
||||
stmt0 = n;
|
||||
}
|
||||
c.close();
|
||||
}
|
||||
// is 0'th entry out of the cache? it should be in the list of statementIds
|
||||
// corresponding to the pre-compiled sql statements to be finalized.
|
||||
assertTrue(mDatabase.getQueuedUpStmtList().contains(stmt0));
|
||||
for (int i = 1; i < N+1; i++) {
|
||||
SQLiteCompiledSql compSql = mDatabase.getCompiledStatementForSql(sqlStrings.get(i));
|
||||
assertNotNull(compSql);
|
||||
assertTrue(stmtObjs.contains(compSql.nStatement));
|
||||
}
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testDbCloseReleasingAllCachedSql() {
|
||||
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
|
||||
@@ -467,7 +503,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
|
||||
mDatabase.enableWriteAheadLogging();
|
||||
mDatabase.lock();
|
||||
// flush the above statement from cache and close all the pending statements to be released
|
||||
mDatabase.mCache.dealloc();
|
||||
mDatabase.deallocCachedSqlStatements();
|
||||
mDatabase.closePendingStatements();
|
||||
mDatabase.unlock();
|
||||
assertEquals(0, mDatabase.getQueuedUpStmtList().size());
|
||||
|
||||
@@ -53,12 +53,6 @@ public class SQLiteStatementTest extends AndroidTestCase {
|
||||
* Start 2 threads to repeatedly execute the above SQL statement.
|
||||
* Even though 2 threads are executing the same SQL, they each should get their own copy of
|
||||
* prepared SQL statement id and there SHOULD NOT be an error from sqlite or android.
|
||||
*<p>
|
||||
* This method will produce a lot of the following warnings:
|
||||
* 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.
|
||||
* That is expected behavior.
|
||||
*
|
||||
* @throws InterruptedException thrown if the test threads started by this test are interrupted
|
||||
*/
|
||||
@LargeTest
|
||||
|
||||
Reference in New Issue
Block a user