I am using Android's content providers as a wrapper around an SQLite3 database. It's never been my choice to use content providers for stand alone app centric databases due to the limited flexibility and bloat they can cause. However I'm working on a pre-existing code base. I need to write an insert query with a WHERE NOT EXISTS clause. However, I see that the insert command of the ContentProvider only permits a URL (which I used to identify the table) and a ContentValues object containing the data to insert as a new row.
Is there any way to do a this, or something that is in effect the same, without manually performing essentially an inefficient checking select query before the insert for checking purposes?
I don't want to modify the DB table structures or constraints as there are to many dependencies which may break with alterations.
Related
I'm trying to implement databases linked with foreign keys and I've been told it's better (more secure) to stick to queries instead of rawQueries in general, but I'm unsure how far. What's the best approach to CRUD operations; to use multiple queries (.query(.)) or use more sophisticated rawQueries?
For example
If I want to delete a row from a table iff there is no other table linking to that particular row.
Or are there perhaps better ways when constructing the database that makes such operations smoother?
Also, when inserting into a table and there's already a row (and the column is unique or similar restraint), is there a quick way to find the rowId of the value being inserted regardless of whether or not it was inserted or already exists?
I tried `insertWithOnConflict(.)`
but it throws -1 instead of the rowId when already exists. Is there a better way than to execute another query afterwards upon failure?
Assume that I want to update dirty flags of some contacts linked to a group.
To do this,
From 'data' table, records having the group ID should be queried.
Should update using contact ID in the fetched records.
But if I can use SQL, it can be done with one SQL statement.
Is it possible?
Thanks in advance.
No, I don't think so.
See, Android API doesn't let you access the SQLite database behind the contacts (although it is there) but rather abstracts data access by means of ContentProviders (And there's a good reason for that: giving developers access to the SQLite db would be way too insecure -- any app with proper permissions could e.g. drop the contact tables and thus cause major malfunctions of other apps)
It's not much more complex to run update statements on those though (well, apart from the fact that SQL statements are kind of broken down into methods and parameters), the ContentProvider class has .update() method for just that, the tricky part is the WHERE part of the call, you'll have to take a good look at the ContactsContract class.
I need to get all contacts on a phone and their associated phone numbers, therefore I need to join some of the contact tables that Android stores in its internal contacts.db. I think just joining the data by an SQL statement would be much easier than going with all that content provider stuff, or is there any way to join data on those? Also, if I write an SQL statement, are there any constants for the table names and fields, so that I can keep the query at least a little bit generic, so it won't break on the next Android update, in case they change a table name? Thanks for any hint!
You could write your own content provider to do the join, but there you would use your own SQL query anyway.
The built-in contact providers have several documented column names.
The table names appear to be an implementation detail, but if they ever change, it's likely that the semantics will change too, so your custom queries would break anyway.
When using a content provider for SQLite database access
Is it better practice to have a content provider for each table or to use one for all tables?
How to handle one-to-many relationships when creating new records?
A ContentProvider is not a database
A ContentProvider is a way to publicly (or semi-publicly) access data as content. This may be done in a number of ways, via file access, SQLite or even web access. A ContentProvider in and of itself is not a database, but you can program a database for it. You may also have multiple ContentProviders accessing the same database, but distributing different levels of access, or the same content in different ways according to the requestor.
What you are really asking is not a ContentProvider question, but a database question "How to handle relationships in an SQLite database" because the ContentProvider doesn't use any database code unless you tell it to via an SQLiteOpenHelper and other similar classes. So, you simply have to program your database access correctly and your SQLite database will work as desired.
A database is a database
In the old days, databases were simply flat files where each table was often its own entity to allow for growth. Now, with DBMS, there is very little reason to ever do that. SQLite is just like any other database platform in this regard and can house as many tables as you have space to hold them.
SQLite
There are certain features that SQLite handles well, some that it handles - but not well, and some that it does not handle at all. Relationships are one of those things that were left out of some versions of Android's SQLite, because it shipped without foreign key support. This was a highly requested feature and it was added in SQLite 3.6.22 which didn't ship until Android 2.2. There are still many reported bugs with it, however, in its earliest incarnations.
Android pre 2.2
Thankfully being SQL compliant and a simple DBMS (not RDBMS at this time), there are some easy ways to work around this, after all, a foreign key is just a field in another table.
You can enforce database INSERT and UPDATE statements by creating CONSTRAINTs when you use your CREATE TABLE statement.
You can query the other table for the appropriate _id to get your foreign key.
You can query your source table with any appropriate SELECT statement using an INNER JOIN, thus enforcing a pseudo-relationship.
Since Android's version of SQLite does not enforce relationships directly, if you wanted to CASCADE ON DELETE you would have to do it manually. But this can be done via another simple SQL statement. I have essentially written my own library to enforce these kinds of relationships, as it all must be done manually. I must say, however, the efficiency of SQLite and SQL as a whole makes this very quick and easy.
In essence, the process for any enforced relationship goes as follows:
In a query that requires a foreign key, use a JOIN.
In an INSERT use a CONSTRAINT on the foreign key field of NOT NULL
In an UPDATE on the primary key field that is a foreign key in another TABLE, run a second UPDATE on the related TABLE that has the foreign key. (CASCADE UPDATE)
For a DELETE with the same parameters, do another DELETE with the where being foreign_key = _id (make sure you get the _id before you DELETE the row, first).
Android 2.2+
Foreign keys is supported, but is off by default. First you have to turn them on:
db.execSQL("PRAGMA foreign_keys=ON;");
Next you have to create the relationship TRIGGER. This is done when you create the TABLE, rather than a separate TRIGGER statement. See below:
// Added at the end of CREATE TABLE statement in the MANY table
FOREIGN KEY(foreign_key_name) REFERENCES one_table_name(primary_key_name)
For further information on SQLite and its capabilities, check out SQLite official site. This is important as you don't have all of the JOINs that you do in other RDBMS. For specific information on the SQLite classes in Android, read the documentation.
As for first question: you don't need to create content provider for every table. You can use in with multiple tables, but the complexity of provider increased with each table.
A Content Provider is roughly equivalent to the concept of a database. You'd have multiple tables in a database, so having multiple tables in your content provider makes perfect sense.
One to many relationships can be handled just like in any other database. Use references and foreign keys like you would with any other database. You can use things like CASCADE ON DELETE to make sure records are deleted when the records they reference in other tables are also deleted.
I am being asked to provide a dual-cache scheme for syncing cloud DB with device DB. The requirement is to create a set of tables and then at some point in time, the sync will occur to a mirror of the same set of tables. When done, the ContentProvider is expected to swap one set for the other without the application noticing.
Is this possible?
Can I add-swap-drop tables safely?
Can I add-swap-drop several tables safely - some activities may be displaying joins?
Comments and ideas are welcome, but it is not an option to "merge sync" the existing tables.
My concern is that there can be outstanding cursors in Activities or Services that are operating on the table-set and no way to assure that they have closed the cursor before the table that it may be accessing is dropped.
Yes it is possible (Even though your two-table requirement from your customer sounds like a really ugly hack.)
Your ContentProvider is expected to serve up a cursor, in response to a specific Content URI. How that URI gets mapped to a table in the database is completely up to you.
So do this:
Create 2 tables, "data1" and "data2" with an identical database schema. Fill data1 with your data like normally. When an app queries your URI, respond with a cursor filled with data out of data1.
Later, fill up data2. When the command comes and it's time to switch tables, call notifyChange() with the (one) URI you use for that data. All the cursors that are listening (Your display apps are using RegisterContentObserver() aren't they?) will be notified that the data at your URI has changed. When they get that notify, they'll re-query. This time, your ContentProvider will answer with data out of table2 instead of table1... The cursor doesn't know what table it's filled from, just what URI. So to the applications using the data, the change is completely transparent (as long as the two tables have the same schema.)
I've glossed over commanding the switch and saving which table is active. That's obviously kind of application specific to you, but you should be able to find a good way to persist that information already.
As for table JOINs... The trick of course is to ensure that the logic to do the JOIN itself happens inside the ContentProvider, not inside the app. Then, you just switch several sets of tables at once, and do JOIN out of the right sets.
Finally, as for stale Cursors, the Cursor contains a copy of table data, not any reference to the table itself. So if the table goes away, the Cursor data would be stale, at worst. It won't hold the table open or the like. The Cursor keeps a back-reference to the Content URI it came from, not the table. So, again, when it requeries, it'll work just like above.