diff --git a/api/current.xml b/api/current.xml
index 310944f6c3e72..c17f2acdd03a8 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -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"
diff --git a/core/java/android/database/sqlite/DatabaseConnectionPool.java b/core/java/android/database/sqlite/DatabaseConnectionPool.java
index aede438657840..54b06054f86c4 100644
--- a/core/java/android/database/sqlite/DatabaseConnectionPool.java
+++ b/core/java/android/database/sqlite/DatabaseConnectionPool.java
@@ -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;
}
diff --git a/core/java/android/database/sqlite/SQLiteCache.java b/core/java/android/database/sqlite/SQLiteCache.java
deleted file mode 100644
index 049fb626197f7..0000000000000
--- a/core/java/android/database/sqlite/SQLiteCache.java
+++ /dev/null
@@ -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.
- *
- *
- * - 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 compiled-sql statements to the
- * database connections.
- *
- * 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 mCompiledQueries =
- new LinkedHashMap(mMaxSqlCacheSize + 1, 0.75f, true) {
- @Override
- public boolean removeEldestEntry(Map.Entry 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.
- *
- * 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.
- *
- * 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 getKeys() {
- return mCompiledQueries.keySet();
- }
-}
diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java
index 120d090a881ea..0b5a4dff3d4d5 100644
--- a/core/java/android/database/sqlite/SQLiteCompiledSql.java
+++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java
@@ -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.
+ *
+ * Note: this method acquires the database lock.
+ *
+ * @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=");
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 70bd3d8304787..6937da01f6458 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -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 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 mCompiledQueries =
+ new LinkedHashMap(mMaxSqlCacheSize + 1, 0.75f, true) {
+ @Override
+ public boolean removeEldestEntry(Map.Entry 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 mClosedStatementIds = new ArrayList();
@@ -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> 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'.
+ *
+ * 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).
*
- * 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.
+ *
+ * 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) {
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index b3903693dfa44..c7c0c79a31649 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -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;
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCacheTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCacheTest.java
deleted file mode 100644
index 245981531c83b..0000000000000
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteCacheTest.java
+++ /dev/null
@@ -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 stmtObjs = new ArrayList();
- ArrayList sqlStrings = new ArrayList();
- 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());
- }
-}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCompiledSqlTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCompiledSqlTest.java
deleted file mode 100644
index 1606cf6bfd9a0..0000000000000
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteCompiledSqlTest.java
+++ /dev/null
@@ -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));
- }
-}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index 3249cb63f45f9..86eda710266f6 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -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 stmtObjs = new ArrayList();
+ ArrayList sqlStrings = new ArrayList();
+ 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());
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
index bdd6d5cc57e45..217545f9cf6c0 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
@@ -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.
- *
- * 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