Can I query inside a SQLite Transaction? - android

I'm using the SQLite3 database system in the Android library.
I need to execute a query during a transaction to see if there is a similar entry already there. If there is, I have to perform some other logic and adjustments before I add a new row.
Can I execute a query within a transaction and get the result back immediately?

The answer should be yes, BUT only if you're doing queries in the same thread the transaction was started in. Even if you use the same database connection, queries fail completely if performed on different threads when you're inside transaction (despite transaction mode).

Yes it is safe to do so.

Related

Return a query without LiveData

Basically what I want to do is when I open my app It'll make a query to get a value from the database and use it.
I'm using Room and when I try not to use LiveData it tells me Cannot access database on the main thread since it may potentially lock the UI for a long period of time , so I wonder how could I avoid using LiveData, because if I use it (if I understood right) I can't just pick the information from the database - I have to wait until something changes and only then I'd have access to the query results.
So I found a simple solution.
Because I just want this query used once - on app startup, I just enabled Room database to use the main thread using the allowMainThreadQueries() function.
You're welcome to post here your solution if you think it's better, it would be appreciated.

Android: how can I lock SQLite database accessible with ContentProvider (or other way of executing atomic conditional operations)

I got two tables in my SQLite DB: entities and user_actions. Their approximate schemes:
The flow of the program is something like this (all DB accesses handled by ContentProvider):
The user performs some action which modifies one of the entities
The corresponding entity is updated in entities immediately. locally_modified value of this entity is set to 1
The information about user's action is stored in user_actions
At some point in future a sync session with the server is being initiated (I use SyncAdapter framework)
User's actions from user_actions are uploaded to the server one by one and removed from the DB in a background thread
When the uploading completed, I need to clear locally_modified flags in entities
At this point I encounter my atomicity issue: the synchronization with the server happens in a background thread, therefore the user can use the app and perform additional actions. As a consequence, right before I clear locally_modified flag for an entity, I must check that there are no records in user_actions corresponding to this entity. These three steps must be executed atomically for each entity having locally_modified set to 1:
Query user_actions for entries corresponding to entity's _id
Test whether the query from #1 returned an empty set
Clear locally_modified of that entity to 0
Given the above scenario, I have three questions:
Q1: Is there a way to lock SQLite DB accessed over ContentProvider in Android such that it can be accessed only by the locking thread?
Q2: If the answer to Q1 is positive, what happens if some other thread tries to access a locked DB? What precautions should I take to ensure reliable operation?
Q3: It is possible to execute atomic transactions with conditional logic using ContentProviderOperation? You can use "back-references" as described in this answer and this blog post to reference the result of a previous operations, but is there a way to use that result in some kind of if-else statement?
Thanks
Is there a way to lock SQLite DB in Android such that it can be accessed only by the locking thread?
Yes, have a look at SQLiteDatabase.beginTransaction() (source). I believe you need SQLite's exclusive transactions, but you need to study that a bit more for your exact usage.
If the answer to Q1 is positive, what happens if some other thread tries to access a locked DB? What precautions should I take to ensure reliable operation?
There's an SQLite.amIInTransaction() method that you could check, or just catch an SQLiteDatabaseLockedException (more SQLite exceptions that you should look up)
It is possible to execute atomic transactions with conditional logic using ContentProviderOperation? You can use "back-references" as described in this answer and this blog post to reference the result of a previous operations, but is there a way to use that result in some kind of if-else statement?
Never done that, but it seems that overriding ContentProvider's applyBatch and wrapping it in a transaction should work:
Android: SQLite transactions when using ContentResolver
The answer turned out to be pretty simple, but it is kind of a "hack" - just add additional Uri to ContentProvider.
For example: initially my ContentProvider supported the following URIs:
Uri.withAppendedPath(MyContract.CONTENT_URI, "entities")
Uri.withAppendedPath(MyContract.CONTENT_URI, "user_actions")
In order to support the atomic operation described in the question I added an additional Uri:
Uri.withAppendedPath(MyContract.CONTENT_URI, "clear_modified_flag")
When this Uri is updated through:
getContentResolver().update(
MyContract.ClearModifiedFlag.CONTENT_URI,
new ContentValues(),
null,
null);
my ContentProvider executes an SQLite transaction that locks the database for the duration of the operation and rolls it back in case of any errors (as described in this answer).
That's it.
P.S. my ContentProvider is not exported (i.e. other apps can't access and use it), therefore it is safe to add this new Uri to it. But keep in mind that if you do export your ContentProvider, then exposing functionality like this one could be problematic.

How to make SQL insert safely?

I am creating a Hotel Booking System for the Android application.
I was thinking about how to implement a safe way to insert a booking into my database. The request will be sent from the app to the MySQL database (which is hosted on a web server) via my web service.
When I create a booking I insert the: CustomerID, HotelID, RoomID (via a nested select statement to find an available room), checkInDate and checkOutDate.
However, how can I ensure that two different people won't book the same/last room if they both hit 'book now' at roughly the same time. I thought about using Synchornisedfor the java method but will that make any difference if a range of different users on different mobiles try to make a booking?
What you are looking for is transactions.
Using transactions you will be able to isolate access to a given set of records into a single, atomic logical entity. Any operation of a certain complexity affects multiple rows, columns and even tables, or needs multiple (atomic or non-atomic) statements to complete. A transaction is a mechanism to ensure logical consistency of your data even if any of these operations fails. In that case, the incomplete transaction is rolled back, otherwise the transaction is committed.
And these are exactly the both possible outcomes with transactions: commit or rollback. In pseudo code it looks like this:
begin transaction
try {
required operations to reserve a room for a given time frame
if( success)
commit transaction
else
rollback transaction
} catch {
rollback transaction
}
Synchronizing in Java is certainly possible, but it has several major drawbacks:
it prevents you from scaling the application, because it affects only the current process.
it also prevents you from extending the solution, e.g. by sharing the data with associated programs
a database transaction is designed to handle crashes without producing inconsistent data
I don't think that you're asking about threading as much as avoiding a race condition.
Not knowing anything about your architecture, one way to avoid such a race is to set a timestamp when each user hits the 'book now' button and pass it as a field in the transaction. The application server would then sort all the entries in its queue according to the timestamp, as opposed to simply accepting them in the order they arrive.
You need to create a unique key {room number, date} such that a room can only have one booking per date. Then a client booking becomes an insert of as many rows as there are days in the booking, carried out within a transaction.
Threads really have little to do with it.

Database operation using transaction methods in Android

I am reading SQLite in Android, I see that there is transaction methods to do CRUD operation on SQLite in Android, same thing can be done without transactions methods.
Transaction methods : beginTransaction, setTransactionSuccessfull, endTransaction.
In what cases should we use the transactions method over simple approach.
All sqlite writes are in transactions. If you don't start one explicitly yourself, one will be started and committed for you implicitly. This implicit transaction finishes automatically when the statement finishes. Further reading.
Generally, you want explicit transactions when you want to group more than one database operation as one to maintain ACID properties of your data model. As a consequence, you also gain performance benefit since you don't need to wait for I/O after each statement, only at commit.
It's also worth noting that sqlite does not support nested transactions but the Android sqlite API emulates them using nest level counting. As a consequence, any nested Android sqlite transaction really commits or rollbacks when the outermost transactions is committed or rolled back.

Does yieldIfContendedSafely() lose the benefits of a transaction?

I have a long-running operation which I perform in a background thread. As it is important for the operation to either complete successfully or not at all, I am wrapping the entire operation in a transaction.
Aspects of the UI need read-only access to the database during this time. To avoid blocking the UI, I am experimenting with inserting calls to db.yieldIfContendedSafely() in the main loop of the background operation.
This does what I want in that the UI is no longer blocked, but it's not completely clear to me if this is risking a loss of data integrity.
The javadoc for yieldIfContendedSafely() says:
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.
Does this mean that my long-running operation is actually being committed to the database in separate chunks, or is the overall transaction maintaining enough state to commit the whole lot in one go at the end, thus preserving data-integrity?
Does this mean that my long-running operation is actually being committed to the database in separate chunks
Yes. Within yieldIfContendedSafely(), Android calls setTransactionSuccessful(), endTransaction(), and begins a new transaction -- committing your statements in the process. There is no mechanism to rollback the "real" transaction after it ends.
This behavior only occurs if there is another thread waiting on the database, otherwise yieldIfContendedSafely() does nothing.
I checked this with the following scenario. I started two threads: one inserted data into a table using a transaction, another read data out of the same table. The transaction didn't call setTransactionSuccessful() so normally everything is rolled back at the end, leaving the table empty. I added a call to yieldIfContendedSafely(), and afterwards the table was not empty and had data from the transaction.

Categories

Resources