Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5a03f36e authored by Vasu Nori's avatar Vasu Nori
Browse files

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

parent 53e9c126
Loading
Loading
Loading
Loading
+27 −3
Original line number Diff line number Diff line
@@ -50825,6 +50825,17 @@
<exception name="SQLException" type="android.database.SQLException">
</exception>
</method>
<method name="resetCompiledSqlCache"
 return="void"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
</method>
<method name="setLocale"
 return="void"
 abstract="false"
@@ -50851,6 +50862,19 @@
<parameter name="lockingEnabled" type="boolean">
</parameter>
</method>
<method name="setMaxSqlCacheSize"
 return="void"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="cacheSize" type="int">
</parameter>
</method>
<method name="setMaximumSize"
 return="long"
 abstract="false"
@@ -51411,7 +51435,7 @@
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 deprecated="deprecated"
 visibility="protected"
>
<parameter name="sql" type="java.lang.String">
@@ -51510,7 +51534,7 @@
 synchronized="false"
 static="false"
 final="true"
 deprecated="not deprecated"
 deprecated="deprecated"
 visibility="protected"
>
<parameter name="sql" type="java.lang.String">
@@ -51523,7 +51547,7 @@
 synchronized="false"
 static="false"
 final="true"
 deprecated="not deprecated"
 deprecated="deprecated"
 visibility="protected"
>
</method>
+111 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.database.sqlite;

import android.util.Log;

/**
 * This class encapsulates compilation of sql statement and release of the compiled statement obj.
 * Once a sql statement is compiled, it is cached in {@link SQLiteDatabase}
 * and it is released in one of the 2 following ways
 * 1. when {@link SQLiteDatabase} object is closed.
 * 2. dalvikVM wants to reclaim some memory and releases it from the cache in
 * {@link SQLiteDatabase}.
 */
/* package */ class SQLiteCompiledSql {

    /** The database this program is compiled against. */
    /* package */ SQLiteDatabase mDatabase;

    /**
     * Native linkage, do not modify. This comes from the database.
     */
    /* package */ int nHandle = 0;

    /**
     * Native linkage, do not modify. When non-0 this holds a reference to a valid
     * sqlite3_statement object. It is only updated by the native code, but may be
     * checked in this class when the database lock is held to determine if there
     * is a valid native-side program or not.
     */
    /* package */ int nStatement = 0;

    /* package */ SQLiteCompiledSql(SQLiteDatabase db, String sql) {
        mDatabase = db;
        this.nHandle = db.mNativeHandle;
        compile(sql, true);
    }

    /**
     * Compiles the given SQL into a SQLite byte code program using sqlite3_prepare_v2(). If
     * this method has been called previously without a call to close and forCompilation is set
     * to false the previous compilation will be used. Setting forceCompilation to true will
     * always re-compile the program and should be done if you pass differing SQL strings to this
     * method.
     *
     * <P>Note: this method acquires the database lock.</P>
     *
     * @param sql the SQL string to compile
     * @param forceCompilation forces the SQL to be recompiled in the event that there is an
     *  existing compiled SQL program already around
     */
    private void compile(String sql, boolean forceCompilation) {
        // Only compile if we don't have a valid statement already or the caller has
        // explicitly requested a recompile.
        if (forceCompilation) {
            mDatabase.lock();
            try {
                // Note that the native_compile() takes care of destroying any previously
                // existing programs before it compiles.
                native_compile(sql);
            } finally {
                mDatabase.unlock();
            }
        }
    }

    /* package */ void releaseSqlStatement() {
        // Note that native_finalize() checks to make sure that nStatement is
        // non-null before destroying it.
        if (nStatement != 0) {
            try {
                mDatabase.lock();
                native_finalize();
                nStatement = 0;
            } finally {
                mDatabase.unlock();
            }
        }
    }

    /**
     * Make sure that the native resource is cleaned up.
     */
    @Override
    protected void finalize() {
        releaseSqlStatement();
    }

    /**
     * Compiles SQL into a SQLite program.
     *
     * <P>The database lock must be held when calling this method.
     * @param sql The SQL to compile.
     */
    private final native void native_compile(String sql);
    private final native void native_finalize();
}
+157 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.database.sqlite;

import com.google.android.collect.Maps;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.DatabaseUtils;
@@ -29,6 +31,7 @@ import android.util.EventLog;
import android.util.Log;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
@@ -217,6 +220,34 @@ public class SQLiteDatabase extends SQLiteClosable {

    private WeakHashMap<SQLiteClosable, Object> mPrograms;

    /**
     * for each instance of this class, a cache is maintained to store
     * the compiled query statement ids returned by sqlite database.
     *     key = sql statement with "?" for bind args
     *     value = {@link SQLiteCompiledSql}
     * If an application opens the database and keeps it open during its entire life, then
     * there will not be an overhead of compilation of sql statements by sqlite.
     *
     * why is this cache NOT static? because sqlite attaches compiledsql statements to the
     * struct created when {@link SQLiteDatabase#openDatabase(String, CursorFactory, int)} is
     * invoked.
     *
     * this cache has an upper limit of mMaxSqlCacheSize (settable by calling the method
     * (@link setMaxCacheSize(int)}). its default is 0 - i.e., no caching by default because
     * most of the apps don't use "?" syntax in their sql, caching is not useful for them.
     */
    private Map<String, SQLiteCompiledSql> mCompiledQueries = Maps.newHashMap();
    private int mMaxSqlCacheSize = 0; // no caching by default
    private static final int MAX_SQL_CACHE_SIZE = 1000;

    /** maintain stats about number of cache hits and misses */
    private int mNumCacheHits;
    private int mNumCacheMisses;

    /** the following 2 members maintain the time when a database is opened and closed */
    private String mTimeOpened = null;
    private String mTimeClosed = null;

    private final RuntimeException mLeakedException;

    // package visible, since callers will access directly to minimize overhead in the case
@@ -251,6 +282,9 @@ public class SQLiteDatabase extends SQLiteClosable {
    @Override
    protected void onAllReferencesReleased() {
        if (isOpen()) {
            if (SQLiteDebug.DEBUG_SQL_CACHE) {
                mTimeClosed = getTime();
            }
            dbclose();
        }
    }
@@ -798,6 +832,13 @@ public class SQLiteDatabase extends SQLiteClosable {
                program.onAllReferencesReleasedFromContainer();
            }
        }

        // finalize all compiled sql statement objects in compiledQueries cache
        synchronized (mCompiledQueries) {
            for (SQLiteCompiledSql compiledStatement : mCompiledQueries.values()) {
                compiledStatement.releaseSqlStatement();
            }
        }
    }

    /**
@@ -1695,16 +1736,26 @@ public class SQLiteDatabase extends SQLiteClosable {
            " SQLiteDatabase created and never closed");
        mFactory = factory;
        dbopen(mPath, mFlags);
        if (SQLiteDebug.DEBUG_SQL_CACHE) {
            mTimeOpened = getTime();
        }
        mPrograms = new WeakHashMap<SQLiteClosable,Object>();
        try {
            setLocale(Locale.getDefault());
        } catch (RuntimeException e) {
            Log.e(TAG, "Failed to setLocale() when constructing, closing the database", e);
            dbclose();
            if (SQLiteDebug.DEBUG_SQL_CACHE) {
                mTimeClosed = getTime();
            }
            throw e;
        }
    }

    private String getTime() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ").format(System.currentTimeMillis());
    }

    /**
     * return whether the DB is opened as read only.
     * @return true if DB is opened as read only
@@ -1733,6 +1784,112 @@ public class SQLiteDatabase extends SQLiteClosable {
        return mPath;
    }

    /**
     * set the max size of the compiled sql cache for this database after purging the cache.
     * (size of the cache = number of compiled-sql-statements stored in the cache)
     *
     * synchronized because we don't want t threads to change cache size at the same time.
     * @param cacheSize the size of the cache. can be (0 to MAX_SQL_CACHE_SIZE)
     */
    public void setMaxSqlCacheSize(int cacheSize) {
        synchronized(mCompiledQueries) {
            resetCompiledSqlCache();
            mMaxSqlCacheSize = (cacheSize > MAX_SQL_CACHE_SIZE) ? MAX_SQL_CACHE_SIZE
                    : (cacheSize < 0) ? 0 : cacheSize;
        }
    }

    /**
     * remove everything from the compiled sql cache
     */
    public void resetCompiledSqlCache() {
        synchronized(mCompiledQueries) {
            mCompiledQueries.clear();
        }
    }

    /**
     * adds the given sql and its compiled-statement-id-returned-by-sqlite to the
     * cache of compiledQueries attached to 'this'.
     *
     * if there is already a {@link SQLiteCompiledSql} in compiledQueries for the given sql,
     * the new {@link SQLiteCompiledSql} object is NOT inserted into the cache (i.e.,the current
     * mapping is NOT replaced with the new mapping).
     *
     * @return true if the given obj is added to cache. false otherwise.
     */
    /* package */ boolean addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) {
        if (mMaxSqlCacheSize == 0) {
            // for this database, there is no cache of compiled sql.
            if (SQLiteDebug.DEBUG_SQL_CACHE) {
                Log.v(TAG, "|NOT adding_sql_to_cache|" + getPath() + "|" + sql);
            }
            return false;
        }

        SQLiteCompiledSql compiledSql = null;
        synchronized(mCompiledQueries) {
            // don't insert the new mapping if a mapping already exists
            compiledSql = mCompiledQueries.get(sql);
            if (compiledSql != null) {
                return false;
            }
            // add this <sql, compiledStatement> to the cache
            if (mCompiledQueries.size() == mMaxSqlCacheSize) {
                /* reached max cachesize. before adding new entry, remove an entry from the
                 * cache. we don't want to wipe out the entire cache because of this:
                 * GCing {@link SQLiteCompiledSql} requires call to sqlite3_finalize
                 * JNI method. If entire cache is wiped out, it could be cause a big GC activity
                 * just because a (rogue) process is using the cache incorrectly.
                 */
                Set<String> keySet = mCompiledQueries.keySet();
                for (String s : keySet) {
                    mCompiledQueries.remove(s);
                    break;
                }
            }
            compiledSql = new SQLiteCompiledSql(this, sql);
            mCompiledQueries.put(sql, compiledSql);
        }
        if (SQLiteDebug.DEBUG_SQL_CACHE) {
            Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" + mCompiledQueries.size() + "|" +
                    sql);
        }
        return true;
    }

    /**
     * from the compiledQueries cache, returns the compiled-statement-id for the given sql.
     * returns null, if not found in the cache.
     */
    /* package */ SQLiteCompiledSql getCompiledStatementForSql(String sql) {
        SQLiteCompiledSql compiledStatement = null;
        boolean cacheHit;
        synchronized(mCompiledQueries) {
            if (mMaxSqlCacheSize == 0) {
                // for this database, there is no cache of compiled sql.
                if (SQLiteDebug.DEBUG_SQL_CACHE) {
                    Log.v(TAG, "|cache NOT found|" + getPath());
                }
                return null;
            }
            cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null;
        }
        if (cacheHit) {
            mNumCacheHits++;
        } else {
            mNumCacheMisses++;
        }

        if (SQLiteDebug.DEBUG_SQL_CACHE) {
            Log.v(TAG, "|cache_stats|" +
                    getPath() + "|" + mCompiledQueries.size() +
                    "|" + mNumCacheHits + "|" + mNumCacheMisses +
                    "|" + cacheHit + "|" + mTimeOpened + "|" + mTimeClosed + "|" + sql);
        }
        return compiledStatement;
    }

    /* package */ void logTimeStat(boolean read, long begin, long end) {
        EventLog.writeEvent(EVENT_DB_OPERATION, mPath, read ? 0 : 1, end - begin);
    }
+6 −0
Original line number Diff line number Diff line
@@ -31,6 +31,12 @@ public final class SQLiteDebug {
    public static final boolean DEBUG_SQL_STATEMENTS =
            Log.isLoggable("SQLiteStatements", Log.VERBOSE);

    /**
     * Controls the printing of compiled-sql-statement cache stats.
     */
    public static final boolean DEBUG_SQL_CACHE =
            Log.isLoggable("SQLiteCompiledSql", Log.VERBOSE);

    /**
     * Controls the stack trace reporting of active cursors being
     * finalized.
+55 −70
Original line number Diff line number Diff line
@@ -27,6 +27,9 @@ public abstract class SQLiteProgram extends SQLiteClosable {
    /** The database this program is compiled against. */
    protected SQLiteDatabase mDatabase;

    /** The SQL used to create this query */
    /* package */ final String mSql;

    /**
     * Native linkage, do not modify. This comes from the database and should not be modified
     * in here or in the native code.
@@ -34,45 +37,60 @@ public abstract class SQLiteProgram extends SQLiteClosable {
    protected int nHandle = 0;

    /**
     * Native linkage, do not modify. When non-0 this holds a reference to a valid
     * sqlite3_statement object. It is only updated by the native code, but may be
     * checked in this class when the database lock is held to determine if there
     * is a valid native-side program or not.
     * the compiledSql object for the given sql statement.
     */
    protected int nStatement = 0;
    private SQLiteCompiledSql compiledSql;
    private boolean myCompiledSqlIsInCache;

    /**
     * Used to find out where a cursor was allocated in case it never got
     * released.
     * compiledSql statement id is populated with the corresponding object from the above
     * member compiledSql.
     * this member is used by the native_bind_* methods
     */
    private StackTraceElement[] mStackTraceElements;    
    protected int nStatement = 0;

    /* package */ SQLiteProgram(SQLiteDatabase db, String sql) {
        if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
            mStackTraceElements = new Exception().getStackTrace();
            Log.d(TAG, "processing sql: " + sql);
        }

        mDatabase = db;
        mSql = sql;
        db.acquireReference();
        db.addSQLiteClosable(this);
        this.nHandle = db.mNativeHandle;
        compile(sql, false);

        compiledSql = db.getCompiledStatementForSql(sql);
        if (compiledSql == null) {
            // create a new compiled-sql obj
            compiledSql = new SQLiteCompiledSql(db, sql);

            // add it to the cache of compiled-sqls
            myCompiledSqlIsInCache = db.addToCompiledQueries(sql, compiledSql);
        } else {
            myCompiledSqlIsInCache = true;
        }
        nStatement = compiledSql.nStatement;
    }

    @Override
    protected void onAllReferencesReleased() {
        // Note that native_finalize() checks to make sure that nStatement is
        // non-null before destroying it.
        native_finalize();
        // release the compiled sql statement used by me if it is NOT in cache
        if (!myCompiledSqlIsInCache) {
            compiledSql.releaseSqlStatement();
            compiledSql = null; // so that GC doesn't call finalize() on it
        }
        mDatabase.releaseReference();
        mDatabase.removeSQLiteClosable(this);
    }

    @Override
    protected void onAllReferencesReleasedFromContainer() {
        // Note that native_finalize() checks to make sure that nStatement is
        // non-null before destroying it.
        native_finalize();
        // release the compiled sql statement used by me if it is NOT in cache
        if (!myCompiledSqlIsInCache) {
            compiledSql.releaseSqlStatement();
            compiledSql = null; // so that GC doesn't call finalize() on it
        }
        mDatabase.releaseReference();
    }

@@ -82,37 +100,23 @@ public abstract class SQLiteProgram extends SQLiteClosable {
     * @return a unique identifier for this program
     */
    public final int getUniqueId() {
        return nStatement;
        return compiledSql.nStatement;
    }

    /* package */ String getSqlString() {
        return mSql;
    }

    /**
     * Compiles the given SQL into a SQLite byte code program using sqlite3_prepare_v2(). If
     * this method has been called previously without a call to close and forCompilation is set
     * to false the previous compilation will be used. Setting forceCompilation to true will
     * always re-compile the program and should be done if you pass differing SQL strings to this
     * method.
     *
     * <P>Note: this method acquires the database lock.</P>
     * @deprecated use this.compiledStatement.compile instead
     *
     * @param sql the SQL string to compile
     * @param forceCompilation forces the SQL to be recompiled in the event that there is an
     *  existing compiled SQL program already around
     */
    @Deprecated
    protected void compile(String sql, boolean forceCompilation) {
        // Only compile if we don't have a valid statement already or the caller has
        // explicitly requested a recompile. 
        if (nStatement == 0 || forceCompilation) {
            mDatabase.lock();
            try {
                // Note that the native_compile() takes care of destroying any previously
                // existing programs before it compiles.
                acquireReference();                
                native_compile(sql);
            } finally {
                releaseReference();
                mDatabase.unlock();
            }        
        }
        // TODO is there a need for this?
    }

    /**
@@ -224,34 +228,15 @@ public abstract class SQLiteProgram extends SQLiteClosable {
        }
    }

    /**
     * Make sure that the native resource is cleaned up.
     */
    @Override
    protected void finalize() {
        if (nStatement != 0) {
            if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
                String message = "Finalizing " + this +  
                    " that has not been closed";

                Log.d(TAG, message + "\nThis cursor was created in:");
                for (StackTraceElement ste : mStackTraceElements) {
                    Log.d(TAG, "      " + ste);
                }
            }
            // when in finalize() it is already removed from weakhashmap
            // so it is safe to not removed itself from db
            onAllReferencesReleasedFromContainer();
        }
    }

    /**
     * Compiles SQL into a SQLite program.
     *
     * <P>The database lock must be held when calling this method.
     * @param sql The SQL to compile.
     */
    @Deprecated
    protected final native void native_compile(String sql);
    @Deprecated
    protected final native void native_finalize();

    protected final native void native_bind_null(int index);
Loading