mirror of https://github.com/M66B/FairEmail.git
parent
bf9e353754
commit
2c4eb99d6a
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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
|
||||
|
||||
/**
|
||||
* A basic implementation of [SupportSQLiteQuery] which receives a query and its args and
|
||||
* binds args based on the passed in Object type.
|
||||
*
|
||||
* @constructor Creates an SQL query with the sql string and the bind arguments.
|
||||
*
|
||||
* @param query The query string, can include bind arguments (.e.g ?).
|
||||
* @param bindArgs The bind argument value that will replace the placeholders in the query.
|
||||
*/
|
||||
class SimpleSQLiteQuery(
|
||||
private val query: String,
|
||||
@Suppress("ArrayReturn") // Due to legacy API
|
||||
private val bindArgs: Array<out Any?>?
|
||||
) : SupportSQLiteQuery {
|
||||
|
||||
/**
|
||||
* Creates an SQL query without any bind arguments.
|
||||
*
|
||||
* @param query The SQL query to execute. Cannot include bind parameters.
|
||||
*/
|
||||
constructor(query: String) : this(query, null)
|
||||
|
||||
override val sql: String
|
||||
get() = this.query
|
||||
|
||||
/**
|
||||
* Creates an SQL query without any bind arguments.
|
||||
*
|
||||
* @param [statement] The SQL query to execute. Cannot include bind parameters.
|
||||
*/
|
||||
override fun bindTo(statement: SupportSQLiteProgram) {
|
||||
bind(statement, bindArgs)
|
||||
}
|
||||
|
||||
override val argCount: Int
|
||||
get() = bindArgs?.size ?: 0
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Binds the given arguments into the given sqlite statement.
|
||||
*
|
||||
* @param [statement] The sqlite statement
|
||||
* @param [bindArgs] The list of bind arguments
|
||||
*/
|
||||
@JvmStatic
|
||||
fun bind(
|
||||
statement: SupportSQLiteProgram,
|
||||
@Suppress("ArrayReturn") // Due to legacy API
|
||||
bindArgs: Array<out Any?>?
|
||||
) {
|
||||
if (bindArgs == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val limit = bindArgs.size
|
||||
for (i in 0 until limit) {
|
||||
val arg = bindArgs[i]
|
||||
bind(statement, i + 1, arg)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bind(statement: SupportSQLiteProgram, index: Int, arg: Any?) {
|
||||
// extracted from android.database.sqlite.SQLiteConnection
|
||||
if (arg == null) {
|
||||
statement.bindNull(index)
|
||||
} else if (arg is ByteArray) {
|
||||
statement.bindBlob(index, arg)
|
||||
} else if (arg is Float) {
|
||||
statement.bindDouble(index, arg.toDouble())
|
||||
} else if (arg is Double) {
|
||||
statement.bindDouble(index, arg)
|
||||
} else if (arg is Long) {
|
||||
statement.bindLong(index, arg)
|
||||
} else if (arg is Int) {
|
||||
statement.bindLong(index, arg.toLong())
|
||||
} else if (arg is Short) {
|
||||
statement.bindLong(index, arg.toLong())
|
||||
} else if (arg is Byte) {
|
||||
statement.bindLong(index, arg.toLong())
|
||||
} else if (arg is String) {
|
||||
statement.bindString(index, arg)
|
||||
} else if (arg is Boolean) {
|
||||
statement.bindLong(index, if (arg) 1 else 0)
|
||||
} else {
|
||||
throw IllegalArgumentException(
|
||||
"Cannot bind $arg at index $index Supported types: Null, ByteArray, " +
|
||||
"Float, Double, Long, Int, Short, Byte, String"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright 2021 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.app.ActivityManager
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteDatabase.CursorFactory
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.CancellationSignal
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.RestrictTo
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Helper for accessing features in [SupportSQLiteOpenHelper].
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
class SupportSQLiteCompat private constructor() {
|
||||
/**
|
||||
* Class for accessing functions that require SDK version 16 and higher.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@RequiresApi(16)
|
||||
object Api16Impl {
|
||||
/**
|
||||
* Cancels the operation and signals the cancellation listener. If the operation has not yet
|
||||
* started, then it will be canceled as soon as it does.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@JvmStatic
|
||||
fun cancel(cancellationSignal: CancellationSignal) {
|
||||
cancellationSignal.cancel()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cancellation signal, initially not canceled.
|
||||
*
|
||||
* @return a new cancellation signal
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@JvmStatic
|
||||
fun createCancellationSignal(): CancellationSignal {
|
||||
return CancellationSignal()
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a database including its journal file and other auxiliary files
|
||||
* that may have been created by the database engine.
|
||||
*
|
||||
* @param file The database file path.
|
||||
* @return True if the database was successfully deleted.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@JvmStatic
|
||||
fun deleteDatabase(file: File): Boolean {
|
||||
return SQLiteDatabase.deleteDatabase(file)
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the provided SQL and returns a 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.
|
||||
* @param editTable the name of the first table, which is editable
|
||||
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
|
||||
* If the operation is canceled, then [OperationCanceledException] will be thrown
|
||||
* when the query is executed.
|
||||
* @param cursorFactory the cursor factory to use, or null for the default factory
|
||||
* @return A [Cursor] object, which is positioned before the first entry. Note that
|
||||
* [Cursor]s are not synchronized, see the documentation for more details.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@JvmStatic
|
||||
fun rawQueryWithFactory(
|
||||
sQLiteDatabase: SQLiteDatabase,
|
||||
sql: String,
|
||||
selectionArgs: Array<out String?>,
|
||||
editTable: String?,
|
||||
cancellationSignal: CancellationSignal,
|
||||
cursorFactory: CursorFactory
|
||||
): Cursor {
|
||||
return sQLiteDatabase.rawQueryWithFactory(
|
||||
cursorFactory, sql, selectionArgs, editTable,
|
||||
cancellationSignal
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether foreign key constraints are enabled for the database.
|
||||
*
|
||||
* @param enable True to enable foreign key constraints, false to disable them.
|
||||
*
|
||||
* @throws [IllegalStateException] if the are transactions is in progress
|
||||
* when this method is called.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@JvmStatic
|
||||
fun setForeignKeyConstraintsEnabled(
|
||||
sQLiteDatabase: SQLiteDatabase,
|
||||
enable: Boolean
|
||||
) {
|
||||
sQLiteDatabase.setForeignKeyConstraintsEnabled(enable)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method disables the features enabled by
|
||||
* [SQLiteDatabase.enableWriteAheadLogging].
|
||||
*
|
||||
* @throws - 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.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@JvmStatic
|
||||
fun disableWriteAheadLogging(sQLiteDatabase: SQLiteDatabase) {
|
||||
sQLiteDatabase.disableWriteAheadLogging()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if [SQLiteDatabase.enableWriteAheadLogging] logging has been enabled for
|
||||
* this database.
|
||||
*
|
||||
* For details, see [SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING].
|
||||
*
|
||||
* @return True if write-ahead logging has been enabled for this database.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@JvmStatic
|
||||
fun isWriteAheadLoggingEnabled(sQLiteDatabase: SQLiteDatabase): Boolean {
|
||||
return sQLiteDatabase.isWriteAheadLoggingEnabled
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets [SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING] flag if `enabled` is `true`, unsets
|
||||
* otherwise.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@JvmStatic
|
||||
fun setWriteAheadLoggingEnabled(
|
||||
sQLiteOpenHelper: SQLiteOpenHelper,
|
||||
enabled: Boolean
|
||||
) {
|
||||
sQLiteOpenHelper.setWriteAheadLoggingEnabled(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for accessing functions that require SDK version 19 and higher.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@RequiresApi(19)
|
||||
object Api19Impl {
|
||||
/**
|
||||
* Return the URI at which notifications of changes in this Cursor's data
|
||||
* will be delivered.
|
||||
*
|
||||
* @return Returns a URI that can be used with [ContentResolver.registerContentObserver] to
|
||||
* find out about changes to this Cursor's data. May be null if no notification URI has been
|
||||
* set.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@JvmStatic
|
||||
fun getNotificationUri(cursor: Cursor): Uri {
|
||||
return cursor.notificationUri
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this is a low-RAM device. Exactly whether a device is low-RAM
|
||||
* is ultimately up to the device configuration, but currently it generally means
|
||||
* something with 1GB or less of RAM. This is mostly intended to be used by apps
|
||||
* to determine whether they should turn off certain features that require more RAM.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@JvmStatic
|
||||
fun isLowRamDevice(activityManager: ActivityManager): Boolean {
|
||||
return activityManager.isLowRamDevice
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for accessing functions that require SDK version 21 and higher.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@RequiresApi(21)
|
||||
object Api21Impl {
|
||||
/**
|
||||
* Returns the absolute path to the directory on the filesystem.
|
||||
*
|
||||
* @return The path of the directory holding application files that will not be
|
||||
* automatically backed up to remote storage.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@JvmStatic
|
||||
fun getNoBackupFilesDir(context: Context): File {
|
||||
return context.noBackupFilesDir
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for accessing functions that require SDK version 23 and higher.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@RequiresApi(23)
|
||||
object Api23Impl {
|
||||
/**
|
||||
* Sets a [Bundle] that will be returned by [Cursor.getExtras].
|
||||
*
|
||||
* @param extras [Bundle] to set, or null to set an empty bundle.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@JvmStatic
|
||||
fun setExtras(cursor: Cursor, extras: Bundle) {
|
||||
cursor.extras = extras
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for accessing functions that require SDK version 29 and higher.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@RequiresApi(29)
|
||||
object Api29Impl {
|
||||
/**
|
||||
* Similar to [Cursor.setNotificationUri], except this version
|
||||
* allows to watch multiple content URIs for changes.
|
||||
*
|
||||
* @param cr The content resolver from the caller's context. The listener attached to
|
||||
* this resolver will be notified.
|
||||
* @param uris The content URIs to watch.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@JvmStatic
|
||||
fun setNotificationUris(
|
||||
cursor: Cursor,
|
||||
cr: ContentResolver,
|
||||
uris: List<Uri?>
|
||||
) {
|
||||
cursor.setNotificationUris(cr, uris)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the URIs at which notifications of changes in this Cursor's data
|
||||
* will be delivered, as previously set by [setNotificationUris].
|
||||
*
|
||||
* @return Returns URIs that can be used with [ContentResolver.registerContentObserver]
|
||||
* to find out about changes to this Cursor's data. May be null if no notification URI has
|
||||
* been set.
|
||||
*
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
@JvmStatic
|
||||
fun getNotificationUris(cursor: Cursor): List<Uri> {
|
||||
return cursor.notificationUris!!
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,598 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
@ -0,0 +1,425 @@
|
||||
/*
|
||||
* 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.content.Context
|
||||
import android.database.sqlite.SQLiteException
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.util.Pair
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.sqlite.db.SupportSQLiteCompat.Api16Impl.deleteDatabase
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper.Callback
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper.Factory
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* An interface to map the behavior of [android.database.sqlite.SQLiteOpenHelper].
|
||||
* Note that since that class requires overriding certain methods, support implementation
|
||||
* uses [Factory.create] to create this and [Callback] to implement
|
||||
* the methods that should be overridden.
|
||||
*/
|
||||
interface SupportSQLiteOpenHelper : Closeable {
|
||||
/**
|
||||
* Return the name of the SQLite database being opened, as given to
|
||||
* the constructor. `null` indicates an in-memory database.
|
||||
*/
|
||||
val databaseName: String?
|
||||
|
||||
/**
|
||||
* Enables or disables the use of write-ahead logging for the database.
|
||||
*
|
||||
* See [SupportSQLiteDatabase.enableWriteAheadLogging] for details.
|
||||
*
|
||||
* Write-ahead logging cannot be used with read-only databases so the value of
|
||||
* this flag is ignored if the database is opened read-only.
|
||||
*
|
||||
* @param enabled True if write-ahead logging should be enabled, false if it
|
||||
* should be disabled.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||
fun setWriteAheadLoggingEnabled(enabled: Boolean)
|
||||
|
||||
/**
|
||||
* Create and/or open a database that will be used for reading and writing.
|
||||
* The first time this is called, the database will be opened and
|
||||
* [Callback.onCreate], [Callback.onUpgrade] and/or [Callback.onOpen] will be
|
||||
* called.
|
||||
*
|
||||
* Once opened successfully, the database is cached, so you can
|
||||
* call this method every time you need to write to the database.
|
||||
* (Make sure to call [close] when you no longer need the database.)
|
||||
* Errors such as bad permissions or a full disk may cause this method
|
||||
* to fail, but future attempts may succeed if the problem is fixed.
|
||||
*
|
||||
* Database upgrade may take a long time, you
|
||||
* should not call this method from the application main thread, including
|
||||
* from [ContentProvider.onCreate()].
|
||||
*
|
||||
* @return a read/write database object valid until [close] is called
|
||||
* @throws SQLiteException if the database cannot be opened for writing
|
||||
*/
|
||||
val writableDatabase: SupportSQLiteDatabase
|
||||
|
||||
/**
|
||||
* Create and/or open a database. This will be the same object returned by
|
||||
* [writableDatabase] unless some problem, such as a full disk,
|
||||
* requires the database to be opened read-only. In that case, a read-only
|
||||
* database object will be returned. If the problem is fixed, a future call
|
||||
* to [writableDatabase] may succeed, in which case the read-only
|
||||
* database object will be closed and the read/write object will be returned
|
||||
* in the future.
|
||||
*
|
||||
* Like [writableDatabase], this method may
|
||||
* take a long time to return, so you should not call it from the
|
||||
* application main thread, including from
|
||||
* [ContentProvider.onCreate()].
|
||||
*
|
||||
* @return a database object valid until [writableDatabase]
|
||||
* or [close] is called.
|
||||
* @throws SQLiteException if the database cannot be opened
|
||||
*/
|
||||
val readableDatabase: SupportSQLiteDatabase
|
||||
|
||||
/**
|
||||
* Close any open database object.
|
||||
*/
|
||||
override fun close()
|
||||
|
||||
/**
|
||||
* Creates a new Callback to get database lifecycle events.
|
||||
*
|
||||
* Handles various lifecycle events for the SQLite connection, similar to
|
||||
* [room-runtime.SQLiteOpenHelper].
|
||||
*/
|
||||
abstract class Callback(
|
||||
/**
|
||||
* Version number of the database (starting at 1); if the database is older,
|
||||
* [Callback.onUpgrade] will be used to upgrade the database; if the database is newer,
|
||||
* [Callback.onDowngrade] will be used to downgrade the database.
|
||||
*/
|
||||
@JvmField
|
||||
val version: Int
|
||||
) {
|
||||
/**
|
||||
* Called when the database connection is being configured, to enable features such as
|
||||
* write-ahead logging or foreign key support.
|
||||
*
|
||||
* This method is called before [onCreate], [onUpgrade], [onDowngrade],
|
||||
* or [onOpen] are called. It should not modify the database except to configure the
|
||||
* database connection as required.
|
||||
*
|
||||
* This method should only call methods that configure the parameters of the database
|
||||
* connection, such as [SupportSQLiteDatabase.enableWriteAheadLogging]
|
||||
* [SupportSQLiteDatabase.setForeignKeyConstraintsEnabled],
|
||||
* [SupportSQLiteDatabase.setLocale],
|
||||
* [SupportSQLiteDatabase.setMaximumSize], or executing PRAGMA statements.
|
||||
*
|
||||
* @param db The database.
|
||||
*/
|
||||
open fun onConfigure(db: SupportSQLiteDatabase) {}
|
||||
|
||||
/**
|
||||
* Called when the database is created for the first time. This is where the
|
||||
* creation of tables and the initial population of the tables should happen.
|
||||
*
|
||||
* @param db The database.
|
||||
*/
|
||||
abstract fun onCreate(db: SupportSQLiteDatabase)
|
||||
|
||||
/**
|
||||
* Called when the database needs to be upgraded. The implementation
|
||||
* should use this method to drop tables, add tables, or do anything else it
|
||||
* needs to upgrade to the new schema version.
|
||||
*
|
||||
* The SQLite ALTER TABLE documentation can be found
|
||||
* [here](http://sqlite.org/lang_altertable.html). If you add new columns
|
||||
* you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
|
||||
* you can use ALTER TABLE to rename the old table, then create the new table and then
|
||||
* populate the new table with the contents of the old table.
|
||||
*
|
||||
* This method executes within a transaction. If an exception is thrown, all changes
|
||||
* will automatically be rolled back.
|
||||
*
|
||||
* @param db The database.
|
||||
* @param oldVersion The old database version.
|
||||
* @param newVersion The new database version.
|
||||
*/
|
||||
abstract fun onUpgrade(
|
||||
db: SupportSQLiteDatabase,
|
||||
oldVersion: Int,
|
||||
newVersion: Int
|
||||
)
|
||||
|
||||
/**
|
||||
* Called when the database needs to be downgraded. This is strictly similar to
|
||||
* [onUpgrade] method, but is called whenever current version is newer than requested
|
||||
* one.
|
||||
* However, this method is not abstract, so it is not mandatory for a customer to
|
||||
* implement it. If not overridden, default implementation will reject downgrade and
|
||||
* throws SQLiteException
|
||||
*
|
||||
* This method executes within a transaction. If an exception is thrown, all changes
|
||||
* will automatically be rolled back.
|
||||
*
|
||||
* @param db The database.
|
||||
* @param oldVersion The old database version.
|
||||
* @param newVersion The new database version.
|
||||
*/
|
||||
open fun onDowngrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
throw SQLiteException(
|
||||
"Can't downgrade database from version $oldVersion to $newVersion"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the database has been opened. The implementation
|
||||
* should check [SupportSQLiteDatabase.isReadOnly] before updating the
|
||||
* database.
|
||||
*
|
||||
* This method is called after the database connection has been configured
|
||||
* and after the database schema has been created, upgraded or downgraded as necessary.
|
||||
* If the database connection must be configured in some way before the schema
|
||||
* is created, upgraded, or downgraded, do it in [onConfigure] instead.
|
||||
*
|
||||
* @param db The database.
|
||||
*/
|
||||
open fun onOpen(db: SupportSQLiteDatabase) {}
|
||||
|
||||
/**
|
||||
* The method invoked when database corruption is detected. Default implementation will
|
||||
* delete the database file.
|
||||
*
|
||||
* @param db the [SupportSQLiteDatabase] object representing the database on which
|
||||
* corruption is detected.
|
||||
*/
|
||||
open fun onCorruption(db: SupportSQLiteDatabase) {
|
||||
// the following implementation is taken from {@link DefaultDatabaseErrorHandler}.
|
||||
Log.e(TAG, "Corruption reported by sqlite on database: $db.path")
|
||||
// is the corruption detected even before database could be 'opened'?
|
||||
if (!db.isOpen) {
|
||||
// database files are not even openable. delete this database file.
|
||||
// NOTE if the database has attached databases, then any of them could be corrupt.
|
||||
// and not deleting all of them could cause corrupted database file to remain and
|
||||
// make the application crash on database open operation. To avoid this problem,
|
||||
// the application should provide its own {@link DatabaseErrorHandler} impl class
|
||||
// to delete ALL files of the database (including the attached databases).
|
||||
db.path?.let { deleteDatabaseFile(it) }
|
||||
return
|
||||
}
|
||||
var attachedDbs: List<Pair<String, String>>? = null
|
||||
try {
|
||||
// Close the database, which will cause subsequent operations to fail.
|
||||
// before that, get the attached database list first.
|
||||
try {
|
||||
attachedDbs = db.attachedDbs
|
||||
} catch (e: SQLiteException) {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
db.close()
|
||||
} catch (e: IOException) {
|
||||
/* ignore */
|
||||
}
|
||||
} finally {
|
||||
// Delete all files of this corrupt database and/or attached databases
|
||||
// attachedDbs = null is possible when the database is so corrupt that even
|
||||
// "PRAGMA database_list;" also fails. delete the main database file
|
||||
attachedDbs?.forEach { p ->
|
||||
deleteDatabaseFile(p.second)
|
||||
} ?: db.path?.let { deleteDatabaseFile(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteDatabaseFile(fileName: String) {
|
||||
if (fileName.equals(":memory:", ignoreCase = true) ||
|
||||
fileName.trim { it <= ' ' }.isEmpty()
|
||||
) {
|
||||
return
|
||||
}
|
||||
Log.w(TAG, "deleting the database file: $fileName")
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
deleteDatabase(File(fileName))
|
||||
} else {
|
||||
try {
|
||||
val deleted = File(fileName).delete()
|
||||
if (!deleted) {
|
||||
Log.e(TAG, "Could not delete the database file $fileName")
|
||||
}
|
||||
} catch (error: Exception) {
|
||||
Log.e(TAG, "error while deleting corrupted database file", error)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
/* print warning and ignore exception */
|
||||
Log.w(TAG, "delete failed: ", e)
|
||||
}
|
||||
}
|
||||
|
||||
internal companion object {
|
||||
private const val TAG = "SupportSQLite"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The configuration to create an SQLite open helper object using [Factory].
|
||||
*/
|
||||
class Configuration
|
||||
@Suppress("ExecutorRegistration") // For backwards compatibility
|
||||
constructor(
|
||||
/**
|
||||
* Context to use to open or create the database.
|
||||
*/
|
||||
@JvmField
|
||||
val context: Context,
|
||||
/**
|
||||
* Name of the database file, or null for an in-memory database.
|
||||
*/
|
||||
@JvmField
|
||||
val name: String?,
|
||||
/**
|
||||
* The callback class to handle creation, upgrade and downgrade.
|
||||
*/
|
||||
@JvmField
|
||||
val callback: Callback,
|
||||
/**
|
||||
* If `true` the database will be stored in the no-backup directory.
|
||||
*/
|
||||
@JvmField
|
||||
@Suppress("ListenerLast")
|
||||
val useNoBackupDirectory: Boolean = false,
|
||||
/**
|
||||
* If `true` the database will be delete and its data loss in the case that it
|
||||
* cannot be opened.
|
||||
*/
|
||||
@JvmField
|
||||
@Suppress("ListenerLast")
|
||||
val allowDataLossOnRecovery: Boolean = false
|
||||
) {
|
||||
|
||||
/**
|
||||
* Builder class for [Configuration].
|
||||
*/
|
||||
open class Builder internal constructor(context: Context) {
|
||||
private val context: Context
|
||||
private var name: String? = null
|
||||
private var callback: Callback? = null
|
||||
private var useNoBackupDirectory = false
|
||||
private var allowDataLossOnRecovery = false
|
||||
|
||||
/**
|
||||
* Throws an [IllegalArgumentException] if the [Callback] is `null`.
|
||||
*
|
||||
* Throws an [IllegalArgumentException] if the [Context] is `null`.
|
||||
*
|
||||
* Throws an [IllegalArgumentException] if the [String] database
|
||||
* name is `null`. [Context.getNoBackupFilesDir]
|
||||
*
|
||||
* @return The [Configuration] instance
|
||||
*/
|
||||
open fun build(): Configuration {
|
||||
val callback = callback
|
||||
requireNotNull(callback) {
|
||||
"Must set a callback to create the configuration."
|
||||
}
|
||||
require(!useNoBackupDirectory || !name.isNullOrEmpty()) {
|
||||
"Must set a non-null database name to a configuration that uses the " +
|
||||
"no backup directory."
|
||||
}
|
||||
return Configuration(
|
||||
context,
|
||||
name,
|
||||
callback,
|
||||
useNoBackupDirectory,
|
||||
allowDataLossOnRecovery
|
||||
)
|
||||
}
|
||||
|
||||
init {
|
||||
this.context = context
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name Name of the database file, or null for an in-memory database.
|
||||
* @return This builder instance.
|
||||
*/
|
||||
open fun name(name: String?): Builder = apply {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callback The callback class to handle creation, upgrade and downgrade.
|
||||
* @return This builder instance.
|
||||
*/
|
||||
open fun callback(callback: Callback): Builder = apply {
|
||||
this.callback = callback
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to use a no backup directory or not.
|
||||
*
|
||||
* @param useNoBackupDirectory If `true` the database file will be stored in the
|
||||
* no-backup directory.
|
||||
* @return This builder instance.
|
||||
*/
|
||||
open fun noBackupDirectory(useNoBackupDirectory: Boolean): Builder = apply {
|
||||
this.useNoBackupDirectory = useNoBackupDirectory
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to delete and recreate the database file in situations when the
|
||||
* database file cannot be opened, thus allowing for its data to be lost.
|
||||
*
|
||||
* @param allowDataLossOnRecovery If `true` the database file might be recreated
|
||||
* in the case that it cannot be opened.
|
||||
* @return this
|
||||
*/
|
||||
open fun allowDataLossOnRecovery(allowDataLossOnRecovery: Boolean): Builder = apply {
|
||||
this.allowDataLossOnRecovery = allowDataLossOnRecovery
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Creates a new Configuration.Builder to create an instance of Configuration.
|
||||
*
|
||||
* @param context to use to open or create the database.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun builder(context: Context): Builder {
|
||||
return Builder(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory class to create instances of [SupportSQLiteOpenHelper] using
|
||||
* [Configuration].
|
||||
*/
|
||||
fun interface Factory {
|
||||
/**
|
||||
* Creates an instance of [SupportSQLiteOpenHelper] using the given configuration.
|
||||
*
|
||||
* @param configuration The configuration to use while creating the open helper.
|
||||
*
|
||||
* @return A SupportSQLiteOpenHelper which can be used to open a database.
|
||||
*/
|
||||
fun create(configuration: Configuration): SupportSQLiteOpenHelper
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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 java.io.Closeable
|
||||
|
||||
/**
|
||||
* An interface to map the behavior of [android.database.sqlite.SQLiteProgram].
|
||||
*/
|
||||
interface SupportSQLiteProgram : Closeable {
|
||||
/**
|
||||
* Bind a NULL value to this statement. The value remains bound until
|
||||
* [.clearBindings] is called.
|
||||
*
|
||||
* @param index The 1-based index to the parameter to bind null to
|
||||
*/
|
||||
fun bindNull(index: Int)
|
||||
|
||||
/**
|
||||
* Bind a long value to this statement. The value remains bound until
|
||||
* [clearBindings] is called.
|
||||
* addToBindArgs
|
||||
* @param index The 1-based index to the parameter to bind
|
||||
* @param value The value to bind
|
||||
*/
|
||||
fun bindLong(index: Int, value: Long)
|
||||
|
||||
/**
|
||||
* Bind a double value to this statement. The value remains bound until
|
||||
* [.clearBindings] is called.
|
||||
*
|
||||
* @param index The 1-based index to the parameter to bind
|
||||
* @param value The value to bind
|
||||
*/
|
||||
fun bindDouble(index: Int, value: Double)
|
||||
|
||||
/**
|
||||
* Bind a String value to this statement. The value remains bound until
|
||||
* [.clearBindings] is called.
|
||||
*
|
||||
* @param index The 1-based index to the parameter to bind
|
||||
* @param value The value to bind, must not be null
|
||||
*/
|
||||
fun bindString(index: Int, value: String)
|
||||
|
||||
/**
|
||||
* Bind a byte array value to this statement. The value remains bound until
|
||||
* [.clearBindings] is called.
|
||||
*
|
||||
* @param index The 1-based index to the parameter to bind
|
||||
* @param value The value to bind, must not be null
|
||||
*/
|
||||
fun bindBlob(index: Int, value: ByteArray)
|
||||
|
||||
/**
|
||||
* Clears all existing bindings. Unset bindings are treated as NULL.
|
||||
*/
|
||||
fun clearBindings()
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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
|
||||
|
||||
/**
|
||||
* A query with typed bindings. It is better to use this API instead of
|
||||
* [android.database.sqlite.SQLiteDatabase.rawQuery] because it allows
|
||||
* binding type safe parameters.
|
||||
*/
|
||||
interface SupportSQLiteQuery {
|
||||
/**
|
||||
* The SQL query. This query can have placeholders(?) for bind arguments.
|
||||
*/
|
||||
val sql: String
|
||||
|
||||
/**
|
||||
* Callback to bind the query parameters to the compiled statement.
|
||||
*
|
||||
* @param statement The compiled statement
|
||||
*/
|
||||
fun bindTo(statement: SupportSQLiteProgram)
|
||||
|
||||
/**
|
||||
* Is the number of arguments in this query. This is equal to the number of placeholders
|
||||
* in the query string. See: https://www.sqlite.org/c3ref/bind_blob.html for details.
|
||||
*/
|
||||
val argCount: Int
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* A simple query builder to create SQL SELECT queries.
|
||||
*/
|
||||
class SupportSQLiteQueryBuilder private constructor(private val table: String) {
|
||||
private var distinct = false
|
||||
private var columns: Array<out String>? = null
|
||||
private var selection: String? = null
|
||||
private var bindArgs: Array<out Any?>? = null
|
||||
private var groupBy: String? = null
|
||||
private var having: String? = null
|
||||
private var orderBy: String? = null
|
||||
private var limit: String? = null
|
||||
|
||||
/**
|
||||
* Adds DISTINCT keyword to the query.
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
fun distinct(): SupportSQLiteQueryBuilder = apply {
|
||||
this.distinct = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given list of columns as the columns that will be returned.
|
||||
*
|
||||
* @param columns The list of column names that should be returned.
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
fun columns(columns: Array<out String>?): SupportSQLiteQueryBuilder = apply {
|
||||
this.columns = columns
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the arguments for the WHERE clause.
|
||||
*
|
||||
* @param selection The list of selection columns
|
||||
* @param bindArgs The list of bind arguments to match against these columns
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
fun selection(
|
||||
selection: String?,
|
||||
bindArgs: Array<out Any?>?
|
||||
): SupportSQLiteQueryBuilder = apply {
|
||||
this.selection = selection
|
||||
this.bindArgs = bindArgs
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a GROUP BY statement.
|
||||
*
|
||||
* @param groupBy The value of the GROUP BY statement.
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
fun groupBy(groupBy: String?): SupportSQLiteQueryBuilder = apply {
|
||||
this.groupBy = groupBy
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a HAVING statement. You must also provide [groupBy] for this to work.
|
||||
*
|
||||
* @param having The having clause.
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
fun having(having: String?): SupportSQLiteQueryBuilder = apply {
|
||||
this.having = having
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an ORDER BY statement.
|
||||
*
|
||||
* @param orderBy The order clause.
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
fun orderBy(orderBy: String?): SupportSQLiteQueryBuilder = apply {
|
||||
this.orderBy = orderBy
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a LIMIT statement.
|
||||
*
|
||||
* @param limit The limit value.
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
fun limit(limit: String): SupportSQLiteQueryBuilder = apply {
|
||||
val patternMatches = limitPattern.matcher(
|
||||
limit
|
||||
).matches()
|
||||
require(limit.isEmpty() || patternMatches) { "invalid LIMIT clauses:$limit" }
|
||||
this.limit = limit
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the [SupportSQLiteQuery] that can be passed into
|
||||
* [SupportSQLiteDatabase.query].
|
||||
*
|
||||
* @return a new query
|
||||
*/
|
||||
fun create(): SupportSQLiteQuery {
|
||||
require(!groupBy.isNullOrEmpty() || having.isNullOrEmpty()) {
|
||||
"HAVING clauses are only permitted when using a groupBy clause"
|
||||
}
|
||||
val query = buildString(120) {
|
||||
append("SELECT ")
|
||||
if (distinct) {
|
||||
append("DISTINCT ")
|
||||
}
|
||||
if (!columns.isNullOrEmpty()) {
|
||||
appendColumns(columns!!)
|
||||
} else {
|
||||
append("* ")
|
||||
}
|
||||
append("FROM ")
|
||||
append(table)
|
||||
appendClause(" WHERE ", selection)
|
||||
appendClause(" GROUP BY ", groupBy)
|
||||
appendClause(" HAVING ", having)
|
||||
appendClause(" ORDER BY ", orderBy)
|
||||
appendClause(" LIMIT ", limit)
|
||||
}
|
||||
return SimpleSQLiteQuery(query, bindArgs)
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendClause(name: String, clause: String?) {
|
||||
if (!clause.isNullOrEmpty()) {
|
||||
append(name)
|
||||
append(clause)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the names that are non-null in columns to string, separating
|
||||
* them with commas.
|
||||
*/
|
||||
private fun StringBuilder.appendColumns(columns: Array<out String>) {
|
||||
val n = columns.size
|
||||
for (i in 0 until n) {
|
||||
val column = columns[i]
|
||||
if (i > 0) {
|
||||
append(", ")
|
||||
}
|
||||
append(column)
|
||||
}
|
||||
append(' ')
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val limitPattern = Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?")
|
||||
|
||||
/**
|
||||
* Creates a query for the given table name.
|
||||
*
|
||||
* @param tableName The table name(s) to query.
|
||||
*
|
||||
* @return A builder to create a query.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun builder(tableName: String): SupportSQLiteQueryBuilder {
|
||||
return SupportSQLiteQueryBuilder(tableName)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* An interface to map the behavior of [android.database.sqlite.SQLiteStatement].
|
||||
*/
|
||||
interface SupportSQLiteStatement : SupportSQLiteProgram {
|
||||
/**
|
||||
* Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example
|
||||
* CREATE / DROP table, view, trigger, index etc.
|
||||
*
|
||||
* @throws [android.database.SQLException] If the SQL string is invalid for
|
||||
* some reason
|
||||
*/
|
||||
fun execute()
|
||||
|
||||
/**
|
||||
* Execute this SQL statement, if the the number of rows affected by execution of this SQL
|
||||
* statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements.
|
||||
*
|
||||
* @return the number of rows affected by this SQL statement execution.
|
||||
* @throws [android.database.SQLException] If the SQL string is invalid for
|
||||
* some reason
|
||||
*/
|
||||
fun executeUpdateDelete(): Int
|
||||
|
||||
/**
|
||||
* Execute this SQL statement and return the ID of the row inserted due to this call.
|
||||
* The SQL statement should be an INSERT for this to be a useful call.
|
||||
*
|
||||
* @return the row ID of the last row inserted, if this insert is successful. -1 otherwise.
|
||||
*
|
||||
* @throws [android.database.SQLException] If the SQL string is invalid for
|
||||
* some reason
|
||||
*/
|
||||
fun executeInsert(): Long
|
||||
|
||||
/**
|
||||
* Execute a statement that returns a 1 by 1 table with a numeric value.
|
||||
* For example, SELECT COUNT(*) FROM table;
|
||||
*
|
||||
* @return The result of the query.
|
||||
*
|
||||
* @throws [android.database.sqlite.SQLiteDoneException] if the query returns zero rows
|
||||
*/
|
||||
fun simpleQueryForLong(): Long
|
||||
|
||||
/**
|
||||
* Execute a statement that returns a 1 by 1 table with a text value.
|
||||
* For example, SELECT COUNT(*) FROM table;
|
||||
*
|
||||
* @return The result of the query.
|
||||
*
|
||||
* @throws [android.database.sqlite.SQLiteDoneException] if the query returns zero rows
|
||||
*/
|
||||
fun simpleQueryForString(): String?
|
||||
}
|
@ -0,0 +1,335 @@
|
||||
/*
|
||||
* 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.framework
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.database.Cursor
|
||||
import android.database.SQLException
|
||||
import android.database.sqlite.SQLiteCursor
|
||||
import android.database.sqlite.SQLiteCursorDriver
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteQuery
|
||||
import android.database.sqlite.SQLiteTransactionListener
|
||||
import android.os.Build
|
||||
import android.os.CancellationSignal
|
||||
import android.text.TextUtils
|
||||
import android.util.Pair
|
||||
import androidx.annotation.DoNotInline
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||
import androidx.sqlite.db.SupportSQLiteCompat
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import androidx.sqlite.db.SupportSQLiteQuery
|
||||
import androidx.sqlite.db.SupportSQLiteStatement
|
||||
import java.io.IOException
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Delegates all calls to an implementation of [SQLiteDatabase].
|
||||
*
|
||||
* @constructor Creates a wrapper around [SQLiteDatabase].
|
||||
*
|
||||
* @param delegate The delegate to receive all calls.
|
||||
*/
|
||||
internal class FrameworkSQLiteDatabase(
|
||||
private val delegate: SQLiteDatabase
|
||||
) : SupportSQLiteDatabase {
|
||||
override fun compileStatement(sql: String): SupportSQLiteStatement {
|
||||
return FrameworkSQLiteStatement(delegate.compileStatement(sql))
|
||||
}
|
||||
|
||||
override fun beginTransaction() {
|
||||
delegate.beginTransaction()
|
||||
}
|
||||
|
||||
override fun beginTransactionNonExclusive() {
|
||||
delegate.beginTransactionNonExclusive()
|
||||
}
|
||||
|
||||
override fun beginTransactionWithListener(
|
||||
transactionListener: SQLiteTransactionListener
|
||||
) {
|
||||
delegate.beginTransactionWithListener(transactionListener)
|
||||
}
|
||||
|
||||
override fun beginTransactionWithListenerNonExclusive(
|
||||
transactionListener: SQLiteTransactionListener
|
||||
) {
|
||||
delegate.beginTransactionWithListenerNonExclusive(transactionListener)
|
||||
}
|
||||
|
||||
override fun endTransaction() {
|
||||
delegate.endTransaction()
|
||||
}
|
||||
|
||||
override fun setTransactionSuccessful() {
|
||||
delegate.setTransactionSuccessful()
|
||||
}
|
||||
|
||||
override fun inTransaction(): Boolean {
|
||||
return delegate.inTransaction()
|
||||
}
|
||||
|
||||
override val isDbLockedByCurrentThread: Boolean
|
||||
get() = delegate.isDbLockedByCurrentThread
|
||||
|
||||
override fun yieldIfContendedSafely(): Boolean {
|
||||
return delegate.yieldIfContendedSafely()
|
||||
}
|
||||
|
||||
override fun yieldIfContendedSafely(sleepAfterYieldDelayMillis: Long): Boolean {
|
||||
return delegate.yieldIfContendedSafely(sleepAfterYieldDelayMillis)
|
||||
}
|
||||
|
||||
override var version: Int
|
||||
get() = delegate.version
|
||||
set(value) {
|
||||
delegate.version = value
|
||||
}
|
||||
|
||||
override var maximumSize: Long
|
||||
get() = delegate.maximumSize
|
||||
set(numBytes) {
|
||||
delegate.maximumSize = numBytes
|
||||
}
|
||||
|
||||
override fun setMaximumSize(numBytes: Long): Long {
|
||||
delegate.maximumSize = numBytes
|
||||
return delegate.maximumSize
|
||||
}
|
||||
|
||||
override val isExecPerConnectionSQLSupported: Boolean
|
||||
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
|
||||
override fun execPerConnectionSQL(sql: String, bindArgs: Array<out Any?>?) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
Api30Impl.execPerConnectionSQL(delegate, sql, bindArgs)
|
||||
} else {
|
||||
throw UnsupportedOperationException(
|
||||
"execPerConnectionSQL is not supported on a " +
|
||||
"SDK version lower than 30, current version is: " + Build.VERSION.SDK_INT
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override var pageSize: Long
|
||||
get() = delegate.pageSize
|
||||
set(numBytes) {
|
||||
delegate.pageSize = numBytes
|
||||
}
|
||||
|
||||
override fun query(query: String): Cursor {
|
||||
return query(SimpleSQLiteQuery(query))
|
||||
}
|
||||
|
||||
override fun query(query: String, bindArgs: Array<out Any?>): Cursor {
|
||||
return query(SimpleSQLiteQuery(query, bindArgs))
|
||||
}
|
||||
|
||||
override fun query(query: SupportSQLiteQuery): Cursor {
|
||||
val cursorFactory = {
|
||||
_: SQLiteDatabase?,
|
||||
masterQuery: SQLiteCursorDriver?,
|
||||
editTable: String?,
|
||||
sqLiteQuery: SQLiteQuery? ->
|
||||
query.bindTo(
|
||||
FrameworkSQLiteProgram(
|
||||
sqLiteQuery!!
|
||||
)
|
||||
)
|
||||
SQLiteCursor(masterQuery, editTable, sqLiteQuery)
|
||||
}
|
||||
|
||||
return delegate.rawQueryWithFactory(
|
||||
cursorFactory, query.sql, EMPTY_STRING_ARRAY, null)
|
||||
}
|
||||
|
||||
@RequiresApi(16)
|
||||
override fun query(
|
||||
query: SupportSQLiteQuery,
|
||||
cancellationSignal: CancellationSignal?
|
||||
): Cursor {
|
||||
return SupportSQLiteCompat.Api16Impl.rawQueryWithFactory(delegate, query.sql,
|
||||
EMPTY_STRING_ARRAY, null, cancellationSignal!!
|
||||
) { _: SQLiteDatabase?,
|
||||
masterQuery: SQLiteCursorDriver?,
|
||||
editTable: String?,
|
||||
sqLiteQuery: SQLiteQuery? ->
|
||||
query.bindTo(
|
||||
FrameworkSQLiteProgram(
|
||||
sqLiteQuery!!
|
||||
)
|
||||
)
|
||||
SQLiteCursor(masterQuery, editTable, sqLiteQuery)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(SQLException::class)
|
||||
override fun insert(table: String, conflictAlgorithm: Int, values: ContentValues): Long {
|
||||
return delegate.insertWithOnConflict(table, null, values, conflictAlgorithm)
|
||||
}
|
||||
|
||||
override fun delete(table: String, whereClause: String?, whereArgs: Array<out Any?>?): Int {
|
||||
val query = buildString {
|
||||
append("DELETE FROM ")
|
||||
append(table)
|
||||
if (!whereClause.isNullOrEmpty()) {
|
||||
append(" WHERE ")
|
||||
append(whereClause)
|
||||
}
|
||||
}
|
||||
val statement = compileStatement(query)
|
||||
SimpleSQLiteQuery.bind(statement, whereArgs)
|
||||
return statement.executeUpdateDelete()
|
||||
}
|
||||
|
||||
override fun update(
|
||||
table: String,
|
||||
conflictAlgorithm: Int,
|
||||
values: ContentValues,
|
||||
whereClause: String?,
|
||||
whereArgs: Array<out Any?>?
|
||||
): Int {
|
||||
// taken from SQLiteDatabase class.
|
||||
require(values.size() != 0) { "Empty values" }
|
||||
|
||||
// move all bind args to one array
|
||||
val setValuesSize = values.size()
|
||||
val bindArgsSize =
|
||||
if (whereArgs == null) setValuesSize else setValuesSize + whereArgs.size
|
||||
val bindArgs = arrayOfNulls<Any>(bindArgsSize)
|
||||
val sql = buildString {
|
||||
append("UPDATE ")
|
||||
append(CONFLICT_VALUES[conflictAlgorithm])
|
||||
append(table)
|
||||
append(" SET ")
|
||||
|
||||
var i = 0
|
||||
for (colName in values.keySet()) {
|
||||
append(if (i > 0) "," else "")
|
||||
append(colName)
|
||||
bindArgs[i++] = values[colName]
|
||||
append("=?")
|
||||
}
|
||||
if (whereArgs != null) {
|
||||
i = setValuesSize
|
||||
while (i < bindArgsSize) {
|
||||
bindArgs[i] = whereArgs[i - setValuesSize]
|
||||
i++
|
||||
}
|
||||
}
|
||||
if (!TextUtils.isEmpty(whereClause)) {
|
||||
append(" WHERE ")
|
||||
append(whereClause)
|
||||
}
|
||||
}
|
||||
val stmt = compileStatement(sql)
|
||||
SimpleSQLiteQuery.bind(stmt, bindArgs)
|
||||
return stmt.executeUpdateDelete()
|
||||
}
|
||||
|
||||
@Throws(SQLException::class)
|
||||
override fun execSQL(sql: String) {
|
||||
delegate.execSQL(sql)
|
||||
}
|
||||
|
||||
@Throws(SQLException::class)
|
||||
override fun execSQL(sql: String, bindArgs: Array<out Any?>) {
|
||||
delegate.execSQL(sql, bindArgs)
|
||||
}
|
||||
|
||||
override val isReadOnly: Boolean
|
||||
get() = delegate.isReadOnly
|
||||
|
||||
override val isOpen: Boolean
|
||||
get() = delegate.isOpen
|
||||
|
||||
override fun needUpgrade(newVersion: Int): Boolean {
|
||||
return delegate.needUpgrade(newVersion)
|
||||
}
|
||||
|
||||
override val path: String?
|
||||
get() = delegate.path
|
||||
|
||||
override fun setLocale(locale: Locale) {
|
||||
delegate.setLocale(locale)
|
||||
}
|
||||
|
||||
override fun setMaxSqlCacheSize(cacheSize: Int) {
|
||||
delegate.setMaxSqlCacheSize(cacheSize)
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||
override fun setForeignKeyConstraintsEnabled(enabled: Boolean) {
|
||||
SupportSQLiteCompat.Api16Impl.setForeignKeyConstraintsEnabled(delegate, enabled)
|
||||
}
|
||||
|
||||
override fun enableWriteAheadLogging(): Boolean {
|
||||
return delegate.enableWriteAheadLogging()
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||
override fun disableWriteAheadLogging() {
|
||||
SupportSQLiteCompat.Api16Impl.disableWriteAheadLogging(delegate)
|
||||
}
|
||||
|
||||
@get:RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||
override val isWriteAheadLoggingEnabled: Boolean
|
||||
get() = SupportSQLiteCompat.Api16Impl.isWriteAheadLoggingEnabled(delegate)
|
||||
|
||||
override val attachedDbs: List<Pair<String, String>>?
|
||||
get() = delegate.attachedDbs
|
||||
|
||||
override val isDatabaseIntegrityOk: Boolean
|
||||
get() = delegate.isDatabaseIntegrityOk
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun close() {
|
||||
delegate.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this object delegates to the same given database reference.
|
||||
*/
|
||||
fun isDelegate(sqLiteDatabase: SQLiteDatabase): Boolean {
|
||||
return delegate == sqLiteDatabase
|
||||
}
|
||||
|
||||
@RequiresApi(30)
|
||||
internal object Api30Impl {
|
||||
@DoNotInline
|
||||
fun execPerConnectionSQL(
|
||||
sQLiteDatabase: SQLiteDatabase,
|
||||
sql: String,
|
||||
bindArgs: Array<out Any?>?
|
||||
) {
|
||||
sQLiteDatabase.execPerConnectionSQL(sql, bindArgs)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val CONFLICT_VALUES =
|
||||
arrayOf(
|
||||
"",
|
||||
" OR ROLLBACK ",
|
||||
" OR ABORT ",
|
||||
" OR FAIL ",
|
||||
" OR IGNORE ",
|
||||
" OR REPLACE "
|
||||
)
|
||||
private val EMPTY_STRING_ARRAY = arrayOfNulls<String>(0)
|
||||
}
|
||||
}
|
@ -0,0 +1,341 @@
|
||||
/*
|
||||
* 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.framework
|
||||
|
||||
import android.content.Context
|
||||
import android.database.DatabaseErrorHandler
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteException
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.sqlite.db.SupportSQLiteCompat
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper
|
||||
import androidx.sqlite.util.ProcessLock
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
||||
internal class FrameworkSQLiteOpenHelper @JvmOverloads constructor(
|
||||
private val context: Context,
|
||||
private val name: String?,
|
||||
private val callback: SupportSQLiteOpenHelper.Callback,
|
||||
private val useNoBackupDirectory: Boolean = false,
|
||||
private val allowDataLossOnRecovery: Boolean = false
|
||||
) : SupportSQLiteOpenHelper {
|
||||
|
||||
// Delegate is created lazily
|
||||
private val lazyDelegate = lazy {
|
||||
// OpenHelper initialization code
|
||||
val openHelper: OpenHelper
|
||||
|
||||
if (
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
||||
name != null &&
|
||||
useNoBackupDirectory
|
||||
) {
|
||||
val file = File(
|
||||
SupportSQLiteCompat.Api21Impl.getNoBackupFilesDir(context),
|
||||
name
|
||||
)
|
||||
openHelper = OpenHelper(
|
||||
context = context,
|
||||
name = file.absolutePath,
|
||||
dbRef = DBRefHolder(null),
|
||||
callback = callback,
|
||||
allowDataLossOnRecovery = allowDataLossOnRecovery
|
||||
)
|
||||
} else {
|
||||
openHelper = OpenHelper(
|
||||
context = context,
|
||||
name = name,
|
||||
dbRef = DBRefHolder(null),
|
||||
callback = callback,
|
||||
allowDataLossOnRecovery = allowDataLossOnRecovery
|
||||
)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
SupportSQLiteCompat.Api16Impl.setWriteAheadLoggingEnabled(
|
||||
openHelper,
|
||||
writeAheadLoggingEnabled
|
||||
)
|
||||
}
|
||||
return@lazy openHelper
|
||||
}
|
||||
|
||||
private var writeAheadLoggingEnabled = false
|
||||
|
||||
// getDelegate() is lazy because we don't want to File I/O until the call to
|
||||
// getReadableDatabase() or getWritableDatabase(). This is better because the call to
|
||||
// a getReadableDatabase() or a getWritableDatabase() happens on a background thread unless
|
||||
// queries are allowed on the main thread.
|
||||
|
||||
// We defer computing the path the database from the constructor to getDelegate()
|
||||
// because context.getNoBackupFilesDir() does File I/O :(
|
||||
private val delegate: OpenHelper by lazyDelegate
|
||||
|
||||
override val databaseName: String?
|
||||
get() = name
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||
override fun setWriteAheadLoggingEnabled(enabled: Boolean) {
|
||||
if (lazyDelegate.isInitialized()) {
|
||||
// Use 'delegate', it is already initialized
|
||||
SupportSQLiteCompat.Api16Impl.setWriteAheadLoggingEnabled(delegate, enabled)
|
||||
}
|
||||
writeAheadLoggingEnabled = enabled
|
||||
}
|
||||
|
||||
override val writableDatabase: SupportSQLiteDatabase
|
||||
get() = delegate.getSupportDatabase(true)
|
||||
|
||||
override val readableDatabase: SupportSQLiteDatabase
|
||||
get() = delegate.getSupportDatabase(false)
|
||||
|
||||
override fun close() {
|
||||
if (lazyDelegate.isInitialized()) {
|
||||
delegate.close()
|
||||
}
|
||||
}
|
||||
|
||||
private class OpenHelper(
|
||||
val context: Context,
|
||||
name: String?,
|
||||
/**
|
||||
* This is used as an Object reference so that we can access the wrapped database inside
|
||||
* the constructor. SQLiteOpenHelper requires the error handler to be passed in the
|
||||
* constructor.
|
||||
*/
|
||||
val dbRef: DBRefHolder,
|
||||
val callback: SupportSQLiteOpenHelper.Callback,
|
||||
val allowDataLossOnRecovery: Boolean
|
||||
) : SQLiteOpenHelper(
|
||||
context, name, null, callback.version,
|
||||
DatabaseErrorHandler { dbObj ->
|
||||
callback.onCorruption(
|
||||
getWrappedDb(
|
||||
dbRef,
|
||||
dbObj
|
||||
)
|
||||
)
|
||||
}) {
|
||||
// see b/78359448
|
||||
private var migrated = false
|
||||
|
||||
// see b/193182592
|
||||
private val lock: ProcessLock = ProcessLock(
|
||||
name = name ?: UUID.randomUUID().toString(),
|
||||
lockDir = context.cacheDir,
|
||||
processLock = false
|
||||
)
|
||||
private var opened = false
|
||||
|
||||
fun getSupportDatabase(writable: Boolean): SupportSQLiteDatabase {
|
||||
return try {
|
||||
lock.lock(!opened && databaseName != null)
|
||||
migrated = false
|
||||
val db = innerGetDatabase(writable)
|
||||
if (migrated) {
|
||||
// there might be a connection w/ stale structure, we should re-open.
|
||||
close()
|
||||
return getSupportDatabase(writable)
|
||||
}
|
||||
getWrappedDb(db)
|
||||
} finally {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
private fun innerGetDatabase(writable: Boolean): SQLiteDatabase {
|
||||
val name = databaseName
|
||||
val isOpen = opened
|
||||
if (name != null && !isOpen) {
|
||||
val databaseFile = context.getDatabasePath(name)
|
||||
val parentFile = databaseFile.parentFile
|
||||
if (parentFile != null) {
|
||||
parentFile.mkdirs()
|
||||
if (!parentFile.isDirectory) {
|
||||
Log.w(TAG, "Invalid database parent file, not a directory: $parentFile")
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
return getWritableOrReadableDatabase(writable)
|
||||
} catch (t: Throwable) {
|
||||
// No good, just try again...
|
||||
super.close()
|
||||
}
|
||||
try {
|
||||
// Wait before trying to open the DB, ideally enough to account for some slow I/O.
|
||||
// Similar to android_database_SQLiteConnection's BUSY_TIMEOUT_MS but not as much.
|
||||
Thread.sleep(500)
|
||||
} catch (e: InterruptedException) {
|
||||
// Ignore, and continue
|
||||
}
|
||||
val openRetryError: Throwable = try {
|
||||
return getWritableOrReadableDatabase(writable)
|
||||
} catch (t: Throwable) {
|
||||
super.close()
|
||||
t
|
||||
}
|
||||
if (openRetryError is CallbackException) {
|
||||
// Callback error (onCreate, onUpgrade, onOpen, etc), possibly user error.
|
||||
val cause = openRetryError.cause
|
||||
when (openRetryError.callbackName) {
|
||||
CallbackName.ON_CONFIGURE,
|
||||
CallbackName.ON_CREATE,
|
||||
CallbackName.ON_UPGRADE,
|
||||
CallbackName.ON_DOWNGRADE -> throw cause
|
||||
CallbackName.ON_OPEN -> {}
|
||||
}
|
||||
// If callback exception is not an SQLiteException, then more certainly it is not
|
||||
// recoverable.
|
||||
if (cause !is SQLiteException) {
|
||||
throw cause
|
||||
}
|
||||
} else if (openRetryError is SQLiteException) {
|
||||
// Ideally we are looking for SQLiteCantOpenDatabaseException and similar, but
|
||||
// corruption can manifest in others forms.
|
||||
if (name == null || !allowDataLossOnRecovery) {
|
||||
throw openRetryError
|
||||
}
|
||||
} else {
|
||||
throw openRetryError
|
||||
}
|
||||
|
||||
// Delete the database and try one last time. (mAllowDataLossOnRecovery == true)
|
||||
context.deleteDatabase(name)
|
||||
try {
|
||||
return getWritableOrReadableDatabase(writable)
|
||||
} catch (ex: CallbackException) {
|
||||
// Unwrap our exception to avoid disruption with other try-catch in the call stack.
|
||||
throw ex.cause
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWritableOrReadableDatabase(writable: Boolean): SQLiteDatabase {
|
||||
return if (writable) {
|
||||
super.getWritableDatabase()
|
||||
} else {
|
||||
super.getReadableDatabase()
|
||||
}
|
||||
}
|
||||
|
||||
fun getWrappedDb(sqLiteDatabase: SQLiteDatabase): FrameworkSQLiteDatabase {
|
||||
return getWrappedDb(dbRef, sqLiteDatabase)
|
||||
}
|
||||
|
||||
override fun onCreate(sqLiteDatabase: SQLiteDatabase) {
|
||||
try {
|
||||
callback.onCreate(getWrappedDb(sqLiteDatabase))
|
||||
} catch (t: Throwable) {
|
||||
throw CallbackException(CallbackName.ON_CREATE, t)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUpgrade(sqLiteDatabase: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
migrated = true
|
||||
try {
|
||||
callback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion)
|
||||
} catch (t: Throwable) {
|
||||
throw CallbackException(CallbackName.ON_UPGRADE, t)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigure(db: SQLiteDatabase) {
|
||||
if (!migrated && callback.version != db.version) {
|
||||
// Reduce the prepared statement cache to the minimum allowed (1) to avoid
|
||||
// issues with queries executed during migrations. Note that when a migration is
|
||||
// done the connection is closed and re-opened to avoid stale connections, which
|
||||
// in turns resets the cache max size. See b/271083856
|
||||
db.setMaxSqlCacheSize(1)
|
||||
}
|
||||
try {
|
||||
callback.onConfigure(getWrappedDb(db))
|
||||
} catch (t: Throwable) {
|
||||
throw CallbackException(CallbackName.ON_CONFIGURE, t)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
migrated = true
|
||||
try {
|
||||
callback.onDowngrade(getWrappedDb(db), oldVersion, newVersion)
|
||||
} catch (t: Throwable) {
|
||||
throw CallbackException(CallbackName.ON_DOWNGRADE, t)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOpen(db: SQLiteDatabase) {
|
||||
if (!migrated) {
|
||||
// if we've migrated, we'll re-open the db so we should not call the callback.
|
||||
try {
|
||||
callback.onOpen(getWrappedDb(db))
|
||||
} catch (t: Throwable) {
|
||||
throw CallbackException(CallbackName.ON_OPEN, t)
|
||||
}
|
||||
}
|
||||
opened = true
|
||||
}
|
||||
|
||||
// No need sync due to locks.
|
||||
override fun close() {
|
||||
try {
|
||||
lock.lock()
|
||||
super.close()
|
||||
dbRef.db = null
|
||||
opened = false
|
||||
} finally {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
private class CallbackException(
|
||||
val callbackName: CallbackName,
|
||||
override val cause: Throwable
|
||||
) : RuntimeException(cause)
|
||||
|
||||
internal enum class CallbackName {
|
||||
ON_CONFIGURE, ON_CREATE, ON_UPGRADE, ON_DOWNGRADE, ON_OPEN
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getWrappedDb(
|
||||
refHolder: DBRefHolder,
|
||||
sqLiteDatabase: SQLiteDatabase
|
||||
): FrameworkSQLiteDatabase {
|
||||
val dbRef = refHolder.db
|
||||
return if (dbRef == null || !dbRef.isDelegate(sqLiteDatabase)) {
|
||||
FrameworkSQLiteDatabase(sqLiteDatabase).also { refHolder.db = it }
|
||||
} else {
|
||||
dbRef
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SupportSQLite"
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used as an Object reference so that we can access the wrapped database inside
|
||||
* the constructor. SQLiteOpenHelper requires the error handler to be passed in the
|
||||
* constructor.
|
||||
*/
|
||||
private class DBRefHolder(var db: FrameworkSQLiteDatabase?)
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.framework
|
||||
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper
|
||||
|
||||
/**
|
||||
* Implements [SupportSQLiteOpenHelper.Factory] using the SQLite implementation in the
|
||||
* framework.
|
||||
*/
|
||||
class FrameworkSQLiteOpenHelperFactory : SupportSQLiteOpenHelper.Factory {
|
||||
override fun create(
|
||||
configuration: SupportSQLiteOpenHelper.Configuration
|
||||
): SupportSQLiteOpenHelper {
|
||||
return FrameworkSQLiteOpenHelper(
|
||||
configuration.context,
|
||||
configuration.name,
|
||||
configuration.callback,
|
||||
configuration.useNoBackupDirectory,
|
||||
configuration.allowDataLossOnRecovery
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.framework
|
||||
|
||||
import android.database.sqlite.SQLiteProgram
|
||||
import androidx.sqlite.db.SupportSQLiteProgram
|
||||
|
||||
/**
|
||||
* An wrapper around [SQLiteProgram] to implement [SupportSQLiteProgram] API.
|
||||
*/
|
||||
internal open class FrameworkSQLiteProgram(
|
||||
private val delegate: SQLiteProgram
|
||||
) : SupportSQLiteProgram {
|
||||
override fun bindNull(index: Int) {
|
||||
delegate.bindNull(index)
|
||||
}
|
||||
|
||||
override fun bindLong(index: Int, value: Long) {
|
||||
delegate.bindLong(index, value)
|
||||
}
|
||||
|
||||
override fun bindDouble(index: Int, value: Double) {
|
||||
delegate.bindDouble(index, value)
|
||||
}
|
||||
|
||||
override fun bindString(index: Int, value: String) {
|
||||
delegate.bindString(index, value)
|
||||
}
|
||||
|
||||
override fun bindBlob(index: Int, value: ByteArray) {
|
||||
delegate.bindBlob(index, value)
|
||||
}
|
||||
|
||||
override fun clearBindings() {
|
||||
delegate.clearBindings()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
delegate.close()
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.framework
|
||||
|
||||
import android.database.sqlite.SQLiteStatement
|
||||
import androidx.sqlite.db.SupportSQLiteStatement
|
||||
|
||||
/**
|
||||
* Delegates all calls to a [SQLiteStatement].
|
||||
*
|
||||
* @constructor Creates a wrapper around a framework [SQLiteStatement].
|
||||
*
|
||||
* @param delegate The SQLiteStatement to delegate calls to.
|
||||
*/
|
||||
internal class FrameworkSQLiteStatement(
|
||||
private val delegate: SQLiteStatement
|
||||
) : FrameworkSQLiteProgram(
|
||||
delegate
|
||||
), SupportSQLiteStatement {
|
||||
override fun execute() {
|
||||
delegate.execute()
|
||||
}
|
||||
|
||||
override fun executeUpdateDelete(): Int {
|
||||
return delegate.executeUpdateDelete()
|
||||
}
|
||||
|
||||
override fun executeInsert(): Long {
|
||||
return delegate.executeInsert()
|
||||
}
|
||||
|
||||
override fun simpleQueryForLong(): Long {
|
||||
return delegate.simpleQueryForLong()
|
||||
}
|
||||
|
||||
override fun simpleQueryForString(): String? {
|
||||
return delegate.simpleQueryForString()
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2019 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.util
|
||||
import android.util.Log
|
||||
import androidx.annotation.RestrictTo
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.nio.channels.FileChannel
|
||||
import java.util.concurrent.locks.Lock
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
/**
|
||||
* Utility class for in-process and multi-process key-based lock mechanism for safely doing
|
||||
* synchronized operations.
|
||||
*
|
||||
* Acquiring the lock will be quick if no other thread or process has a lock with the same key.
|
||||
* But if the lock is already held then acquiring it will block, until the other thread or process
|
||||
* releases the lock. Note that the key and lock directory must be the same to achieve
|
||||
* synchronization.
|
||||
*
|
||||
* Locking is done via two levels:
|
||||
*
|
||||
* 1. Thread locking within the same JVM process is done via a map of String key to ReentrantLock
|
||||
* objects.
|
||||
*
|
||||
* 2. Multi-process locking is done via a lock file whose name contains the key and FileLock
|
||||
* objects.
|
||||
*
|
||||
* Creates a lock with `name` and using `lockDir` as the directory for the
|
||||
* lock files.
|
||||
*
|
||||
* @param name the name of this lock.
|
||||
* @param lockDir the directory where the lock files will be located.
|
||||
* @param processLock whether to use file for process level locking or not by default. The
|
||||
* behaviour can be overridden via the [lock] method.
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
class ProcessLock(
|
||||
name: String,
|
||||
lockDir: File?,
|
||||
private val processLock: Boolean
|
||||
) {
|
||||
private val lockFile: File? = lockDir?.let { File(it, "$name.lck") }
|
||||
private val threadLock: Lock = getThreadLock(name)
|
||||
private var lockChannel: FileChannel? = null
|
||||
|
||||
/**
|
||||
* Attempts to grab the lock, blocking if already held by another thread or process.
|
||||
*
|
||||
* @param [processLock] whether to use file for process level locking or not.
|
||||
*/
|
||||
fun lock(processLock: Boolean = this.processLock) {
|
||||
threadLock.lock()
|
||||
if (processLock) {
|
||||
try {
|
||||
if (lockFile == null) {
|
||||
throw IOException("No lock directory was provided.")
|
||||
}
|
||||
// Verify parent dir
|
||||
val parentDir = lockFile.parentFile
|
||||
parentDir?.mkdirs()
|
||||
lockChannel = FileOutputStream(lockFile).channel.apply { lock() }
|
||||
} catch (e: IOException) {
|
||||
lockChannel = null
|
||||
Log.w(TAG, "Unable to grab file lock.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the lock.
|
||||
*/
|
||||
fun unlock() {
|
||||
try {
|
||||
lockChannel?.close()
|
||||
} catch (ignored: IOException) { }
|
||||
threadLock.unlock()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SupportSQLiteLock"
|
||||
// in-process lock map
|
||||
private val threadLocksMap: MutableMap<String, Lock> = HashMap()
|
||||
private fun getThreadLock(key: String): Lock = synchronized(threadLocksMap) {
|
||||
return threadLocksMap.getOrPut(key) { ReentrantLock() }
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue