diff options
Diffstat (limited to 'core/java/android/database')
5 files changed, 206 insertions, 102 deletions
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index 4063534..0687659 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -52,6 +52,21 @@ public class DatabaseUtils { private static final String[] countProjection = new String[]{"count(*)"}; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_SELECT = 1; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_UPDATE = 2; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_ATTACH = 3; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_BEGIN = 4; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_COMMIT = 5; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_ABORT = 6; + /** One of the values returned by {@link #getSqlStatementType(String)}. */ + public static final int STATEMENT_OTHER = 7; + /** * Special function for writing an exception result at the header of * a parcel, to be used when returning an exception from a transaction. @@ -1159,4 +1174,45 @@ public class DatabaseUtils { db.setVersion(dbVersion); db.close(); } + + /** + * Returns one of the following which represent the type of the given SQL statement. + * <ol> + * <li>{@link #STATEMENT_SELECT}</li> + * <li>{@link #STATEMENT_UPDATE}</li> + * <li>{@link #STATEMENT_ATTACH}</li> + * <li>{@link #STATEMENT_BEGIN}</li> + * <li>{@link #STATEMENT_COMMIT}</li> + * <li>{@link #STATEMENT_ABORT}</li> + * <li>{@link #STATEMENT_OTHER}</li> + * </ol> + * @param sql the SQL statement whose type is returned by this method + * @return one of the values listed above + */ + public static int getSqlStatementType(String sql) { + sql = sql.trim(); + if (sql.length() < 3) { + return STATEMENT_OTHER; + } + String prefixSql = sql.substring(0, 3).toUpperCase(); + if (prefixSql.equals("SEL")) { + return STATEMENT_SELECT; + } else if (prefixSql.equals("INS") || + prefixSql.equals("UPD") || + prefixSql.equals("REP") || + prefixSql.equals("DEL")) { + return STATEMENT_UPDATE; + } else if (prefixSql.equals("ATT")) { + return STATEMENT_ATTACH; + } else if (prefixSql.equals("COM")) { + return STATEMENT_COMMIT; + } else if (prefixSql.equals("END")) { + return STATEMENT_COMMIT; + } else if (prefixSql.equals("ROL")) { + return STATEMENT_ABORT; + } else if (prefixSql.equals("BEG")) { + return STATEMENT_BEGIN; + } + return STATEMENT_OTHER; + } } diff --git a/core/java/android/database/sqlite/DatabaseConnectionPool.java b/core/java/android/database/sqlite/DatabaseConnectionPool.java index 50b2919..54b0605 100644 --- a/core/java/android/database/sqlite/DatabaseConnectionPool.java +++ b/core/java/android/database/sqlite/DatabaseConnectionPool.java @@ -63,6 +63,9 @@ import java.util.Random; */ /* package */ void close() { synchronized(mParentDbObj) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Closing the connection pool on " + mParentDbObj.getPath() + toString()); + } for (int i = mPool.size() - 1; i >= 0; i--) { mPool.get(i).mDb.close(); } diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 623821b..a2fff73 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -192,6 +192,11 @@ public class SQLiteDatabase extends SQLiteClosable { */ private SQLiteTransactionListener mTransactionListener; + /** + * this member is set if {@link #execSQL(String)} is used to begin and end transactions. + */ + private boolean mTransactionUsingExecSql; + /** Synchronize on this when accessing the database */ private final ReentrantLock mLock = new ReentrantLock(true); @@ -236,9 +241,6 @@ public class SQLiteDatabase extends SQLiteClosable { /** Used by native code, do not rename. make it volatile, so it is thread-safe. */ /* package */ volatile int mNativeHandle = 0; - /** Used to make temp table names unique */ - /* package */ int mTempTableSequence = 0; - /** * The size, in bytes, of a block on "/data". This corresponds to the Unix * statfs.f_bsize field. note that this field is lazily initialized. @@ -621,9 +623,6 @@ public class SQLiteDatabase extends SQLiteClosable { // This thread didn't already have the lock, so begin a database // transaction now. - // STOPSHIP - uncomment the following 1 line - // if (exclusive) { - // STOPSHIP - remove the following 1 line if (exclusive && mConnectionPool == null) { execSQL("BEGIN EXCLUSIVE;"); } else { @@ -740,7 +739,50 @@ public class SQLiteDatabase extends SQLiteClosable { * return true if there is a transaction pending */ public boolean inTransaction() { - return mLock.getHoldCount() > 0; + return mLock.getHoldCount() > 0 || mTransactionUsingExecSql; + } + + /* package */ synchronized void setTransactionUsingExecSqlFlag() { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.i(TAG, "found execSQL('begin transaction')"); + } + mTransactionUsingExecSql = true; + } + + /* package */ synchronized void resetTransactionUsingExecSqlFlag() { + if (Log.isLoggable(TAG, Log.DEBUG)) { + if (mTransactionUsingExecSql) { + Log.i(TAG, "found execSQL('commit or end or rollback')"); + } + } + mTransactionUsingExecSql = false; + } + + /** + * Returns true if the caller is considered part of the current transaction, if any. + * <p> + * Caller is part of the current transaction if either of the following is true + * <ol> + * <li>If transaction is started by calling beginTransaction() methods AND if the caller is + * in the same thread as the thread that started the transaction. + * </li> + * <li>If the transaction is started by calling {@link #execSQL(String)} like this: + * execSQL("BEGIN transaction"). In this case, every thread in the process is considered + * part of the current transaction.</li> + * </ol> + * + * @return true if the caller is considered part of the current transaction, if any. + */ + /* package */ synchronized boolean amIInTransaction() { + // always do this test on the main database connection - NOT on pooled database connection + // since transactions always occur on the main database connections only. + SQLiteDatabase db = (isPooledConnection()) ? mParentConnObj : this; + boolean b = (!db.inTransaction()) ? false : + db.mTransactionUsingExecSql || db.mLock.isHeldByCurrentThread(); + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.i(TAG, "amIinTransaction: " + b); + } + return b; } /** @@ -932,6 +974,9 @@ public class SQLiteDatabase extends SQLiteClosable { DatabaseErrorHandler errorHandler, short connectionNum) { SQLiteDatabase db = new SQLiteDatabase(path, factory, flags, errorHandler, connectionNum); try { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.i(TAG, "opening the db : " + path); + } // Open the database. db.dbopen(path, flags); db.setLocale(Locale.getDefault()); @@ -1008,7 +1053,7 @@ public class SQLiteDatabase extends SQLiteClosable { if (!isOpen()) { return; // already closed } - if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { + if (Log.isLoggable(TAG, Log.DEBUG)) { Log.i(TAG, "closing db: " + mPath + " (connection # " + mConnectionNum); } lock(); @@ -1020,6 +1065,10 @@ public class SQLiteDatabase extends SQLiteClosable { // close this database instance - regardless of its reference count value dbclose(); if (mConnectionPool != null) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + assert mConnectionPool != null; + Log.i(TAG, mConnectionPool.toString()); + } mConnectionPool.close(); } } finally { @@ -1586,7 +1635,6 @@ public class SQLiteDatabase extends SQLiteClosable { public long insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm) { verifyDbIsOpen(); - BlockGuard.getThreadPolicy().onWriteToDisk(); // Measurements show most sql lengths <= 152 StringBuilder sql = new StringBuilder(152); @@ -1625,7 +1673,6 @@ public class SQLiteDatabase extends SQLiteClosable { sql.append(values); sql.append(");"); - lock(); SQLiteStatement statement = null; try { statement = compileStatement(sql.toString()); @@ -1649,7 +1696,6 @@ public class SQLiteDatabase extends SQLiteClosable { if (statement != null) { statement.close(); } - unlock(); } } @@ -1665,8 +1711,6 @@ public class SQLiteDatabase extends SQLiteClosable { */ public int delete(String table, String whereClause, String[] whereArgs) { verifyDbIsOpen(); - BlockGuard.getThreadPolicy().onWriteToDisk(); - lock(); SQLiteStatement statement = null; try { statement = compileStatement("DELETE FROM " + table @@ -1686,7 +1730,6 @@ public class SQLiteDatabase extends SQLiteClosable { if (statement != null) { statement.close(); } - unlock(); } } @@ -1717,7 +1760,6 @@ public class SQLiteDatabase extends SQLiteClosable { */ public int updateWithOnConflict(String table, ContentValues values, String whereClause, String[] whereArgs, int conflictAlgorithm) { - BlockGuard.getThreadPolicy().onWriteToDisk(); if (values == null || values.size() == 0) { throw new IllegalArgumentException("Empty values"); } @@ -1746,7 +1788,6 @@ public class SQLiteDatabase extends SQLiteClosable { } verifyDbIsOpen(); - lock(); SQLiteStatement statement = null; try { statement = compileStatement(sql.toString()); @@ -1781,7 +1822,6 @@ public class SQLiteDatabase extends SQLiteClosable { if (statement != null) { statement.close(); } - unlock(); } } @@ -1789,9 +1829,7 @@ public class SQLiteDatabase extends SQLiteClosable { * Execute a single SQL statement that is NOT a SELECT * or any other SQL statement that returns data. * <p> - * Use of this method is discouraged as it doesn't perform well when issuing the same SQL - * statement repeatedly (see {@link #compileStatement(String)} to prepare statements for - * repeated use), and it has no means to return any data (such as the number of affected rows). + * It has no means to return any data (such as the number of affected rows). * Instead, you're encouraged to use {@link #insert(String, String, ContentValues)}, * {@link #update(String, ContentValues, String, String[])}, et al, when possible. * </p> @@ -1807,35 +1845,17 @@ public class SQLiteDatabase extends SQLiteClosable { * @throws SQLException If the SQL string is invalid for some reason */ public void execSQL(String sql) throws SQLException { - sql = sql.trim(); - String prefix = sql.substring(0, 6); - if (prefix.equalsIgnoreCase("ATTACH")) { + int stmtType = DatabaseUtils.getSqlStatementType(sql); + if (stmtType == DatabaseUtils.STATEMENT_ATTACH) { disableWriteAheadLogging(); } - verifyDbIsOpen(); - BlockGuard.getThreadPolicy().onWriteToDisk(); long timeStart = SystemClock.uptimeMillis(); - lock(); logTimeStat(mLastSqlStatement, timeStart, GET_LOCK_LOG_PREFIX); - SQLiteStatement stmt = null; - try { - closePendingStatements(); - stmt = compileStatement(sql); - stmt.execute(); - } catch (SQLiteDatabaseCorruptException e) { - onCorruption(); - throw e; - } finally { - if (stmt != null) { - stmt.close(); - } - unlock(); - } + executeSql(sql, null); // 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) { + // SQL statement for disambiguation. + if (stmtType == DatabaseUtils.STATEMENT_COMMIT) { logTimeStat(mLastSqlStatement, timeStart, COMMIT_SQL); } else { logTimeStat(sql, timeStart, null); @@ -1886,13 +1906,15 @@ public class SQLiteDatabase extends SQLiteClosable { * @throws SQLException If the SQL string is invalid for some reason */ public void execSQL(String sql, Object[] bindArgs) throws SQLException { - BlockGuard.getThreadPolicy().onWriteToDisk(); if (bindArgs == null) { throw new IllegalArgumentException("Empty bindArgs"); } + executeSql(sql, bindArgs); + } + + private void executeSql(String sql, Object[] bindArgs) throws SQLException { verifyDbIsOpen(); long timeStart = SystemClock.uptimeMillis(); - lock(); SQLiteStatement statement = null; try { statement = compileStatement(sql); @@ -1902,7 +1924,7 @@ public class SQLiteDatabase extends SQLiteClosable { DatabaseUtils.bindObjectToProgram(statement, i + 1, bindArgs[i]); } } - statement.executeUpdateDelete(); + statement.execute(); } catch (SQLiteDatabaseCorruptException e) { onCorruption(); throw e; @@ -1910,7 +1932,6 @@ public class SQLiteDatabase extends SQLiteClosable { if (statement != null) { statement.close(); } - unlock(); } logTimeStat(sql, timeStart); } @@ -2142,7 +2163,8 @@ public class SQLiteDatabase extends SQLiteClosable { } } - private void deallocCachedSqlStatements() { + /** package-level access for testing purposes */ + /* package */ void deallocCachedSqlStatements() { synchronized (mCompiledQueries) { for (SQLiteCompiledSql compiledSql : mCompiledQueries.values()) { compiledSql.releaseSqlStatement(); @@ -2220,11 +2242,7 @@ public class SQLiteDatabase extends SQLiteClosable { } } - /** - * public visibility only for testing. otherwise, package visibility is sufficient - * @hide - */ - public void closePendingStatements() { + /* package */ void closePendingStatements() { if (!isOpen()) { // since this database is already closed, no need to finalize anything. mClosedStatementIds.clear(); @@ -2246,9 +2264,8 @@ public class SQLiteDatabase extends SQLiteClosable { /** * for testing only - * @hide */ - public ArrayList<Integer> getQueuedUpStmtList() { + /* package */ ArrayList<Integer> getQueuedUpStmtList() { return mClosedStatementIds; } @@ -2303,7 +2320,10 @@ public class SQLiteDatabase extends SQLiteClosable { // make sure this database has NO attached databases because sqlite's write-ahead-logging // doesn't work for databases with attached databases if (getAttachedDbs().size() > 1) { - Log.i(TAG, "this database: " + mPath + " has attached databases. can't enable WAL."); + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, + "this database: " + mPath + " has attached databases. can't enable WAL."); + } return false; } if (mConnectionPool == null) { @@ -2331,7 +2351,8 @@ public class SQLiteDatabase extends SQLiteClosable { /* package */ SQLiteDatabase getDatabaseHandle(String sql) { if (isPooledConnection()) { // this is a pooled database connection - if (isOpen()) { + // use it if it is open AND if I am not currently part of a transaction + if (isOpen() && !amIInTransaction()) { // TODO: use another connection from the pool // if this connection is currently in use by some other thread // AND if there are free connections in the pool @@ -2394,22 +2415,17 @@ public class SQLiteDatabase extends SQLiteClosable { if (isPooledConnection()) { throw new IllegalStateException("incorrect database connection handle"); } - if (Log.isLoggable(TAG, Log.DEBUG)) { - // this method shoudl never be called with anything other than SELECT - if (sql.substring(0, 6).equalsIgnoreCase("SELECT")) { - throw new IllegalStateException("unexpected SQL statement: " + sql); - } - } // use the current connection handle if - // 1. if this thread is in a transaction + // 1. if the caller is part of the ongoing transaction, if any // 2. OR, if there is NO connection handle pool setup - if ((inTransaction() && mLock.isHeldByCurrentThread()) || mConnectionPool == null) { + if (amIInTransaction() || mConnectionPool == null) { return this; } else { // get a connection handle from the pool if (Log.isLoggable(TAG, Log.DEBUG)) { assert mConnectionPool != null; + Log.i(TAG, mConnectionPool.toString()); } return mConnectionPool.get(sql); } diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index 017b65f..a4ebe5a 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -16,6 +16,7 @@ package android.database.sqlite; +import android.database.DatabaseUtils; import android.util.Log; import android.util.Pair; @@ -31,11 +32,6 @@ public abstract class SQLiteProgram extends SQLiteClosable { private static final String TAG = "SQLiteProgram"; - /** the type of sql statement being processed by this object */ - /* package */ static final int SELECT_STMT = 1; - private static final int UPDATE_STMT = 2; - private static final int OTHER_STMT = 3; - /** The database this program is compiled against. * @deprecated do not use this */ @@ -88,7 +84,9 @@ public abstract class SQLiteProgram extends SQLiteClosable { * <p> * It is protected (in multi-threaded environment) by {@link SQLiteProgram}.this */ - private ArrayList<Pair<Integer, Object>> bindArgs = null; + private ArrayList<Pair<Integer, Object>> mBindArgs = null; + + /* package */ final int mStatementType; /* package */ SQLiteProgram(SQLiteDatabase db, String sql) { this(db, sql, true); @@ -96,6 +94,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { /* package */ SQLiteProgram(SQLiteDatabase db, String sql, boolean compileFlag) { mSql = sql.trim(); + mStatementType = DatabaseUtils.getSqlStatementType(mSql); db.acquireReference(); db.addSQLiteClosable(this); mDatabase = db; @@ -107,7 +106,8 @@ public abstract class SQLiteProgram extends SQLiteClosable { private void compileSql() { // only cache CRUD statements - if (getSqlStatementType(mSql) == OTHER_STMT) { + if (mStatementType != DatabaseUtils.STATEMENT_SELECT && + mStatementType != DatabaseUtils.STATEMENT_UPDATE) { mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql); nStatement = mCompiledSql.nStatement; // since it is not in the cache, no need to acquire() it. @@ -150,22 +150,6 @@ public abstract class SQLiteProgram extends SQLiteClosable { nStatement = mCompiledSql.nStatement; } - /* package */ int getSqlStatementType(String sql) { - if (mSql.length() < 6) { - return OTHER_STMT; - } - String prefixSql = mSql.substring(0, 6); - if (prefixSql.equalsIgnoreCase("SELECT")) { - return SELECT_STMT; - } else if (prefixSql.equalsIgnoreCase("INSERT") || - prefixSql.equalsIgnoreCase("UPDATE") || - prefixSql.equalsIgnoreCase("REPLAC") || - prefixSql.equalsIgnoreCase("DELETE")) { - return UPDATE_STMT; - } - return OTHER_STMT; - } - @Override protected void onAllReferencesReleased() { releaseCompiledSqlIfNotInCache(); @@ -361,7 +345,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { */ public void clearBindings() { synchronized (this) { - bindArgs = null; + mBindArgs = null; if (this.nStatement == 0) { return; } @@ -380,7 +364,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { */ public void close() { synchronized (this) { - bindArgs = null; + mBindArgs = null; if (nHandle == 0 || !mDatabase.isOpen()) { return; } @@ -389,19 +373,19 @@ public abstract class SQLiteProgram extends SQLiteClosable { } private synchronized void addToBindArgs(int index, Object value) { - if (bindArgs == null) { - bindArgs = new ArrayList<Pair<Integer, Object>>(); + if (mBindArgs == null) { + mBindArgs = new ArrayList<Pair<Integer, Object>>(); } - bindArgs.add(new Pair<Integer, Object>(index, value)); + mBindArgs.add(new Pair<Integer, Object>(index, value)); } /* package */ synchronized void compileAndbindAllArgs() { assert nStatement == 0; compileSql(); - if (bindArgs == null) { + if (mBindArgs == null) { return; } - for (Pair<Integer, Object> p : bindArgs) { + for (Pair<Integer, Object> p : mBindArgs) { if (p.second == null) { native_bind_null(p.first); } else if (p.second instanceof Long) { diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java index 7a683e4..619764a 100644 --- a/core/java/android/database/sqlite/SQLiteStatement.java +++ b/core/java/android/database/sqlite/SQLiteStatement.java @@ -16,6 +16,7 @@ package android.database.sqlite; +import android.database.DatabaseUtils; import android.os.SystemClock; import dalvik.system.BlockGuard; @@ -36,6 +37,11 @@ public class SQLiteStatement extends SQLiteProgram private static final boolean WRITE = false; private SQLiteDatabase mOrigDb; + private int state; + /** possible value for {@link #state}. indicates that a transaction is started.} */ + private static final int TRANS_STARTED = 1; + /** possible value for {@link #state}. indicates that a lock is acquired.} */ + private static final int LOCK_ACQUIRED = 2; /** * Don't use SQLiteStatement constructor directly, please use @@ -150,18 +156,20 @@ public class SQLiteStatement extends SQLiteProgram * <li>make sure the database is open</li> * <li>get a database connection from the connection pool,if possible</li> * <li>notifies {@link BlockGuard} of read/write</li> - * <li>get lock on the database</li> + * <li>if the SQL statement is an update, start transaction if not already in one. + * otherwise, get lock on the database</li> * <li>acquire reference on this object</li> * <li>and then return the current time _before_ the database lock was acquired</li> * </ul> * <p> - * This method removes the duplcate code from the other public + * This method removes the duplicate code from the other public * methods in this class. */ private long acquireAndLock(boolean rwFlag) { + state = 0; // use pooled database connection handles for SELECT SQL statements mDatabase.verifyDbIsOpen(); - SQLiteDatabase db = (getSqlStatementType(mSql) != SELECT_STMT) ? mDatabase + SQLiteDatabase db = (mStatementType != DatabaseUtils.STATEMENT_SELECT) ? mDatabase : mDatabase.getDbConnection(mSql); // use the database connection obtained above mOrigDb = mDatabase; @@ -172,20 +180,57 @@ public class SQLiteStatement extends SQLiteProgram } else { BlockGuard.getThreadPolicy().onReadFromDisk(); } - long startTime = SystemClock.uptimeMillis(); - mDatabase.lock(); + + /* + * Special case handling of SQLiteDatabase.execSQL("BEGIN transaction"). + * we know it is execSQL("BEGIN transaction") from the caller IF there is no lock held. + * beginTransaction() methods in SQLiteDatabase call lockForced() before + * calling execSQL("BEGIN transaction"). + */ + if (mStatementType == DatabaseUtils.STATEMENT_BEGIN) { + if (!mDatabase.isDbLockedByCurrentThread()) { + // transaction is NOT started by calling beginTransaction() methods in + // SQLiteDatabase + mDatabase.setTransactionUsingExecSqlFlag(); + } + } else if (mStatementType == DatabaseUtils.STATEMENT_UPDATE) { + // got update SQL statement. if there is NO pending transaction, start one + if (!mDatabase.inTransaction()) { + mDatabase.beginTransactionNonExclusive(); + state = TRANS_STARTED; + } + } + // do I have database lock? if not, grab it. + if (!mDatabase.isDbLockedByCurrentThread()) { + mDatabase.lock(); + state = LOCK_ACQUIRED; + } + acquireReference(); + long startTime = SystemClock.uptimeMillis(); mDatabase.closePendingStatements(); compileAndbindAllArgs(); return startTime; } /** - * this method releases locks and references acquired in {@link #acquireAndLock(boolean)}. + * this method releases locks and references acquired in {@link #acquireAndLock(boolean)} */ private void releaseAndUnlock() { releaseReference(); - mDatabase.unlock(); + if (state == TRANS_STARTED) { + try { + mDatabase.setTransactionSuccessful(); + } finally { + mDatabase.endTransaction(); + } + } else if (state == LOCK_ACQUIRED) { + mDatabase.unlock(); + } + if (mStatementType == DatabaseUtils.STATEMENT_COMMIT || + mStatementType == DatabaseUtils.STATEMENT_ABORT) { + mDatabase.resetTransactionUsingExecSqlFlag(); + } clearBindings(); // release the compiled sql statement so that the caller's SQLiteStatement no longer // has a hard reference to a database object that may get deallocated at any point. |