• SQLiteDatabase 源码


    /**
     * Copyright (C) 2006 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 com.google.android.collect.Maps;
    
    import android.app.ActivityThread;
    import android.content.ContentValues;
    import android.database.Cursor;
    import android.database.DatabaseUtils;
    import android.database.SQLException;
    import android.database.sqlite.SQLiteDebug.DbStats;
    import android.os.Debug;
    import android.os.SystemClock;
    import android.os.SystemProperties;
    import android.text.TextUtils;
    import android.util.Config;
    import android.util.EventLog;
    import android.util.Log;
    import android.util.Pair;
    
    import java.io.File;
    import java.lang.ref.WeakReference;
    import java.text.SimpleDateFormat;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Locale;
    import java.util.Map;
    import java.util.Random;
    import java.util.Set;
    import java.util.WeakHashMap;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.regex.Pattern;
    
    /***
     * Exposes methods to manage a SQLite database.
     * <p>SQLiteDatabase has methods to create, delete, execute SQL commands, and
     * perform other common database management tasks.
     * <p>See the Notepad sample application in the SDK for an example of creating
     * and managing a database.
     * <p> Database names must be unique within an application, not across all
     * applications.
     *
     * <h3>Localized Collation - ORDER BY</h3>
     * <p>In addition to SQLite's default <code>BINARY</code> collator, Android supplies
     * two more, <code>LOCALIZED</code>, which changes with the system's current locale
     * if you wire it up correctly (XXX a link needed!), and <code>UNICODE</code>, which
     * is the Unicode Collation Algorithm and not tailored to the current locale.
     */
    public class SQLiteDatabase extends SQLiteClosable {
        private static final String TAG = "Database";
        private static final int EVENT_DB_OPERATION = 52000;
        private static final int EVENT_DB_CORRUPT = 75004;
    
        /***
         * Algorithms used in ON CONFLICT clause
         * http://www.sqlite.org/lang_conflict.html
         */
        /***
         *  When a constraint violation occurs, an immediate ROLLBACK occurs,
         * thus ending the current transaction, and the command aborts with a
         * return code of SQLITE_CONSTRAINT. If no transaction is active
         * (other than the implied transaction that is created on every command)
         *  then this algorithm works the same as ABORT.
         */
        public static final int CONFLICT_ROLLBACK = 1;
    
        /***
         * When a constraint violation occurs,no ROLLBACK is executed
         * so changes from prior commands within the same transaction
         * are preserved. This is the default behavior.
         */
        public static final int CONFLICT_ABORT = 2;
    
        /***
         * When a constraint violation occurs, the command aborts with a return
         * code SQLITE_CONSTRAINT. But any changes to the database that
         * the command made prior to encountering the constraint violation
         * are preserved and are not backed out.
         */
        public static final int CONFLICT_FAIL = 3;
    
        /***
         * When a constraint violation occurs, the one row that contains
         * the constraint violation is not inserted or changed.
         * But the command continues executing normally. Other rows before and
         * after the row that contained the constraint violation continue to be
         * inserted or updated normally. No error is returned.
         */
        public static final int CONFLICT_IGNORE = 4;
    
        /***
         * When a UNIQUE constraint violation occurs, the pre-existing rows that
         * are causing the constraint violation are removed prior to inserting
         * or updating the current row. Thus the insert or update always occurs.
         * The command continues executing normally. No error is returned.
         * If a NOT NULL constraint violation occurs, the NULL value is replaced
         * by the default value for that column. If the column has no default
         * value, then the ABORT algorithm is used. If a CHECK constraint
         * violation occurs then the IGNORE algorithm is used. When this conflict
         * resolution strategy deletes rows in order to satisfy a constraint,
         * it does not invoke delete triggers on those rows.
         *  This behavior might change in a future release.
         */
        public static final int CONFLICT_REPLACE = 5;
    
        /***
         * use the following when no conflict action is specified.
         */
        public static final int CONFLICT_NONE = 0;
        private static final String[] CONFLICT_VALUES = new String[]
                {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "};
    
        /***
         * Maximum Length Of A LIKE Or GLOB Pattern
         * The pattern matching algorithm used in the default LIKE and GLOB implementation
         * of SQLite can exhibit O(N^2) performance (where N is the number of characters in
         * the pattern) for certain pathological cases. To avoid denial-of-service attacks
         * the length of the LIKE or GLOB pattern is limited to SQLITE_MAX_LIKE_PATTERN_LENGTH bytes.
         * The default value of this limit is 50000. A modern workstation can evaluate
         * even a pathological LIKE or GLOB pattern of 50000 bytes relatively quickly.
         * The denial of service problem only comes into play when the pattern length gets
         * into millions of bytes. Nevertheless, since most useful LIKE or GLOB patterns
         * are at most a few dozen bytes in length, paranoid application developers may
         * want to reduce this parameter to something in the range of a few hundred
         * if they know that external users are able to generate arbitrary patterns.
         */
        public static final int SQLITE_MAX_LIKE_PATTERN_LENGTH = 50000;
    
        /***
         * Flag for {@link #openDatabase} to open the database for reading and writing.
         * If the disk is full, this may fail even before you actually write anything.
         *
         * {@more} Note that the value of this flag is 0, so it is the default.
         */
        public static final int OPEN_READWRITE = 0x00000000;          // update native code if changing
    
        /***
         * Flag for {@link #openDatabase} to open the database for reading only.
         * This is the only reliable way to open a database if the disk may be full.
         */
        public static final int OPEN_READONLY = 0x00000001;           // update native code if changing
    
        private static final int OPEN_READ_MASK = 0x00000001;         // update native code if changing
    
        /***
         * Flag for {@link #openDatabase} to open the database without support for localized collators.
         *
         * {@more} This causes the collator <code>LOCALIZED</code> not to be created.
         * You must be consistent when using this flag to use the setting the database was
         * created with.  If this is set, {@link #setLocale} will do nothing.
         */
        public static final int NO_LOCALIZED_COLLATORS = 0x00000010;  // update native code if changing
    
        /***
         * Flag for {@link #openDatabase} to create the database file if it does not already exist.
         */
        public static final int CREATE_IF_NECESSARY = 0x10000000;     // update native code if changing
    
        /***
         * Indicates whether the most-recently started transaction has been marked as successful.
         */
        private boolean mInnerTransactionIsSuccessful;
    
        /***
         * Valid during the life of a transaction, and indicates whether the entire transaction (the
         * outer one and all of the inner ones) so far has been successful.
         */
        private boolean mTransactionIsSuccessful;
    
        /***
         * Valid during the life of a transaction.
         */
        private SQLiteTransactionListener mTransactionListener;
    
        /*** Synchronize on this when accessing the database */
        private final ReentrantLock mLock = new ReentrantLock(true);
    
        private long mLockAcquiredWallTime = 0L;
        private long mLockAcquiredThreadTime = 0L;
    
        // limit the frequency of complaints about each database to one within 20 sec
        // unless run command adb shell setprop log.tag.Database VERBOSE
        private static final int LOCK_WARNING_WINDOW_IN_MS = 20000;
        /*** If the lock is held this long then a warning will be printed when it is released. */
        private static final int LOCK_ACQUIRED_WARNING_TIME_IN_MS = 300;
        private static final int LOCK_ACQUIRED_WARNING_THREAD_TIME_IN_MS = 100;
        private static final int LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT = 2000;
    
        private static final int SLEEP_AFTER_YIELD_QUANTUM = 1000;
    
        // The pattern we remove from database filenames before
        // potentially logging them.
        private static final Pattern EMAIL_IN_DB_PATTERN = Pattern.compile("[\w\.\-]+@[\w\.\-]+");
    
        private long mLastLockMessageTime = 0L;
    
        // Things related to query logging/sampling for debugging
        // slow/frequent queries during development.  Always log queries
        // which take (by default) 500ms+; shorter queries are sampled
        // accordingly.  Commit statements, which are typically slow, are
        // logged together with the most recently executed SQL statement,
        // for disambiguation.  The 500ms value is configurable via a
        // SystemProperty, but developers actively debugging database I/O
        // should probably use the regular log tunable,
        // LOG_SLOW_QUERIES_PROPERTY, defined below.
        private static int sQueryLogTimeInMillis = 0;  // lazily initialized
        private static final int QUERY_LOG_SQL_LENGTH = 64;
        private static final String COMMIT_SQL = "COMMIT;";
        private final Random mRandom = new Random();
        private String mLastSqlStatement = null;
    
        // String prefix for slow database query EventLog records that show
        // lock acquistions of the database.
        /** package */ static final String GET_LOCK_LOG_PREFIX = "GETLOCK:";
    
        /*** Used by native code, do not rename */
        /** package */ int mNativeHandle = 0;
    
        /*** Used to make temp table names unique */
        /** package */ int mTempTableSequence = 0;
    
        /*** The path for the database file */
        private String mPath;
    
        /*** The anonymized path for the database file for logging purposes */
        private String mPathForLogs = null;  // lazily populated
    
        /*** The flags passed to open/create */
        private int mFlags;
    
        /*** The optional factory to use when creating new Cursors */
        private CursorFactory mFactory;
    
        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.
         */
        /** package */ Map<String, SQLiteCompiledSql> mCompiledQueries = Maps.newHashMap();
        /***
         * @hide
         */
        public static final int MAX_SQL_CACHE_SIZE = 250;
        private int mMaxSqlCacheSize = MAX_SQL_CACHE_SIZE; // max cache size per Database instance
        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;
    
        /*** the following 2 members maintain the time when a database is opened and closed */
        private String mTimeOpened = null;
        private String mTimeClosed = null;
    
        /*** Used to find out where this object was created in case it never got closed. */
        private Throwable mStackTrace = null;
    
        // System property that enables logging of slow queries. Specify the threshold in ms.
        private static final String LOG_SLOW_QUERIES_PROPERTY = "db.log.slow_query_threshold";
        private final int mSlowQueryThreshold;
    
        /***
         * @param closable
         */
        void addSQLiteClosable(SQLiteClosable closable) {
            lock();
            try {
                mPrograms.put(closable, null);
            } finally {
                unlock();
            }
        }
    
        void removeSQLiteClosable(SQLiteClosable closable) {
            lock();
            try {
                mPrograms.remove(closable);
            } finally {
                unlock();
            }
        }
    
        @Override
        protected void onAllReferencesReleased() {
            if (isOpen()) {
                if (SQLiteDebug.DEBUG_SQL_CACHE) {
                    mTimeClosed = getTime();
                }
                dbclose();
            }
        }
    
        /***
         * Attempts to release memory that SQLite holds but does not require to
         * operate properly. Typically this memory will come from the page cache.
         *
         * @return the number of bytes actually released
         */
        static public native int releaseMemory();
    
        /***
         * Control whether or not the SQLiteDatabase is made thread-safe by using locks
         * around critical sections. This is pretty expensive, so if you know that your
         * DB will only be used by a single thread then you should set this to false.
         * The default is true.
         * @param lockingEnabled set to true to enable locks, false otherwise
         */
        public void setLockingEnabled(boolean lockingEnabled) {
            mLockingEnabled = lockingEnabled;
        }
    
        /***
         * If set then the SQLiteDatabase is made thread-safe by using locks
         * around critical sections
         */
        private boolean mLockingEnabled = true;
    
        /** package */ void onCorruption() {
            Log.e(TAG, "Removing corrupt database: " + mPath);
            EventLog.writeEvent(EVENT_DB_CORRUPT, mPath);
            try {
                // Close the database (if we can), which will cause subsequent operations to fail.
                close();
            } finally {
                // Delete the corrupt file.  Don't re-create it now -- that would just confuse people
                // -- but the next time someone tries to open it, they can set it up from scratch.
                if (!mPath.equalsIgnoreCase(":memory")) {
                    // delete is only for non-memory database files
                    new File(mPath).delete();
                }
            }
        }
    
        /***
         * Locks the database for exclusive access. The database lock must be held when
         * touch the native sqlite3* object since it is single threaded and uses
         * a polling lock contention algorithm. The lock is recursive, and may be acquired
         * multiple times by the same thread. This is a no-op if mLockingEnabled is false.
         *
         * @see #unlock()
         */
        /** package */ void lock() {
            if (!mLockingEnabled) return;
            mLock.lock();
            if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
                if (mLock.getHoldCount() == 1) {
                    // Use elapsed real-time since the CPU may sleep when waiting for IO
                    mLockAcquiredWallTime = SystemClock.elapsedRealtime();
                    mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
                }
            }
        }
    
        /***
         * Locks the database for exclusive access. The database lock must be held when
         * touch the native sqlite3* object since it is single threaded and uses
         * a polling lock contention algorithm. The lock is recursive, and may be acquired
         * multiple times by the same thread.
         *
         * @see #unlockForced()
         */
        private void lockForced() {
            mLock.lock();
            if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
                if (mLock.getHoldCount() == 1) {
                    // Use elapsed real-time since the CPU may sleep when waiting for IO
                    mLockAcquiredWallTime = SystemClock.elapsedRealtime();
                    mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
                }
            }
        }
    
        /***
         * Releases the database lock. This is a no-op if mLockingEnabled is false.
         *
         * @see #unlock()
         */
        /** package */ void unlock() {
            if (!mLockingEnabled) return;
            if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
                if (mLock.getHoldCount() == 1) {
                    checkLockHoldTime();
                }
            }
            mLock.unlock();
        }
    
        /***
         * Releases the database lock.
         *
         * @see #unlockForced()
         */
        private void unlockForced() {
            if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
                if (mLock.getHoldCount() == 1) {
                    checkLockHoldTime();
                }
            }
            mLock.unlock();
        }
    
        private void checkLockHoldTime() {
            // Use elapsed real-time since the CPU may sleep when waiting for IO
            long elapsedTime = SystemClock.elapsedRealtime();
            long lockedTime = elapsedTime - mLockAcquiredWallTime;
            if (lockedTime < LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT &&
                    !Log.isLoggable(TAG, Log.VERBOSE) &&
                    (elapsedTime - mLastLockMessageTime) < LOCK_WARNING_WINDOW_IN_MS) {
                return;
            }
            if (lockedTime > LOCK_ACQUIRED_WARNING_TIME_IN_MS) {
                int threadTime = (int)
                        ((Debug.threadCpuTimeNanos() - mLockAcquiredThreadTime) / 1000000);
                if (threadTime > LOCK_ACQUIRED_WARNING_THREAD_TIME_IN_MS ||
                        lockedTime > LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT) {
                    mLastLockMessageTime = elapsedTime;
                    String msg = "lock held on " + mPath + " for " + lockedTime + "ms. Thread time was "
                            + threadTime + "ms";
                    if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING_STACK_TRACE) {
                        Log.d(TAG, msg, new Exception());
                    } else {
                        Log.d(TAG, msg);
                    }
                }
            }
        }
    
        /***
         * Begins a transaction. Transactions can be nested. When the outer transaction is ended all of
         * the work done in that transaction and all of the nested transactions will be committed or
         * rolled back. The changes will be rolled back if any transaction is ended without being
         * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed.
         *
         * <p>Here is the standard idiom for transactions:
         *
         * <pre>
         *   db.beginTransaction();
         *   try {
         *     ...
         *     db.setTransactionSuccessful();
         *   } finally {
         *     db.endTransaction();
         *   }
         * </pre>
         */
        public void beginTransaction() {
            beginTransactionWithListener(null /** transactionStatusCallback */);
        }
    
        /***
         * Begins a transaction. Transactions can be nested. When the outer transaction is ended all of
         * the work done in that transaction and all of the nested transactions will be committed or
         * rolled back. The changes will be rolled back if any transaction is ended without being
         * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed.
         *
         * <p>Here is the standard idiom for transactions:
         *
         * <pre>
         *   db.beginTransactionWithListener(listener);
         *   try {
         *     ...
         *     db.setTransactionSuccessful();
         *   } finally {
         *     db.endTransaction();
         *   }
         * </pre>
         * @param transactionListener listener that should be notified when the transaction begins,
         * commits, or is rolled back, either explicitly or by a call to
         * {@link #yieldIfContendedSafely}.
         */
        public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) {
            lockForced();
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
            boolean ok = false;
            try {
                // If this thread already had the lock then get out
                if (mLock.getHoldCount() > 1) {
                    if (mInnerTransactionIsSuccessful) {
                        String msg = "Cannot call beginTransaction between "
                                + "calling setTransactionSuccessful and endTransaction";
                        IllegalStateException e = new IllegalStateException(msg);
                        Log.e(TAG, "beginTransaction() failed", e);
                        throw e;
                    }
                    ok = true;
                    return;
                }
    
                // This thread didn't already have the lock, so begin a database
                // transaction now.
                execSQL("BEGIN EXCLUSIVE;");
                mTransactionListener = transactionListener;
                mTransactionIsSuccessful = true;
                mInnerTransactionIsSuccessful = false;
                if (transactionListener != null) {
                    try {
                        transactionListener.onBegin();
                    } catch (RuntimeException e) {
                        execSQL("ROLLBACK;");
                        throw e;
                    }
                }
                ok = true;
            } finally {
                if (!ok) {
                    // beginTransaction is called before the try block so we must release the lock in
                    // the case of failure.
                    unlockForced();
                }
            }
        }
    
        /***
         * End a transaction. See beginTransaction for notes about how to use this and when transactions
         * are committed and rolled back.
         */
        public void endTransaction() {
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
            if (!mLock.isHeldByCurrentThread()) {
                throw new IllegalStateException("no transaction pending");
            }
            try {
                if (mInnerTransactionIsSuccessful) {
                    mInnerTransactionIsSuccessful = false;
                } else {
                    mTransactionIsSuccessful = false;
                }
                if (mLock.getHoldCount() != 1) {
                    return;
                }
                RuntimeException savedException = null;
                if (mTransactionListener != null) {
                    try {
                        if (mTransactionIsSuccessful) {
                            mTransactionListener.onCommit();
                        } else {
                            mTransactionListener.onRollback();
                        }
                    } catch (RuntimeException e) {
                        savedException = e;
                        mTransactionIsSuccessful = false;
                    }
                }
                if (mTransactionIsSuccessful) {
                    execSQL(COMMIT_SQL);
                } else {
                    try {
                        execSQL("ROLLBACK;");
                        if (savedException != null) {
                            throw savedException;
                        }
                    } catch (SQLException e) {
                        if (Config.LOGD) {
                            Log.d(TAG, "exception during rollback, maybe the DB previously "
                                    + "performed an auto-rollback");
                        }
                    }
                }
            } finally {
                mTransactionListener = null;
                unlockForced();
                if (Config.LOGV) {
                    Log.v(TAG, "unlocked " + Thread.currentThread()
                            + ", holdCount is " + mLock.getHoldCount());
                }
            }
        }
    
        /***
         * Marks the current transaction as successful. Do not do any more database work between
         * calling this and calling endTransaction. Do as little non-database work as possible in that
         * situation too. If any errors are encountered between this and endTransaction the transaction
         * will still be committed.
         *
         * @throws IllegalStateException if the current thread is not in a transaction or the
         * transaction is already marked as successful.
         */
        public void setTransactionSuccessful() {
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
            if (!mLock.isHeldByCurrentThread()) {
                throw new IllegalStateException("no transaction pending");
            }
            if (mInnerTransactionIsSuccessful) {
                throw new IllegalStateException(
                        "setTransactionSuccessful may only be called once per call to beginTransaction");
            }
            mInnerTransactionIsSuccessful = true;
        }
    
        /***
         * return true if there is a transaction pending
         */
        public boolean inTransaction() {
            return mLock.getHoldCount() > 0;
        }
    
        /***
         * Checks if the database lock is held by this thread.
         *
         * @return true, if this thread is holding the database lock.
         */
        public boolean isDbLockedByCurrentThread() {
            return mLock.isHeldByCurrentThread();
        }
    
        /***
         * Checks if the database is locked by another thread. This is
         * just an estimate, since this status can change at any time,
         * including after the call is made but before the result has
         * been acted upon.
         *
         * @return true, if the database is locked by another thread
         */
        public boolean isDbLockedByOtherThreads() {
            return !mLock.isHeldByCurrentThread() && mLock.isLocked();
        }
    
        /***
         * Temporarily end the transaction to let other threads run. The transaction is assumed to be
         * successful so far. Do not call setTransactionSuccessful before calling this. When this
         * returns a new transaction will have been created but not marked as successful.
         * @return true if the transaction was yielded
         * @deprecated if the db is locked more than once (becuase of nested transactions) then the lock
         *   will not be yielded. Use yieldIfContendedSafely instead.
         */
        @Deprecated
        public boolean yieldIfContended() {
            return yieldIfContendedHelper(false /** do not check yielding */,
                    -1 /** sleepAfterYieldDelay */);
        }
    
        /***
         * Temporarily end the transaction to let other threads run. The transaction is assumed to be
         * successful so far. Do not call setTransactionSuccessful before calling this. When this
         * returns a new transaction will have been created but not marked as successful. This assumes
         * that there are no nested transactions (beginTransaction has only been called once) and will
         * throw an exception if that is not the case.
         * @return true if the transaction was yielded
         */
        public boolean yieldIfContendedSafely() {
            return yieldIfContendedHelper(true /** check yielding */, -1 /** sleepAfterYieldDelay*/);
        }
    
        /***
         * Temporarily end the transaction to let other threads run. The transaction is assumed to be
         * successful so far. Do not call setTransactionSuccessful before calling this. When this
         * returns a new transaction will have been created but not marked as successful. This assumes
         * that there are no nested transactions (beginTransaction has only been called once) and will
         * throw an exception if that is not the case.
         * @param sleepAfterYieldDelay if > 0, sleep this long before starting a new transaction if
         *   the lock was actually yielded. This will allow other background threads to make some
         *   more progress than they would if we started the transaction immediately.
         * @return true if the transaction was yielded
         */
        public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) {
            return yieldIfContendedHelper(true /** check yielding */, sleepAfterYieldDelay);
        }
    
        private boolean yieldIfContendedHelper(boolean checkFullyYielded, long sleepAfterYieldDelay) {
            if (mLock.getQueueLength() == 0) {
                // Reset the lock acquire time since we know that the thread was willing to yield
                // the lock at this time.
                mLockAcquiredWallTime = SystemClock.elapsedRealtime();
                mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
                return false;
            }
            setTransactionSuccessful();
            SQLiteTransactionListener transactionListener = mTransactionListener;
            endTransaction();
            if (checkFullyYielded) {
                if (this.isDbLockedByCurrentThread()) {
                    throw new IllegalStateException(
                            "Db locked more than once. yielfIfContended cannot yield");
                }
            }
            if (sleepAfterYieldDelay > 0) {
                // Sleep for up to sleepAfterYieldDelay milliseconds, waking up periodically to
                // check if anyone is using the database.  If the database is not contended,
                // retake the lock and return.
                long remainingDelay = sleepAfterYieldDelay;
                while (remainingDelay > 0) {
                    try {
                        Thread.sleep(remainingDelay < SLEEP_AFTER_YIELD_QUANTUM ?
                                remainingDelay : SLEEP_AFTER_YIELD_QUANTUM);
                    } catch (InterruptedException e) {
                        Thread.interrupted();
                    }
                    remainingDelay -= SLEEP_AFTER_YIELD_QUANTUM;
                    if (mLock.getQueueLength() == 0) {
                        break;
                    }
                }
            }
            beginTransactionWithListener(transactionListener);
            return true;
        }
    
        /*** Maps table names to info about what to which _sync_time column to set
         * to NULL on an update. This is used to support syncing. */
        private final Map<String, SyncUpdateInfo> mSyncUpdateInfo =
                new HashMap<String, SyncUpdateInfo>();
    
        public Map<String, String> getSyncedTables() {
            synchronized(mSyncUpdateInfo) {
                HashMap<String, String> tables = new HashMap<String, String>();
                for (String table : mSyncUpdateInfo.keySet()) {
                    SyncUpdateInfo info = mSyncUpdateInfo.get(table);
                    if (info.deletedTable != null) {
                        tables.put(table, info.deletedTable);
                    }
                }
                return tables;
            }
        }
    
        /***
         * Internal class used to keep track what needs to be marked as changed
         * when an update occurs. This is used for syncing, so the sync engine
         * knows what data has been updated locally.
         */
        static private class SyncUpdateInfo {
            /***
             * Creates the SyncUpdateInfo class.
             *
             * @param masterTable The table to set _sync_time to NULL in
             * @param deletedTable The deleted table that corresponds to the
             *          master table
             * @param foreignKey The key that refers to the primary key in table
             */
            SyncUpdateInfo(String masterTable, String deletedTable,
                    String foreignKey) {
                this.masterTable = masterTable;
                this.deletedTable = deletedTable;
                this.foreignKey = foreignKey;
            }
    
            /*** The table containing the _sync_time column */
            String masterTable;
    
            /*** The deleted table that corresponds to the master table */
            String deletedTable;
    
            /*** The key in the local table the row in table. It may be _id, if table
             * is the local table. */
            String foreignKey;
        }
    
        /***
         * Used to allow returning sub-classes of {@link Cursor} when calling query.
         */
        public interface CursorFactory {
            /***
             * See
             * {@link SQLiteCursor#SQLiteCursor(SQLiteDatabase, SQLiteCursorDriver,
             * String, SQLiteQuery)}.
             */
            public Cursor newCursor(SQLiteDatabase db,
                    SQLiteCursorDriver masterQuery, String editTable,
                    SQLiteQuery query);
        }
    
        /***
         * Open the database according to the flags {@link #OPEN_READWRITE}
         * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
         *
         * <p>Sets the locale of the database to the  the system's current locale.
         * Call {@link #setLocale} if you would like something else.</p>
         *
         * @param path to database file to open and/or create
         * @param factory an optional factory class that is called to instantiate a
         *            cursor when query is called, or null for default
         * @param flags to control database access mode
         * @return the newly opened database
         * @throws SQLiteException if the database cannot be opened
         */
        public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags) {
            SQLiteDatabase sqliteDatabase = null;
            try {
                // Open the database.
                sqliteDatabase = new SQLiteDatabase(path, factory, flags);
                if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
                    sqliteDatabase.enableSqlTracing(path);
                }
                if (SQLiteDebug.DEBUG_SQL_TIME) {
                    sqliteDatabase.enableSqlProfiling(path);
                }
            } catch (SQLiteDatabaseCorruptException e) {
                // Try to recover from this, if we can.
                // TODO: should we do this for other open failures?
                Log.e(TAG, "Deleting and re-creating corrupt database " + path, e);
                EventLog.writeEvent(EVENT_DB_CORRUPT, path);
                if (!path.equalsIgnoreCase(":memory")) {
                    // delete is only for non-memory database files
                    new File(path).delete();
                }
                sqliteDatabase = new SQLiteDatabase(path, factory, flags);
            }
            ActiveDatabases.getInstance().mActiveDatabases.add(
                    new WeakReference<SQLiteDatabase>(sqliteDatabase));
            return sqliteDatabase;
        }
    
        /***
         * Equivalent to openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY).
         */
        public static SQLiteDatabase openOrCreateDatabase(File file, CursorFactory factory) {
            return openOrCreateDatabase(file.getPath(), factory);
        }
    
        /***
         * Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY).
         */
        public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory) {
            return openDatabase(path, factory, CREATE_IF_NECESSARY);
        }
    
        /***
         * Create a memory backed SQLite database.  Its contents will be destroyed
         * when the database is closed.
         *
         * <p>Sets the locale of the database to the  the system's current locale.
         * Call {@link #setLocale} if you would like something else.</p>
         *
         * @param factory an optional factory class that is called to instantiate a
         *            cursor when query is called
         * @return a SQLiteDatabase object, or null if the database can't be created
         */
        public static SQLiteDatabase create(CursorFactory factory) {
            // This is a magic string with special meaning for SQLite.
            return openDatabase(":memory:", factory, CREATE_IF_NECESSARY);
        }
    
        /***
         * Close the database.
         */
        public void close() {
            if (!isOpen()) {
                return; // already closed
            }
            lock();
            try {
                closeClosable();
                // close this database instance - regardless of its reference count value
                onAllReferencesReleased();
            } finally {
                unlock();
            }
        }
    
        private void closeClosable() {
            /** deallocate all compiled sql statement objects from mCompiledQueries cache.
             * this should be done before de-referencing all {@link SQLiteClosable} objects
             * from this database object because calling
             * {@link SQLiteClosable#onAllReferencesReleasedFromContainer()} could cause the database
             * to be closed. sqlite doesn't let a database close if there are
             * any unfinalized statements - such as the compiled-sql objects in mCompiledQueries.
             */
            deallocCachedSqlStatements();
    
            Iterator<Map.Entry<SQLiteClosable, Object>> iter = mPrograms.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry<SQLiteClosable, Object> entry = iter.next();
                SQLiteClosable program = entry.getKey();
                if (program != null) {
                    program.onAllReferencesReleasedFromContainer();
                }
            }
        }
    
        /***
         * Native call to close the database.
         */
        private native void dbclose();
    
        /***
         * Gets the database version.
         *
         * @return the database version
         */
        public int getVersion() {
            SQLiteStatement prog = null;
            lock();
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
            try {
                prog = new SQLiteStatement(this, "PRAGMA user_version;");
                long version = prog.simpleQueryForLong();
                return (int) version;
            } finally {
                if (prog != null) prog.close();
                unlock();
            }
        }
    
        /***
         * Sets the database version.
         *
         * @param version the new database version
         */
        public void setVersion(int version) {
            execSQL("PRAGMA user_version = " + version);
        }
    
        /***
         * Returns the maximum size the database may grow to.
         *
         * @return the new maximum database size
         */
        public long getMaximumSize() {
            SQLiteStatement prog = null;
            lock();
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
            try {
                prog = new SQLiteStatement(this,
                        "PRAGMA max_page_count;");
                long pageCount = prog.simpleQueryForLong();
                return pageCount * getPageSize();
            } finally {
                if (prog != null) prog.close();
                unlock();
            }
        }
    
        /***
         * Sets the maximum size the database will grow to. The maximum size cannot
         * be set below the current size.
         *
         * @param numBytes the maximum database size, in bytes
         * @return the new maximum database size
         */
        public long setMaximumSize(long numBytes) {
            SQLiteStatement prog = null;
            lock();
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
            try {
                long pageSize = getPageSize();
                long numPages = numBytes / pageSize;
                // If numBytes isn't a multiple of pageSize, bump up a page
                if ((numBytes % pageSize) != 0) {
                    numPages++;
                }
                prog = new SQLiteStatement(this,
                        "PRAGMA max_page_count = " + numPages);
                long newPageCount = prog.simpleQueryForLong();
                return newPageCount * pageSize;
            } finally {
                if (prog != null) prog.close();
                unlock();
            }
        }
    
        /***
         * Returns the current database page size, in bytes.
         *
         * @return the database page size, in bytes
         */
        public long getPageSize() {
            SQLiteStatement prog = null;
            lock();
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
            try {
                prog = new SQLiteStatement(this,
                        "PRAGMA page_size;");
                long size = prog.simpleQueryForLong();
                return size;
            } finally {
                if (prog != null) prog.close();
                unlock();
            }
        }
    
        /***
         * Sets the database page size. The page size must be a power of two. This
         * method does not work if any data has been written to the database file,
         * and must be called right after the database has been created.
         *
         * @param numBytes the database page size, in bytes
         */
        public void setPageSize(long numBytes) {
            execSQL("PRAGMA page_size = " + numBytes);
        }
    
        /***
         * Mark this table as syncable. When an update occurs in this table the
         * _sync_dirty field will be set to ensure proper syncing operation.
         *
         * @param table the table to mark as syncable
         * @param deletedTable The deleted table that corresponds to the
         *          syncable table
         */
        public void markTableSyncable(String table, String deletedTable) {
            markTableSyncable(table, "_id", table, deletedTable);
        }
    
        /***
         * Mark this table as syncable, with the _sync_dirty residing in another
         * table. When an update occurs in this table the _sync_dirty field of the
         * row in updateTable with the _id in foreignKey will be set to
         * ensure proper syncing operation.
         *
         * @param table an update on this table will trigger a sync time removal
         * @param foreignKey this is the column in table whose value is an _id in
         *          updateTable
         * @param updateTable this is the table that will have its _sync_dirty
         */
        public void markTableSyncable(String table, String foreignKey,
                String updateTable) {
            markTableSyncable(table, foreignKey, updateTable, null);
        }
    
        /***
         * Mark this table as syncable, with the _sync_dirty residing in another
         * table. When an update occurs in this table the _sync_dirty field of the
         * row in updateTable with the _id in foreignKey will be set to
         * ensure proper syncing operation.
         *
         * @param table an update on this table will trigger a sync time removal
         * @param foreignKey this is the column in table whose value is an _id in
         *          updateTable
         * @param updateTable this is the table that will have its _sync_dirty
         * @param deletedTable The deleted table that corresponds to the
         *          updateTable
         */
        private void markTableSyncable(String table, String foreignKey,
                String updateTable, String deletedTable) {
            lock();
            try {
                native_execSQL("SELECT _sync_dirty FROM " + updateTable
                        + " LIMIT 0");
                native_execSQL("SELECT " + foreignKey + " FROM " + table
                        + " LIMIT 0");
            } finally {
                unlock();
            }
    
            SyncUpdateInfo info = new SyncUpdateInfo(updateTable, deletedTable,
                    foreignKey);
            synchronized (mSyncUpdateInfo) {
                mSyncUpdateInfo.put(table, info);
            }
        }
    
        /***
         * Call for each row that is updated in a cursor.
         *
         * @param table the table the row is in
         * @param rowId the row ID of the updated row
         */
        /** package */ void rowUpdated(String table, long rowId) {
            SyncUpdateInfo info;
            synchronized (mSyncUpdateInfo) {
                info = mSyncUpdateInfo.get(table);
            }
            if (info != null) {
                execSQL("UPDATE " + info.masterTable
                        + " SET _sync_dirty=1 WHERE _id=(SELECT " + info.foreignKey
                        + " FROM " + table + " WHERE _id=" + rowId + ")");
            }
        }
    
        /***
         * Finds the name of the first table, which is editable.
         *
         * @param tables a list of tables
         * @return the first table listed
         */
        public static String findEditTable(String tables) {
            if (!TextUtils.isEmpty(tables)) {
                // find the first word terminated by either a space or a comma
                int spacepos = tables.indexOf(' ');
                int commapos = tables.indexOf(',');
    
                if (spacepos > 0 && (spacepos < commapos || commapos < 0)) {
                    return tables.substring(0, spacepos);
                } else if (commapos > 0 && (commapos < spacepos || spacepos < 0) ) {
                    return tables.substring(0, commapos);
                }
                return tables;
            } else {
                throw new IllegalStateException("Invalid tables");
            }
        }
    
        /***
         * Compiles an SQL statement into a reusable pre-compiled statement object.
         * The parameters are identical to {@link #execSQL(String)}. You may put ?s in the
         * statement and fill in those values with {@link SQLiteProgram#bindString}
         * and {@link SQLiteProgram#bindLong} each time you want to run the
         * statement. Statements may not return result sets larger than 1x1.
         *
         * @param sql The raw SQL statement, may contain ? for unknown values to be
         *            bound later.
         * @return a pre-compiled statement object.
         */
        public SQLiteStatement compileStatement(String sql) throws SQLException {
            lock();
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
            try {
                return new SQLiteStatement(this, sql);
            } finally {
                unlock();
            }
        }
    
        /***
         * Query the given URL, returning a {@link Cursor} over the result set.
         *
         * @param distinct true if you want each row to be unique, false otherwise.
         * @param table The table name to compile the query against.
         * @param columns A list of which columns to return. Passing null will
         *            return all columns, which is discouraged to prevent reading
         *            data from storage that isn't going to be used.
         * @param selection A filter declaring which rows to return, formatted as an
         *            SQL WHERE clause (excluding the WHERE itself). Passing null
         *            will return all rows for the given table.
         * @param selectionArgs You may include ?s in selection, which will be
         *         replaced by the values from selectionArgs, in order that they
         *         appear in the selection. The values will be bound as Strings.
         * @param groupBy A filter declaring how to group rows, formatted as an SQL
         *            GROUP BY clause (excluding the GROUP BY itself). Passing null
         *            will cause the rows to not be grouped.
         * @param having A filter declare which row groups to include in the cursor,
         *            if row grouping is being used, formatted as an SQL HAVING
         *            clause (excluding the HAVING itself). Passing null will cause
         *            all row groups to be included, and is required when row
         *            grouping is not being used.
         * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
         *            (excluding the ORDER BY itself). Passing null will use the
         *            default sort order, which may be unordered.
         * @param limit Limits the number of rows returned by the query,
         *            formatted as LIMIT clause. Passing null denotes no LIMIT clause.
         * @return A Cursor object, which is positioned before the first entry
         * @see Cursor
         */
        public Cursor query(boolean distinct, String table, String[] columns,
                String selection, String[] selectionArgs, String groupBy,
                String having, String orderBy, String limit) {
            return queryWithFactory(null, distinct, table, columns, selection, selectionArgs,
                    groupBy, having, orderBy, limit);
        }
    
        /***
         * Query the given URL, returning a {@link Cursor} over the result set.
         *
         * @param cursorFactory the cursor factory to use, or null for the default factory
         * @param distinct true if you want each row to be unique, false otherwise.
         * @param table The table name to compile the query against.
         * @param columns A list of which columns to return. Passing null will
         *            return all columns, which is discouraged to prevent reading
         *            data from storage that isn't going to be used.
         * @param selection A filter declaring which rows to return, formatted as an
         *            SQL WHERE clause (excluding the WHERE itself). Passing null
         *            will return all rows for the given table.
         * @param selectionArgs You may include ?s in selection, which will be
         *         replaced by the values from selectionArgs, in order that they
         *         appear in the selection. The values will be bound as Strings.
         * @param groupBy A filter declaring how to group rows, formatted as an SQL
         *            GROUP BY clause (excluding the GROUP BY itself). Passing null
         *            will cause the rows to not be grouped.
         * @param having A filter declare which row groups to include in the cursor,
         *            if row grouping is being used, formatted as an SQL HAVING
         *            clause (excluding the HAVING itself). Passing null will cause
         *            all row groups to be included, and is required when row
         *            grouping is not being used.
         * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
         *            (excluding the ORDER BY itself). Passing null will use the
         *            default sort order, which may be unordered.
         * @param limit Limits the number of rows returned by the query,
         *            formatted as LIMIT clause. Passing null denotes no LIMIT clause.
         * @return A Cursor object, which is positioned before the first entry
         * @see Cursor
         */
        public Cursor queryWithFactory(CursorFactory cursorFactory,
                boolean distinct, String table, String[] columns,
                String selection, String[] selectionArgs, String groupBy,
                String having, String orderBy, String limit) {
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
            String sql = SQLiteQueryBuilder.buildQueryString(
                    distinct, table, columns, selection, groupBy, having, orderBy, limit);
    
            return rawQueryWithFactory(
                    cursorFactory, sql, selectionArgs, findEditTable(table));
        }
    
        /***
         * Query the given table, returning a {@link Cursor} over the result set.
         *
         * @param table The table name to compile the query against.
         * @param columns A list of which columns to return. Passing null will
         *            return all columns, which is discouraged to prevent reading
         *            data from storage that isn't going to be used.
         * @param selection A filter declaring which rows to return, formatted as an
         *            SQL WHERE clause (excluding the WHERE itself). Passing null
         *            will return all rows for the given table.
         * @param selectionArgs You may include ?s in selection, which will be
         *         replaced by the values from selectionArgs, in order that they
         *         appear in the selection. The values will be bound as Strings.
         * @param groupBy A filter declaring how to group rows, formatted as an SQL
         *            GROUP BY clause (excluding the GROUP BY itself). Passing null
         *            will cause the rows to not be grouped.
         * @param having A filter declare which row groups to include in the cursor,
         *            if row grouping is being used, formatted as an SQL HAVING
         *            clause (excluding the HAVING itself). Passing null will cause
         *            all row groups to be included, and is required when row
         *            grouping is not being used.
         * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
         *            (excluding the ORDER BY itself). Passing null will use the
         *            default sort order, which may be unordered.
         * @return A {@link Cursor} object, which is positioned before the first entry
         * @see Cursor
         */
        public Cursor query(String table, String[] columns, String selection,
                String[] selectionArgs, String groupBy, String having,
                String orderBy) {
    
            return query(false, table, columns, selection, selectionArgs, groupBy,
                    having, orderBy, null /** limit */);
        }
    
        /***
         * Query the given table, returning a {@link Cursor} over the result set.
         *
         * @param table The table name to compile the query against.
         * @param columns A list of which columns to return. Passing null will
         *            return all columns, which is discouraged to prevent reading
         *            data from storage that isn't going to be used.
         * @param selection A filter declaring which rows to return, formatted as an
         *            SQL WHERE clause (excluding the WHERE itself). Passing null
         *            will return all rows for the given table.
         * @param selectionArgs You may include ?s in selection, which will be
         *         replaced by the values from selectionArgs, in order that they
         *         appear in the selection. The values will be bound as Strings.
         * @param groupBy A filter declaring how to group rows, formatted as an SQL
         *            GROUP BY clause (excluding the GROUP BY itself). Passing null
         *            will cause the rows to not be grouped.
         * @param having A filter declare which row groups to include in the cursor,
         *            if row grouping is being used, formatted as an SQL HAVING
         *            clause (excluding the HAVING itself). Passing null will cause
         *            all row groups to be included, and is required when row
         *            grouping is not being used.
         * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
         *            (excluding the ORDER BY itself). Passing null will use the
         *            default sort order, which may be unordered.
         * @param limit Limits the number of rows returned by the query,
         *            formatted as LIMIT clause. Passing null denotes no LIMIT clause.
         * @return A {@link Cursor} object, which is positioned before the first entry
         * @see Cursor
         */
        public Cursor query(String table, String[] columns, String selection,
                String[] selectionArgs, String groupBy, String having,
                String orderBy, String limit) {
    
            return query(false, table, columns, selection, selectionArgs, groupBy,
                    having, orderBy, limit);
        }
    
        /***
         * Runs the provided SQL and returns a {@link Cursor} over the result set.
         *
         * @param sql the SQL query. The SQL string must not be ; terminated
         * @param selectionArgs You may include ?s in where clause in the query,
         *     which will be replaced by the values from selectionArgs. The
         *     values will be bound as Strings.
         * @return A {@link Cursor} object, which is positioned before the first entry
         */
        public Cursor rawQuery(String sql, String[] selectionArgs) {
            return rawQueryWithFactory(null, sql, selectionArgs, null);
        }
    
        /***
         * Runs the provided SQL and returns a cursor over the result set.
         *
         * @param cursorFactory the cursor factory to use, or null for the default factory
         * @param sql the SQL query. The SQL string must not be ; terminated
         * @param selectionArgs You may include ?s in where clause in the query,
         *     which will be replaced by the values from selectionArgs. The
         *     values will be bound as Strings.
         * @param editTable the name of the first table, which is editable
         * @return A {@link Cursor} object, which is positioned before the first entry
         */
        public Cursor rawQueryWithFactory(
                CursorFactory cursorFactory, String sql, String[] selectionArgs,
                String editTable) {
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
            long timeStart = 0;
    
            if (Config.LOGV || mSlowQueryThreshold != -1) {
                timeStart = System.currentTimeMillis();
            }
    
            SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable);
    
            Cursor cursor = null;
            try {
                cursor = driver.query(
                        cursorFactory != null ? cursorFactory : mFactory,
                        selectionArgs);
            } finally {
                if (Config.LOGV || mSlowQueryThreshold != -1) {
    
                    // Force query execution
                    int count = -1;
                    if (cursor != null) {
                        count = cursor.getCount();
                    }
    
                    long duration = System.currentTimeMillis() - timeStart;
    
                    if (Config.LOGV || duration >= mSlowQueryThreshold) {
                        Log.v(SQLiteCursor.TAG,
                              "query (" + duration + " ms): " + driver.toString() + ", args are "
                                      + (selectionArgs != null
                                      ? TextUtils.join(",", selectionArgs)
                                      : "<null>")  + ", count is " + count);
                    }
                }
            }
            return cursor;
        }
    
        /***
         * Runs the provided SQL and returns a cursor over the result set.
         * The cursor will read an initial set of rows and the return to the caller.
         * It will continue to read in batches and send data changed notifications
         * when the later batches are ready.
         * @param sql the SQL query. The SQL string must not be ; terminated
         * @param selectionArgs You may include ?s in where clause in the query,
         *     which will be replaced by the values from selectionArgs. The
         *     values will be bound as Strings.
         * @param initialRead set the initial count of items to read from the cursor
         * @param maxRead set the count of items to read on each iteration after the first
         * @return A {@link Cursor} object, which is positioned before the first entry
         *
         * This work is incomplete and not fully tested or reviewed, so currently
         * hidden.
         * @hide
         */
        public Cursor rawQuery(String sql, String[] selectionArgs,
                int initialRead, int maxRead) {
            SQLiteCursor c = (SQLiteCursor)rawQueryWithFactory(
                    null, sql, selectionArgs, null);
            c.setLoadStyle(initialRead, maxRead);
            return c;
        }
    
        /***
         * Convenience method for inserting a row into the database.
         *
         * @param table the table to insert the row into
         * @param nullColumnHack SQL doesn't allow inserting a completely empty row,
         *            so if initialValues is empty this column will explicitly be
         *            assigned a NULL value
         * @param values this map contains the initial column values for the
         *            row. The keys should be the column names and the values the
         *            column values
         * @return the row ID of the newly inserted row, or -1 if an error occurred
         */
        public long insert(String table, String nullColumnHack, ContentValues values) {
            try {
                return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
            } catch (SQLException e) {
                Log.e(TAG, "Error inserting " + values, e);
                return -1;
            }
        }
    
        /***
         * Convenience method for inserting a row into the database.
         *
         * @param table the table to insert the row into
         * @param nullColumnHack SQL doesn't allow inserting a completely empty row,
         *            so if initialValues is empty this column will explicitly be
         *            assigned a NULL value
         * @param values this map contains the initial column values for the
         *            row. The keys should be the column names and the values the
         *            column values
         * @throws SQLException
         * @return the row ID of the newly inserted row, or -1 if an error occurred
         */
        public long insertOrThrow(String table, String nullColumnHack, ContentValues values)
                throws SQLException {
            return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
        }
    
        /***
         * Convenience method for replacing a row in the database.
         *
         * @param table the table in which to replace the row
         * @param nullColumnHack SQL doesn't allow inserting a completely empty row,
         *            so if initialValues is empty this row will explicitly be
         *            assigned a NULL value
         * @param initialValues this map contains the initial column values for
         *   the row. The key
         * @return the row ID of the newly inserted row, or -1 if an error occurred
         */
        public long replace(String table, String nullColumnHack, ContentValues initialValues) {
            try {
                return insertWithOnConflict(table, nullColumnHack, initialValues,
                        CONFLICT_REPLACE);
            } catch (SQLException e) {
                Log.e(TAG, "Error inserting " + initialValues, e);
                return -1;
            }
        }
    
        /***
         * Convenience method for replacing a row in the database.
         *
         * @param table the table in which to replace the row
         * @param nullColumnHack SQL doesn't allow inserting a completely empty row,
         *            so if initialValues is empty this row will explicitly be
         *            assigned a NULL value
         * @param initialValues this map contains the initial column values for
         *   the row. The key
         * @throws SQLException
         * @return the row ID of the newly inserted row, or -1 if an error occurred
         */
        public long replaceOrThrow(String table, String nullColumnHack,
                ContentValues initialValues) throws SQLException {
            return insertWithOnConflict(table, nullColumnHack, initialValues,
                    CONFLICT_REPLACE);
        }
    
        /***
         * General method for inserting a row into the database.
         *
         * @param table the table to insert the row into
         * @param nullColumnHack SQL doesn't allow inserting a completely empty row,
         *            so if initialValues is empty this column will explicitly be
         *            assigned a NULL value
         * @param initialValues this map contains the initial column values for the
         *            row. The keys should be the column names and the values the
         *            column values
         * @param conflictAlgorithm for insert conflict resolver
         * @return the row ID of the newly inserted row
         * OR the primary key of the existing row if the input param 'conflictAlgorithm' =
         * {@link #CONFLICT_IGNORE}
         * OR -1 if any error
         */
        public long insertWithOnConflict(String table, String nullColumnHack,
                ContentValues initialValues, int conflictAlgorithm) {
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
    
            // Measurements show most sql lengths <= 152
            StringBuilder sql = new StringBuilder(152);
            sql.append("INSERT");
            sql.append(CONFLICT_VALUES[conflictAlgorithm]);
            sql.append(" INTO ");
            sql.append(table);
            // Measurements show most values lengths < 40
            StringBuilder values = new StringBuilder(40);
    
            Set<Map.Entry<String, Object>> entrySet = null;
            if (initialValues != null && initialValues.size() > 0) {
                entrySet = initialValues.valueSet();
                Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator();
                sql.append('(');
    
                boolean needSeparator = false;
                while (entriesIter.hasNext()) {
                    if (needSeparator) {
                        sql.append(", ");
                        values.append(", ");
                    }
                    needSeparator = true;
                    Map.Entry<String, Object> entry = entriesIter.next();
                    sql.append(entry.getKey());
                    values.append('?');
                }
    
                sql.append(')');
            } else {
                sql.append("(" + nullColumnHack + ") ");
                values.append("NULL");
            }
    
            sql.append(" VALUES(");
            sql.append(values);
            sql.append(");");
    
            lock();
            SQLiteStatement statement = null;
            try {
                statement = compileStatement(sql.toString());
    
                // Bind the values
                if (entrySet != null) {
                    int size = entrySet.size();
                    Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator();
                    for (int i = 0; i < size; i++) {
                        Map.Entry<String, Object> entry = entriesIter.next();
                        DatabaseUtils.bindObjectToProgram(statement, i + 1, entry.getValue());
                    }
                }
    
                // Run the program and then cleanup
                statement.execute();
    
                long insertedRowId = lastInsertRow();
                if (insertedRowId == -1) {
                    Log.e(TAG, "Error inserting " + initialValues + " using " + sql);
                } else {
                    if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) {
                        Log.v(TAG, "Inserting row " + insertedRowId + " from "
                                + initialValues + " using " + sql);
                    }
                }
                return insertedRowId;
            } catch (SQLiteDatabaseCorruptException e) {
                onCorruption();
                throw e;
            } finally {
                if (statement != null) {
                    statement.close();
                }
                unlock();
            }
        }
    
        /***
         * Convenience method for deleting rows in the database.
         *
         * @param table the table to delete from
         * @param whereClause the optional WHERE clause to apply when deleting.
         *            Passing null will delete all rows.
         * @return the number of rows affected if a whereClause is passed in, 0
         *         otherwise. To remove all rows and get a count pass "1" as the
         *         whereClause.
         */
        public int delete(String table, String whereClause, String[] whereArgs) {
            lock();
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
            SQLiteStatement statement = null;
            try {
                statement = compileStatement("DELETE FROM " + table
                        + (!TextUtils.isEmpty(whereClause)
                        ? " WHERE " + whereClause : ""));
                if (whereArgs != null) {
                    int numArgs = whereArgs.length;
                    for (int i = 0; i < numArgs; i++) {
                        DatabaseUtils.bindObjectToProgram(statement, i + 1, whereArgs[i]);
                    }
                }
                statement.execute();
                return lastChangeCount();
            } catch (SQLiteDatabaseCorruptException e) {
                onCorruption();
                throw e;
            } finally {
                if (statement != null) {
                    statement.close();
                }
                unlock();
            }
        }
    
        /***
         * Convenience method for updating rows in the database.
         *
         * @param table the table to update in
         * @param values a map from column names to new column values. null is a
         *            valid value that will be translated to NULL.
         * @param whereClause the optional WHERE clause to apply when updating.
         *            Passing null will update all rows.
         * @return the number of rows affected
         */
        public int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
            return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE);
        }
    
        /***
         * Convenience method for updating rows in the database.
         *
         * @param table the table to update in
         * @param values a map from column names to new column values. null is a
         *            valid value that will be translated to NULL.
         * @param whereClause the optional WHERE clause to apply when updating.
         *            Passing null will update all rows.
         * @param conflictAlgorithm for update conflict resolver
         * @return the number of rows affected
         */
        public int updateWithOnConflict(String table, ContentValues values,
                String whereClause, String[] whereArgs, int conflictAlgorithm) {
            if (values == null || values.size() == 0) {
                throw new IllegalArgumentException("Empty values");
            }
    
            StringBuilder sql = new StringBuilder(120);
            sql.append("UPDATE ");
            sql.append(CONFLICT_VALUES[conflictAlgorithm]);
            sql.append(table);
            sql.append(" SET ");
    
            Set<Map.Entry<String, Object>> entrySet = values.valueSet();
            Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator();
    
            while (entriesIter.hasNext()) {
                Map.Entry<String, Object> entry = entriesIter.next();
                sql.append(entry.getKey());
                sql.append("=?");
                if (entriesIter.hasNext()) {
                    sql.append(", ");
                }
            }
    
            if (!TextUtils.isEmpty(whereClause)) {
                sql.append(" WHERE ");
                sql.append(whereClause);
            }
    
            lock();
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
            SQLiteStatement statement = null;
            try {
                statement = compileStatement(sql.toString());
    
                // Bind the values
                int size = entrySet.size();
                entriesIter = entrySet.iterator();
                int bindArg = 1;
                for (int i = 0; i < size; i++) {
                    Map.Entry<String, Object> entry = entriesIter.next();
                    DatabaseUtils.bindObjectToProgram(statement, bindArg, entry.getValue());
                    bindArg++;
                }
    
                if (whereArgs != null) {
                    size = whereArgs.length;
                    for (int i = 0; i < size; i++) {
                        statement.bindString(bindArg, whereArgs[i]);
                        bindArg++;
                    }
                }
    
                // Run the program and then cleanup
                statement.execute();
                int numChangedRows = lastChangeCount();
                if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "Updated " + numChangedRows + " using " + values + " and " + sql);
                }
                return numChangedRows;
            } catch (SQLiteDatabaseCorruptException e) {
                onCorruption();
                throw e;
            } catch (SQLException e) {
                Log.e(TAG, "Error updating " + values + " using " + sql);
                throw e;
            } finally {
                if (statement != null) {
                    statement.close();
                }
                unlock();
            }
        }
    
        /***
         * Execute a single SQL statement that is not a query. For example, CREATE
         * TABLE, DELETE, INSERT, etc. Multiple statements separated by ;s are not
         * supported. it takes a write lock
         *
         * @throws SQLException If the SQL string is invalid for some reason
         */
        public void execSQL(String sql) throws SQLException {
            long timeStart = SystemClock.uptimeMillis();
            lock();
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
            logTimeStat(mLastSqlStatement, timeStart, GET_LOCK_LOG_PREFIX);
            try {
                native_execSQL(sql);
            } catch (SQLiteDatabaseCorruptException e) {
                onCorruption();
                throw e;
            } finally {
                unlock();
            }
    
            // Log commit statements along with the most recently executed
            // SQL statement for disambiguation.  Note that instance
            // equality to COMMIT_SQL is safe here.
            if (sql == COMMIT_SQL) {
                logTimeStat(mLastSqlStatement, timeStart, COMMIT_SQL);
            } else {
                logTimeStat(sql, timeStart, null);
            }
        }
    
        /***
         * Execute a single SQL statement that is not a query. For example, CREATE
         * TABLE, DELETE, INSERT, etc. Multiple statements separated by ;s are not
         * supported. it takes a write lock,
         *
         * @param sql
         * @param bindArgs only byte[], String, Long and Double are supported in bindArgs.
         * @throws SQLException If the SQL string is invalid for some reason
         */
        public void execSQL(String sql, Object[] bindArgs) throws SQLException {
            if (bindArgs == null) {
                throw new IllegalArgumentException("Empty bindArgs");
            }
            long timeStart = SystemClock.uptimeMillis();
            lock();
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
            SQLiteStatement statement = null;
            try {
                statement = compileStatement(sql);
                if (bindArgs != null) {
                    int numArgs = bindArgs.length;
                    for (int i = 0; i < numArgs; i++) {
                        DatabaseUtils.bindObjectToProgram(statement, i + 1, bindArgs[i]);
                    }
                }
                statement.execute();
            } catch (SQLiteDatabaseCorruptException e) {
                onCorruption();
                throw e;
            } finally {
                if (statement != null) {
                    statement.close();
                }
                unlock();
            }
            logTimeStat(sql, timeStart);
        }
    
        @Override
        protected void finalize() {
            if (isOpen()) {
                Log.e(TAG, "close() was never explicitly called on database '" +
                        mPath + "' ", mStackTrace);
                closeClosable();
                onAllReferencesReleased();
            }
        }
    
        /***
         * Private constructor. See {@link #create} and {@link #openDatabase}.
         *
         * @param path The full path to the database
         * @param factory The factory to use when creating cursors, may be NULL.
         * @param flags 0 or {@link #NO_LOCALIZED_COLLATORS}.  If the database file already
         *              exists, mFlags will be updated appropriately.
         */
        private SQLiteDatabase(String path, CursorFactory factory, int flags) {
            if (path == null) {
                throw new IllegalArgumentException("path should not be null");
            }
            mFlags = flags;
            mPath = path;
            mSlowQueryThreshold = SystemProperties.getInt(LOG_SLOW_QUERIES_PROPERTY, -1);
            mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
            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
         */
        public boolean isReadOnly() {
            return (mFlags & OPEN_READ_MASK) == OPEN_READONLY;
        }
    
        /***
         * @return true if the DB is currently open (has not been closed)
         */
        public boolean isOpen() {
            return mNativeHandle != 0;
        }
    
        public boolean needUpgrade(int newVersion) {
            return newVersion > getVersion();
        }
    
        /***
         * Getter for the path to the database file.
         *
         * @return the path to our database file.
         */
        public final String getPath() {
            return mPath;
        }
    
        /** package */ void logTimeStat(String sql, long beginMillis) {
            logTimeStat(sql, beginMillis, null);
        }
    
        /** package */ void logTimeStat(String sql, long beginMillis, String prefix) {
            // Keep track of the last statement executed here, as this is
            // the common funnel through which all methods of hitting
            // libsqlite eventually flow.
            mLastSqlStatement = sql;
    
            // Sample fast queries in proportion to the time taken.
            // Quantize the % first, so the logged sampling probability
            // exactly equals the actual sampling rate for this query.
    
            int samplePercent;
            long durationMillis = SystemClock.uptimeMillis() - beginMillis;
            if (durationMillis == 0 && prefix == GET_LOCK_LOG_PREFIX) {
                // The common case is locks being uncontended.  Don't log those,
                // even at 1%, which is our default below.
                return;
            }
            if (sQueryLogTimeInMillis == 0) {
                sQueryLogTimeInMillis = SystemProperties.getInt("db.db_operation.threshold_ms", 500);
            }
            if (durationMillis >= sQueryLogTimeInMillis) {
                samplePercent = 100;
            } else {;
                samplePercent = (int) (100 * durationMillis / sQueryLogTimeInMillis) + 1;
                if (mRandom.nextInt(100) >= samplePercent) return;
            }
    
            // Note: the prefix will be "COMMIT;" or "GETLOCK:" when non-null.  We wait to do
            // it here so we avoid allocating in the common case.
            if (prefix != null) {
                sql = prefix + sql;
            }
    
            if (sql.length() > QUERY_LOG_SQL_LENGTH) sql = sql.substring(0, QUERY_LOG_SQL_LENGTH);
    
            // ActivityThread.currentPackageName() only returns non-null if the
            // current thread is an application main thread.  This parameter tells
            // us whether an event loop is blocked, and if so, which app it is.
            //
            // Sadly, there's no fast way to determine app name if this is *not* a
            // main thread, or when we are invoked via Binder (e.g. ContentProvider).
            // Hopefully the full path to the database will be informative enough.
    
            String blockingPackage = ActivityThread.currentPackageName();
            if (blockingPackage == null) blockingPackage = "";
    
            EventLog.writeEvent(
                EVENT_DB_OPERATION,
                getPathForLogs(),
                sql,
                durationMillis,
                blockingPackage,
                samplePercent);
        }
    
        /***
         * Removes email addresses from database filenames before they're
         * logged to the EventLog where otherwise apps could potentially
         * read them.
         */
        private String getPathForLogs() {
            if (mPathForLogs != null) {
                return mPathForLogs;
            }
            if (mPath == null) {
                return null;
            }
            if (mPath.indexOf('@') == -1) {
                mPathForLogs = mPath;
            } else {
                mPathForLogs = EMAIL_IN_DB_PATTERN.matcher(mPath).replaceAll("XX@YY");
            }
            return mPathForLogs;
        }
    
        /***
         * Sets the locale for this database.  Does nothing if this database has
         * the NO_LOCALIZED_COLLATORS flag set or was opened read only.
         * @throws SQLException if the locale could not be set.  The most common reason
         * for this is that there is no collator available for the locale you requested.
         * In this case the database remains unchanged.
         */
        public void setLocale(Locale locale) {
            lock();
            try {
                native_setLocale(locale.toString(), mFlags);
            } finally {
                unlock();
            }
        }
    
        /**
         * ============================================================================
         *
         *       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) {
            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;
            }
    
            SQLiteCompiledSql compiledSql = null;
            synchronized(mCompiledQueries) {
                // don't insert the new mapping if a mapping already exists
                compiledSql = mCompiledQueries.get(sql);
                if (compiledSql != null) {
                    return;
                }
                // add this <sql, compiledStatement> to the cache
                if (mCompiledQueries.size() == mMaxSqlCacheSize) {
                    /**
                     * cache size of {@link #mMaxSqlCacheSize} is not enough for this app.
                     * log a warning MAX_WARNINGS_ON_CACHESIZE_CONDITION times
                     * chances are it is NOT using ? for bindargs - so caching is useless.
                     * TODO: either let the callers set max cchesize for their app, or intelligently
                     * figure out what should be cached for a given app.
                     */
                    if (++mCacheFullWarnings == MAX_WARNINGS_ON_CACHESIZE_CONDITION) {
                        Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " +
                                getPath() + "; i.e., NO space for this sql statement in cache: " +
                                sql + ". Please change your sql statements to use '?' for " +
                                "bindargs, instead of using actual values");
                    }
                    // don't add this entry to cache
                } else {
                    // cache is NOT full. add this to cache.
                    mCompiledQueries.put(sql, compiledStatement);
                    if (SQLiteDebug.DEBUG_SQL_CACHE) {
                        Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" +
                                mCompiledQueries.size() + "|" + sql);
                    }
                }
            }
            return;
        }
    
    
        private 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) {
                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;
        }
    
        /***
         * returns true if the given sql is cached in compiled-sql cache.
         * @hide
         */
        public boolean isInCompiledSqlCache(String sql) {
            synchronized(mCompiledQueries) {
                return mCompiledQueries.containsKey(sql);
            }
        }
    
        /***
         * purges the given sql from the compiled-sql cache.
         * @hide
         */
        public void purgeFromCompiledSqlCache(String sql) {
            synchronized(mCompiledQueries) {
                mCompiledQueries.remove(sql);
            }
        }
    
        /***
         * remove everything from the compiled sql cache
         * @hide
         */
        public void resetCompiledSqlCache() {
            synchronized(mCompiledQueries) {
                mCompiledQueries.clear();
            }
        }
    
        /***
         * return the current maxCacheSqlCacheSize
         * @hide
         */
        public synchronized int getMaxSqlCacheSize() {
            return mMaxSqlCacheSize;
        }
    
        /***
         * 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).
         *
         * max cache size can ONLY be increased from its current size (default = 0).
         * if this method is called with smaller size than the current value of mMaxSqlCacheSize,
         * then IllegalStateException is thrown
         *
         * 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)
         * @throws IllegalStateException if input cacheSize > MAX_SQL_CACHE_SIZE or < 0 or
         * < the value set with previous setMaxSqlCacheSize() call.
         *
         * @hide
         */
        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;
        }
    
        static class ActiveDatabases {
            private static final ActiveDatabases activeDatabases = new ActiveDatabases();
            private HashSet<WeakReference<SQLiteDatabase>> mActiveDatabases =
                new HashSet<WeakReference<SQLiteDatabase>>();
            private ActiveDatabases() {} // disable instantiation of this class
            static ActiveDatabases getInstance() {return activeDatabases;}
        }
    
        /***
         * this method is used to collect data about ALL open databases in the current process.
         * bugreport is a user of this data. 
         */
        /** package */ static ArrayList<DbStats> getDbStats() {
            ArrayList<DbStats> dbStatsList = new ArrayList<DbStats>();
            for (WeakReference<SQLiteDatabase> w : ActiveDatabases.getInstance().mActiveDatabases) {
                SQLiteDatabase db = w.get();
                if (db == null || !db.isOpen()) {
                    continue;
                }
                // get SQLITE_DBSTATUS_LOOKASIDE_USED for the db
                int lookasideUsed = db.native_getDbLookaside();
    
                // get the lastnode of the dbname
                String path = db.getPath();
                int indx = path.lastIndexOf("/");
                String lastnode = path.substring((indx != -1) ? ++indx : 0);
    
                // get list of attached dbs and for each db, get its size and pagesize
                ArrayList<Pair<String, String>> attachedDbs = getAttachedDbs(db);
                if (attachedDbs == null) {
                    continue;
                }
                for (int i = 0; i < attachedDbs.size(); i++) {
                    Pair<String, String> p = attachedDbs.get(i);
                    long pageCount = getPragmaVal(db, p.first + ".page_count;");
    
                    // first entry in the attached db list is always the main database
                    // don't worry about prefixing the dbname with "main"
                    String dbName;
                    if (i == 0) {
                        dbName = lastnode;
                    } else {
                        // lookaside is only relevant for the main db
                        lookasideUsed = 0;
                        dbName = "  (attached) " + p.first;
                        // if the attached db has a path, attach the lastnode from the path to above
                        if (p.second.trim().length() > 0) {
                            int idx = p.second.lastIndexOf("/");
                            dbName += " : " + p.second.substring((idx != -1) ? ++idx : 0);
                        }
                    }
                    if (pageCount > 0) {
                        dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
                                lookasideUsed));
                    }
                }
            }
            return dbStatsList;
        }
    
        /***
         * get the specified pragma value from sqlite for the specified database.
         * only handles pragma's that return int/long.
         * NO JAVA locks are held in this method.
         * TODO: use this to do all pragma's in this class
         */
        private static long getPragmaVal(SQLiteDatabase db, String pragma) {
            if (!db.isOpen()) {
                return 0;
            }
            SQLiteStatement prog = null;
            try {
                prog = new SQLiteStatement(db, "PRAGMA " + pragma);
                long val = prog.simpleQueryForLong();
                return val;
            } finally {
                if (prog != null) prog.close();
            }
        }
    
        /***
         * returns list of full pathnames of all attached databases
         * including the main database
         * TODO: move this to {@link DatabaseUtils}
         */
        private static ArrayList<Pair<String, String>> getAttachedDbs(SQLiteDatabase dbObj) {
            if (!dbObj.isOpen()) {
                return null;
            }
            ArrayList<Pair<String, String>> attachedDbs = new ArrayList<Pair<String, String>>();
            Cursor c = dbObj.rawQuery("pragma database_list;", null);
            while (c.moveToNext()) {
                 attachedDbs.add(new Pair<String, String>(c.getString(1), c.getString(2)));
            }
            c.close();
            return attachedDbs;
        }
    
        /***
         * Native call to open the database.
         *
         * @param path The full path to the database
         */
        private native void dbopen(String path, int flags);
    
        /***
         * Native call to setup tracing of all sql statements
         *
         * @param path the full path to the database
         */
        private native void enableSqlTracing(String path);
    
        /***
         * Native call to setup profiling of all sql statements.
         * currently, sqlite's profiling = printing of execution-time
         * (wall-clock time) of each of the sql statements, as they
         * are executed.
         *
         * @param path the full path to the database
         */
        private native void enableSqlProfiling(String path);
    
        /***
         * Native call to execute a raw SQL statement. {@link #lock} must be held
         * when calling this method.
         *
         * @param sql The raw SQL string
         * @throws SQLException
         */
        /** package */ native void native_execSQL(String sql) throws SQLException;
    
        /***
         * Native call to set the locale.  {@link #lock} must be held when calling
         * this method.
         * @throws SQLException
         */
        /** package */ native void native_setLocale(String loc, int flags);
    
        /***
         * Returns the row ID of the last row inserted into the database.
         *
         * @return the row ID of the last row inserted into the database.
         */
        /** package */ native long lastInsertRow();
    
        /***
         * Returns the number of changes made in the last statement executed.
         *
         * @return the number of changes made in the last statement executed.
         */
        /** package */ native int lastChangeCount();
    
        /***
         * return the SQLITE_DBSTATUS_LOOKASIDE_USED documented here
         * http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html
         * @return int value of SQLITE_DBSTATUS_LOOKASIDE_USED
         */
        private native int native_getDbLookaside();
    }
    
  • 相关阅读:
    notepad++ remove duplicate line
    notepad++ $ ^
    中文名: 交通事故责任认定, 英文名称: Traffic accident responsibility identification
    java 反射 获取Class对象的三种方式
    [转]java中byte转换int时为何与0xff进行与运算
    java中byte取值范围为什么是 -128到127
    bytes2HexString
    DBCS 从256开始
    android开发之使用拼音搜索汉字
    电脑故障,路由器及网络
  • 原文地址:https://www.cnblogs.com/royi123/p/3861081.html
Copyright © 2020-2023  润新知