首页 > 代码库 > SQLiteDatabase 源码

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 = http://www.mamicode.com/{@link SQLiteCompiledSql}"?" 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();}