3939import java .util .HashMap ;
4040import java .util .HashSet ;
4141import java .util .Iterator ;
42+ import java .util .List ;
4243import java .util .Locale ;
4344import java .util .Map ;
4445import java .util .Set ;
@@ -76,6 +77,12 @@ public class SQLiteDatabase extends SQLiteClosable {
7677 private static final int EVENT_DB_CORRUPT = 75004 ;
7778 private static final String KEY_ENCODING = "UTF-8" ;
7879
80+ private enum SQLiteDatabaseTransactionType {
81+ Deferred ,
82+ Immediate ,
83+ Exclusive ,
84+ }
85+
7986 /**
8087 * The version number of the SQLCipher for Android Java client library.
8188 */
@@ -635,6 +642,75 @@ private void checkLockHoldTime() {
635642 }
636643 }
637644
645+ /**
646+ * Performs a PRAGMA integrity_check; command against the database.
647+ * @return true if the integrity check is ok, otherwise false
648+ */
649+ public boolean isDatabaseIntegrityOk () {
650+ Pair <Boolean , String > result = getResultFromPragma ("PRAGMA integrity_check;" );
651+ return result .first ? result .second .equals ("ok" ) : result .first ;
652+ }
653+
654+ /**
655+ * Returns a list of attached databases including the main database
656+ * by executing PRAGMA database_list
657+ * @return a list of pairs of database name and filename
658+ */
659+ public List <Pair <String , String >> getAttachedDbs () {
660+ return getAttachedDbs (this );
661+ }
662+
663+ /**
664+ * Sets the journal mode of the database to WAL
665+ * @return true if successful, false otherwise
666+ */
667+ public boolean enableWriteAheadLogging () {
668+ if (inTransaction ()) {
669+ String message = "Write Ahead Logging cannot be enabled while in a transaction" ;
670+ throw new IllegalStateException (message );
671+ }
672+ List <Pair <String , String >> attachedDbs = getAttachedDbs (this );
673+ if (attachedDbs != null && attachedDbs .size () > 1 ) return false ;
674+ if (isReadOnly () || getPath ().equals (MEMORY )) return false ;
675+ String command = "PRAGMA journal_mode = WAL;" ;
676+ rawExecSQL (command );
677+ return true ;
678+ }
679+
680+ /**
681+ * Sets the journal mode of the database to DELETE (the default mode)
682+ */
683+ public void disableWriteAheadLogging () {
684+ if (inTransaction ()) {
685+ String message = "Write Ahead Logging cannot be disabled while in a transaction" ;
686+ throw new IllegalStateException (message );
687+ }
688+ String command = "PRAGMA journal_mode = DELETE;" ;
689+ rawExecSQL (command );
690+ }
691+
692+ /**
693+ * Sets the journal mode of the database to DELETE (the default mode)
694+ */
695+ public boolean isWriteAheadLoggingEnabled () {
696+ Pair <Boolean , String > result = getResultFromPragma ("PRAGMA journal_mode;" );
697+ return result .first ? result .second .equals ("wal" ) : result .first ;
698+ }
699+
700+ /**
701+ * Enables or disables foreign key constraints
702+ * @param enable used to determine whether or not foreign key constraints are on
703+ */
704+ public void setForeignKeyConstraintsEnabled (boolean enable ) {
705+ if (inTransaction ()) {
706+ String message = "Foreign key constraints may not be changed while in a transaction" ;
707+ throw new IllegalStateException (message );
708+ }
709+ String command = String .format ("PRAGMA foreign_keys = %s;" ,
710+ enable ? "ON" : "OFF" );
711+ execSQL (command );
712+ }
713+
638714 /**
639715 * Begins a transaction. Transactions can be nested. When the outer transaction is ended all of
640716 * the work done in that transaction and all of the nested transactions will be committed or
@@ -660,10 +736,12 @@ public void beginTransaction() {
660736 }
661737
662738 /**
663- * Begins a transaction. Transactions can be nested. When the outer transaction is ended all of
664- * the work done in that transaction and all of the nested transactions will be committed or
665- * rolled back. The changes will be rolled back if any transaction is ended without being
666- * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed.
739+ * Begins a transaction in Exlcusive mode. Transactions can be nested. When
740+ * the outer transaction is ended all of the work done in that transaction
741+ * and all of the nested transactions will be committed or rolled back. The
742+ * changes will be rolled back if any transaction is ended without being
743+ * marked as clean (by calling setTransactionSuccessful). Otherwise they
744+ * will be committed.
667745 *
668746 * <p>Here is the standard idiom for transactions:
669747 *
@@ -683,47 +761,25 @@ public void beginTransaction() {
683761 * @throws IllegalStateException if the database is not open
684762 */
685763 public void beginTransactionWithListener (SQLiteTransactionListener transactionListener ) {
686- lockForced ();
687- if (!isOpen ()) {
688- throw new IllegalStateException ("database not open" );
689- }
690- boolean ok = false ;
691- try {
692- // If this thread already had the lock then get out
693- if (mLock .getHoldCount () > 1 ) {
694- if (mInnerTransactionIsSuccessful ) {
695- String msg = "Cannot call beginTransaction between "
696- + "calling setTransactionSuccessful and endTransaction" ;
697- IllegalStateException e = new IllegalStateException (msg );
698- Log .e (TAG , "beginTransaction() failed" , e );
699- throw e ;
700- }
701- ok = true ;
702- return ;
703- }
764+ beginTransactionWithListenerInternal (transactionListener ,
765+ SQLiteDatabaseTransactionType .Exclusive );
766+ }
704767
705- // This thread didn't already have the lock, so begin a database
706- // transaction now.
707- execSQL ("BEGIN EXCLUSIVE;" );
708- mTransactionListener = transactionListener ;
709- mTransactionIsSuccessful = true ;
710- mInnerTransactionIsSuccessful = false ;
711- if (transactionListener != null ) {
712- try {
713- transactionListener .onBegin ();
714- } catch (RuntimeException e ) {
715- execSQL ("ROLLBACK;" );
716- throw e ;
717- }
718- }
719- ok = true ;
720- } finally {
721- if (!ok ) {
722- // beginTransaction is called before the try block so we must release the lock in
723- // the case of failure.
724- unlockForced ();
725- }
726- }
768+ /**
769+ * Begins a transaction in Immediate mode
770+ */
771+ public void beginTransactionNonExclusive () {
772+ beginTransactionWithListenerInternal (null ,
773+ SQLiteDatabaseTransactionType .Immediate );
774+ }
775+
776+ /**
777+ * Begins a transaction in Immediate mode
778+ * @param transactionListener is the listener used to report transaction events
779+ */
780+ public void beginTransactionWithListenerNonExclusive (SQLiteTransactionListener transactionListener ) {
781+ beginTransactionWithListenerInternal (transactionListener ,
782+ SQLiteDatabaseTransactionType .Immediate );
727783 }
728784
729785 /**
@@ -2729,6 +2785,60 @@ public synchronized void setMaxSqlCacheSize(int cacheSize) {
27292785 mMaxSqlCacheSize = cacheSize ;
27302786 }
27312787
2788+ private void beginTransactionWithListenerInternal (SQLiteTransactionListener transactionListener ,
2789+ SQLiteDatabaseTransactionType transactionType ) {
2790+ lockForced ();
2791+ if (!isOpen ()) {
2792+ throw new IllegalStateException ("database not open" );
2793+ }
2794+ boolean ok = false ;
2795+ try {
2796+ // If this thread already had the lock then get out
2797+ if (mLock .getHoldCount () > 1 ) {
2798+ if (mInnerTransactionIsSuccessful ) {
2799+ String msg = "Cannot call beginTransaction between "
2800+ + "calling setTransactionSuccessful and endTransaction" ;
2801+ IllegalStateException e = new IllegalStateException (msg );
2802+ Log .e (TAG , "beginTransaction() failed" , e );
2803+ throw e ;
2804+ }
2805+ ok = true ;
2806+ return ;
2807+ }
2808+ // This thread didn't already have the lock, so begin a database
2809+ // transaction now.
2810+ if (transactionType == SQLiteDatabaseTransactionType .Exclusive ) {
2811+ execSQL ("BEGIN EXCLUSIVE;" );
2812+ } else if (transactionType == SQLiteDatabaseTransactionType .Immediate ) {
2813+ execSQL ("BEGIN IMMEDIATE;" );
2814+ } else if (transactionType == SQLiteDatabaseTransactionType .Deferred ) {
2815+ execSQL ("BEGIN DEFERRED;" );
2816+ } else {
2817+ String message = String .format ("%s is an unsupported transaction type" ,
2818+ transactionType );
2819+ throw new IllegalArgumentException (message );
2820+ }
2821+ mTransactionListener = transactionListener ;
2822+ mTransactionIsSuccessful = true ;
2823+ mInnerTransactionIsSuccessful = false ;
2824+ if (transactionListener != null ) {
2825+ try {
2826+ transactionListener .onBegin ();
2827+ } catch (RuntimeException e ) {
2828+ execSQL ("ROLLBACK;" );
2829+ throw e ;
2830+ }
2831+ }
2832+ ok = true ;
2833+ } finally {
2834+ if (!ok ) {
2835+ // beginTransaction is called before the try block so we must release the lock in
2836+ // the case of failure.
2837+ unlockForced ();
2838+ }
2839+ }
2840+ }
2841+
27322842 /**
27332843 * this method is used to collect data about ALL open databases in the current process.
27342844 * bugreport is a user of this data.
@@ -2837,6 +2947,16 @@ private byte[] getBytes(char[] data) {
28372947 return result ;
28382948 }
28392949
2950+ private Pair <Boolean , String > getResultFromPragma (String command ) {
2951+ Cursor cursor = rawQuery (command , new Object []{});
2952+ if (cursor == null ) return new Pair (false , "" );
2953+ cursor .moveToFirst ();
2954+ String value = cursor .getString (0 );
2955+ cursor .close ();
2956+ return new Pair (true , value );
2957+ }
2958+
2959+
28402960 /**
28412961 * Sets the root directory to search for the ICU data file
28422962 */
0 commit comments