@file:JvmName("SupportSQLiteDatabaseKt")
/*
* Copyright (C) 2016 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 androidx.sqlite.db
import android.annotation.SuppressLint
import android.content.ContentValues
import android.database.Cursor
import android.database.SQLException
import android.database.sqlite.SQLiteTransactionListener
import android.os.Build
import android.os.CancellationSignal
import android.util.Pair
import androidx.annotation.RequiresApi
import java.io.Closeable
import java.util.Locale
/**
* A database abstraction which removes the framework dependency and allows swapping underlying
* sql versions. It mimics the behavior of [android.database.sqlite.SQLiteDatabase]
*/
interface SupportSQLiteDatabase : Closeable {
/**
* Compiles the given SQL statement.
*
* @param sql The sql query.
* @return Compiled statement.
*/
fun compileStatement(sql: String): SupportSQLiteStatement
/**
* Begins a transaction in EXCLUSIVE mode.
*
* 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.
*
* Here is the standard idiom for transactions:
*
* ```
* db.beginTransaction()
* try {
* ...
* db.setTransactionSuccessful()
* } finally {
* db.endTransaction()
* }
* ```
*/
fun beginTransaction()
/**
* Begins a transaction in IMMEDIATE mode. 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.
*
* Here is the standard idiom for transactions:
*
* ```
* db.beginTransactionNonExclusive()
* try {
* ...
* db.setTransactionSuccessful()
* } finally {
* db.endTransaction()
* }
* ```
*/
fun beginTransactionNonExclusive()
/**
* Begins a transaction in EXCLUSIVE mode.
*
* 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.
*
* Here is the standard idiom for transactions:
*
* ```
* db.beginTransactionWithListener(listener)
* try {
* ...
* db.setTransactionSuccessful()
* } finally {
* db.endTransaction()
* }
* ```
*
* @param transactionListener listener that should be notified when the transaction begins,
* commits, or is rolled back, either explicitly or by a call to
* [yieldIfContendedSafely].
*/
fun beginTransactionWithListener(transactionListener: SQLiteTransactionListener)
/**
* Begins a transaction in IMMEDIATE mode. 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.
*
* Here is the standard idiom for transactions:
*
* ```
* db.beginTransactionWithListenerNonExclusive(listener)
* try {
* ...
* db.setTransactionSuccessful()
* } finally {
* db.endTransaction()
* }
* ```
*
* @param transactionListener listener that should be notified when the
* transaction begins, commits, or is rolled back, either
* explicitly or by a call to [yieldIfContendedSafely].
*/
fun beginTransactionWithListenerNonExclusive(
transactionListener: SQLiteTransactionListener
)
/**
* End a transaction. See beginTransaction for notes about how to use this and when transactions
* are committed and rolled back.
*/
fun endTransaction()
/**
* 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.
*/
fun setTransactionSuccessful()
/**
* Returns true if the current thread has a transaction pending.
*
* @return True if the current thread is in a transaction.
*/
fun inTransaction(): Boolean
/**
* True if the current thread is holding an active connection to the database.
*
* The name of this method comes from a time when having an active connection
* to the database meant that the thread was holding an actual lock on the
* database. Nowadays, there is no longer a true "database lock" although threads
* may block if they cannot acquire a database connection to perform a
* particular operation.
*/
val isDbLockedByCurrentThread: Boolean
/**
* 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
*/
fun yieldIfContendedSafely(): Boolean
/**
* 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 sleepAfterYieldDelayMillis 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
*/
fun yieldIfContendedSafely(sleepAfterYieldDelayMillis: Long): Boolean
/**
* Is true if [execPerConnectionSQL] is supported by the implementation.
*/
@get:Suppress("AcronymName") // To keep consistency with framework method name.
val isExecPerConnectionSQLSupported: Boolean
get() = false
/**
* Execute the given SQL statement on all connections to this database.
*
* This statement will be immediately executed on all existing connections,
* and will be automatically executed on all future connections.
*
* Some example usages are changes like `PRAGMA trusted_schema=OFF` or
* functions like `SELECT icu_load_collation()`. If you execute these
* statements using [execSQL] then they will only apply to a single
* database connection; using this method will ensure that they are
* uniformly applied to all current and future connections.
*
* An implementation of [SupportSQLiteDatabase] might not support this operation. Use
* [isExecPerConnectionSQLSupported] to check if this operation is supported before
* calling this method.
*
* @param sql The SQL statement to be executed. Multiple statements
* separated by semicolons are not supported.
* @param bindArgs The arguments that should be bound to the SQL statement.
* @throws UnsupportedOperationException if this operation is not supported. To check if it
* supported use [isExecPerConnectionSQLSupported]
*/
@Suppress("AcronymName") // To keep consistency with framework method name.
fun execPerConnectionSQL(
sql: String,
@SuppressLint("ArrayReturn") bindArgs: Array<out Any?>?
) {
throw UnsupportedOperationException()
}
/**
* The database version.
*/
var version: Int
/**
* The maximum size the database may grow to.
*/
val maximumSize: Long
/**
* 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
*/
fun setMaximumSize(numBytes: Long): Long
/**
* The current database page size, in bytes.
*
* 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.
*/
var pageSize: Long
/**
* Runs the given query on the database. If you would like to have typed bind arguments,
* use [query].
*
* @param query The SQL query that includes the query and can bind into a given compiled
* program.
* @return A [Cursor] object, which is positioned before the first entry. Note that
* [Cursor]s are not synchronized, see the documentation for more details.
*/
fun query(query: String): Cursor
/**
* Runs the given query on the database. If you would like to have bind arguments,
* use [query].
*
* @param query The SQL query that includes the query and can bind into a given compiled
* program.
* @param bindArgs The query arguments to bind.
* @return A [Cursor] object, which is positioned before the first entry. Note that
* [Cursor]s are not synchronized, see the documentation for more details.
*/
fun query(query: String, bindArgs: Array<out Any?>): Cursor
/**
* Runs the given query on the database.
*
* This class allows using type safe sql program bindings while running queries.
*
* @param query The [SimpleSQLiteQuery] query that includes the query and can bind into a
* given compiled program.
* @return A [Cursor] object, which is positioned before the first entry. Note that
* [Cursor]s are not synchronized, see the documentation for more details.
*/
fun query(query: SupportSQLiteQuery): Cursor
/**
* Runs the given query on the database.
*
* This class allows using type safe sql program bindings while running queries.
*
* @param query The SQL query that includes the query and can bind into a given compiled
* program.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* If the operation is canceled, then [androidx.core.os.OperationCanceledException] will be
* thrown when the query is executed.
* @return A [Cursor] object, which is positioned before the first entry. Note that
* [Cursor]s are not synchronized, see the documentation for more details.
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
fun query(
query: SupportSQLiteQuery,
cancellationSignal: CancellationSignal?
): Cursor
/**
* Convenience method for inserting a row into the database.
*
* @param table the table to insert the row into
* @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
* @param conflictAlgorithm for insert conflict resolver. One of
* [android.database.sqlite.SQLiteDatabase.CONFLICT_NONE],
* [android.database.sqlite.SQLiteDatabase.CONFLICT_ROLLBACK],
* [android.database.sqlite.SQLiteDatabase.CONFLICT_ABORT],
* [android.database.sqlite.SQLiteDatabase.CONFLICT_FAIL],
* [android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE],
* [android.database.sqlite.SQLiteDatabase.CONFLICT_REPLACE].
* @return the row ID of the newly inserted row, or -1 if an error occurred
* @throws SQLException If the insert fails
*/
@Throws(SQLException::class)
fun insert(table: String, conflictAlgorithm: Int, values: ContentValues): Long
/**
* 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.
* @param whereArgs You may include ?s in the where clause, which
* will be replaced by the values from whereArgs. The values
* will be bound as Strings.
* @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.
*/
fun delete(table: String, whereClause: String?, whereArgs: Array<out Any?>?): Int
/**
* Convenience method for updating rows in the database.
*
* @param table the table to update in
* @param conflictAlgorithm for update conflict resolver. One of
* [android.database.sqlite.SQLiteDatabase.CONFLICT_NONE],
* [android.database.sqlite.SQLiteDatabase.CONFLICT_ROLLBACK],
* [android.database.sqlite.SQLiteDatabase.CONFLICT_ABORT],
* [android.database.sqlite.SQLiteDatabase.CONFLICT_FAIL],
* [android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE],
* [android.database.sqlite.SQLiteDatabase.CONFLICT_REPLACE].
* @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 whereArgs You may include ?s in the where clause, which
* will be replaced by the values from whereArgs. The values
* will be bound as Strings.
* @return the number of rows affected
*/
fun update(
table: String,
conflictAlgorithm: Int,
values: ContentValues,
whereClause: String?,
whereArgs: Array<out Any?>?
): Int
/**
* Execute a single SQL statement that does not return any data.
*
* When using [enableWriteAheadLogging], journal_mode is
* automatically managed by this class. So, do not set journal_mode
* using "PRAGMA journal_mode" statement if your app is using
* [enableWriteAheadLogging]
*
* @param sql the SQL statement to be executed. Multiple statements separated by semicolons are
* not supported.
* @throws SQLException if the SQL string is invalid
*/
@Throws(SQLException::class)
fun execSQL(sql: String)
/**
* Execute a single SQL statement that does not return any data.
*
* When using [enableWriteAheadLogging], journal_mode is
* automatically managed by this class. So, do not set journal_mode
* using "PRAGMA journal_mode" statement if your app is using
* [enableWriteAheadLogging]
*
* @param sql the SQL statement to be executed. Multiple statements separated by semicolons
* are not supported.
* @param bindArgs only byte[], String, Long and Double are supported in selectionArgs.
* @throws SQLException if the SQL string is invalid
*/
@Throws(SQLException::class)
fun execSQL(sql: String, bindArgs: Array<out Any?>)
/**
* Is true if the database is opened as read only.
*/
val isReadOnly: Boolean
/**
* Is true if the database is currently open.
*/
val isOpen: Boolean
/**
* Returns true if the new version code is greater than the current database version.
*
* @param newVersion The new version code.
* @return True if the new version code is greater than the current database version.
*/
fun needUpgrade(newVersion: Int): Boolean
/**
* The path to the database file.
*/
val path: String?
/**
* Sets the locale for this database. Does nothing if this database has
* the [android.database.sqlite.SQLiteDatabase.NO_LOCALIZED_COLLATORS] flag set or was opened
* read only.
*
* @param locale The new locale.
* @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.
*/
fun setLocale(locale: Locale)
/**
* Sets the maximum size of the prepared-statement cache for this database.
* (size of the cache = number of compiled-sql-statements stored in the cache).
*
* Maximum cache size can ONLY be increased from its current size (default = 10).
* If this method is called with smaller size than the current maximum value,
* then IllegalStateException is thrown.
*
* This method is thread-safe.
*
* @param cacheSize the size of the cache. can be (0 to
* [android.database.sqlite.SQLiteDatabase.MAX_SQL_CACHE_SIZE])
* @throws IllegalStateException if input cacheSize is over the max.
* [android.database.sqlite.SQLiteDatabase.MAX_SQL_CACHE_SIZE].
*/
fun setMaxSqlCacheSize(cacheSize: Int)
/**
* Sets whether foreign key constraints are enabled for the database.
*
* By default, foreign key constraints are not enforced by the database.
* This method allows an application to enable foreign key constraints.
* It must be called each time the database is opened to ensure that foreign
* key constraints are enabled for the session.
*
* A good time to call this method is right after calling `#openOrCreateDatabase`
* or in the [SupportSQLiteOpenHelper.Callback.onConfigure] callback.
*
* When foreign key constraints are disabled, the database does not check whether
* changes to the database will violate foreign key constraints. Likewise, when
* foreign key constraints are disabled, the database will not execute cascade
* delete or update triggers. As a result, it is possible for the database
* state to become inconsistent. To perform a database integrity check,
* call [isDatabaseIntegrityOk].
*
* This method must not be called while a transaction is in progress.
*
* See also [SQLite Foreign Key Constraints](http://sqlite.org/foreignkeys.html)
* for more details about foreign key constraint support.
*
* @param enabled True to enable foreign key constraints, false to disable them.
* @throws IllegalStateException if the are transactions is in progress
* when this method is called.
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
fun setForeignKeyConstraintsEnabled(enabled: Boolean)
/**
* This method enables parallel execution of queries from multiple threads on the
* same database. It does this by opening multiple connections to the database
* and using a different database connection for each query. The database
* journal mode is also changed to enable writes to proceed concurrently with reads.
*
* When write-ahead logging is not enabled (the default), it is not possible for
* reads and writes to occur on the database at the same time. Before modifying the
* database, the writer implicitly acquires an exclusive lock on the database which
* prevents readers from accessing the database until the write is completed.
*
* In contrast, when write-ahead logging is enabled (by calling this method), write
* operations occur in a separate log file which allows reads to proceed concurrently.
* While a write is in progress, readers on other threads will perceive the state
* of the database as it was before the write began. When the write completes, readers
* on other threads will then perceive the new state of the database.
*
* It is a good idea to enable write-ahead logging whenever a database will be
* concurrently accessed and modified by multiple threads at the same time.
* However, write-ahead logging uses significantly more memory than ordinary
* journaling because there are multiple connections to the same database.
* So if a database will only be used by a single thread, or if optimizing
* concurrency is not very important, then write-ahead logging should be disabled.
*
* After calling this method, execution of queries in parallel is enabled as long as
* the database remains open. To disable execution of queries in parallel, either
* call [disableWriteAheadLogging] or close the database and reopen it.
*
* The maximum number of connections used to execute queries in parallel is
* dependent upon the device memory and possibly other properties.
*
* If a query is part of a transaction, then it is executed on the same database handle the
* transaction was begun.
*
* Writers should use [beginTransactionNonExclusive] or
* [beginTransactionWithListenerNonExclusive]
* to start a transaction. Non-exclusive mode allows database file to be in readable
* by other threads executing queries.
*
* If the database has any attached databases, then execution of queries in parallel is NOT
* possible. Likewise, write-ahead logging is not supported for read-only databases
* or memory databases. In such cases, `enableWriteAheadLogging` returns false.
*
* The best way to enable write-ahead logging is to pass the
* [android.database.sqlite.SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING] flag to
* [android.database.sqlite.SQLiteDatabase.openDatabase]. This is more efficient than calling
*
* SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
* SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING,
* myDatabaseErrorHandler)
* db.enableWriteAheadLogging()
*
* Another way to enable write-ahead logging is to call `enableWriteAheadLogging`
* after opening the database.
*
* SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
* SQLiteDatabase.CREATE_IF_NECESSARY, myDatabaseErrorHandler)
* db.enableWriteAheadLogging()
*
* See also [SQLite Write-Ahead Logging](http://sqlite.org/wal.html) for
* more details about how write-ahead logging works.
*
* @return True if write-ahead logging is enabled.
* @throws IllegalStateException if there are transactions in progress at the
* time this method is called. WAL mode can only be changed when
* there are no
* transactions in progress.
*/
fun enableWriteAheadLogging(): Boolean
/**
* This method disables the features enabled by [enableWriteAheadLogging].
*
* @throws IllegalStateException if there are transactions in progress at the
* time this method is called. WAL mode can only be changed when there are no transactions in
* progress.
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
fun disableWriteAheadLogging()
/**
* Is true if write-ahead logging has been enabled for this database.
*/
@get:RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
val isWriteAheadLoggingEnabled: Boolean
/**
* The list of full path names of all attached databases including the main database
* by executing 'pragma database_list' on the database.
*/
@get:Suppress("NullableCollection")
val attachedDbs: List<Pair<String, String>>?
/**
* Is true if the given database (and all its attached databases) pass integrity_check,
* false otherwise.
*/
val isDatabaseIntegrityOk: Boolean
}