how add limit clause to manageQuery on android - android

Android's API provides a clean mechanism via SQLite to make queries into the contact list. However, I am not sure how to limit the results:
Cursor cur = ((Activity)mCtx).managedQuery(
People.CONTENT_URI,
columns,
"LIMIT ? OFFSET ?",
new String[] { Integer.toString(limit), Integer.toString(offset) },
null
);
Doesn't work.

Actually, depending on the provider you can append a limit to the URI as follows:
uri.buildUpon().appendQueryParameter("limit", "40").build()
I know the MediaProvider handles this and from looking at recent code it seems you can do it with contacts too.

You are accessing a ContentProvider, not SQLite, when you query the Contacts ContentProvider. The ContentProvider interface does not support a LIMIT clause directly.
If you are directly accessing a SQLite database of your own, use the rawQuery() method on SQLiteDatabase and add a LIMIT clause.

I found out from this bug that Android uses the following regex to parse the LIMIT clause of a query:
From <framework/base/core/java/android/database/sqlite/SQLiteQueryBuilder.java>
LIMIT clause is checked with following sLimitPattern.
private static final Pattern sLimitPattern = Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
Note that the regex does accept the format offsetNumber,limitNumber even though it doesn't accept the OFFSET statement directly.

I think you have to do this sort of manually. The Cursor object that is returned from the managedQuery call doesn't execute a full query right off. You have to use the Cursor.move*() methods to jump around the result set.
If you want to limit it, then create your own limit while looping through the results. If you need paging, then you can use the Cursor.moveToPosition(startIndex) and start reading from there.

You can specify the "limit" parameter in the "order" parameter, maybe even inside other parameters if you don't want to sort, because you'll have to specify a column to sort by then:
mContentResolver.query(uri, columnNames, null, null, "id LIMIT 1");

Related

SQLite query in Android Nougat is not backwards compatible

I have an SQLite query that does not return a record by _id on Android Nougat but does return a record on Android pre-Nougat.
When I attach a debugger to the Nougat emulator and start poking around, I observe the following:
Original Code
readableDatabase.query("report_view", null, "_id = ?", new String[]{"2016309001"}, null, null, null).getCount()
Result: 0
Poking around / Evaluate expression
readableDatabase.rawQuery("SELECT * FROM report_view WHERE _id = 2016309001", null).getCount()
Result: 1
Enabling the SQLite log results in (note the single quotes):
V/SQLiteStatements: /data/user/0/xyz/bla.db: "SELECT * FROM report_view WHERE _id = '2016309001'"
For the non-raw query on Nougat. I cannot seem to enable the SQLite log on the pre-Nougat (Marshmallow) device.
So it seems the issue is caused by Android surrounding the parameter with single quotes. The _id column is of type integer. And a value surrounded by quotes is of type string. Why is this suddenly an issue on Nougat?
Unless I am mistaken your original code passes the date parameter explicitly as a string (new String[]), and the Android documentation for the query method, selectioArgs parameter states
selectionArgs String: You may include ?s in selection, which will be
replaced by the values from selectionArgs, in order that they appear
in the selection. The values will be bound as Strings.
So if you want something else than strings, you should probably perform a cast, or better, do not use the query and rawQuery methods, but prepared statements (SQLiteStatement), which will allow you to specify the type of parameters.
Also the '?' being substituted in the log should be a "fake" substitution for logging purposes (at least I hope it is a fake substitution for logging purposes, as Android should be using a bound parameter there, and not replace it in the query).
Finally the field types in Android are used only for type affinity, so it is entirely possible that even if you declared your field as "integer" in the table, if you used the Query method and its String-only parameters, you actually ended up with strings in your records.
In theory, SQLite should not behave differently. It is possible that you are using a different table definition that results in a different affinity.
Anyway, the parameter you give to the query function is a string, so it is not surprising that the database handles it as a string.
If you want to give a number to the database, you have to make the parameter a number.
And the Android framework doesn't allow this for most database functions, so you have to put it directly into the string:
db.query("report_view", null, "_id = " + "2016309001", null, null, null, null).getCount();
Please note that there is a helper function for counting rows:
DatabaseUtils.queryNumEntries(db, "report_view", "_id = " + "2016309001");

ContentProvider/ContentResolver Query Sorting by ID by default

I am working on an application that stores everything in a database and it is accessed using a ContentProvider. My scenario is as follows:
I make a web call and receive a list of integers which represent the ids of the objects I need to retrieve from my database on the device.
I make a call to ContentResolver.query() with the following Selection:
Selection: _id=? OR _id=? OR _id=?
Selection Ids: 30; 165; 149;
So, I need to get all items where the id is either 30, 165, or 149. And I need them in that exact order.
This is the exact call I am making on the ContentResolver:
Cursor cursor = mActivity.getContentResolver().query(myUri, null, selection, selectionIds, null);
As you can see, I do not pass in any sorting. However, the result gives me a Cursor with the order being the following: 30, 149, 165. So, it appears it is defaulting the sorting by _id even though I do not specify any sort order. My question is, does anyone know of a way to stop this from happening?
When you select rows, from any database, without specifying an ORDER BY clause, you should consider the order of the results as undefined, i.e. it could come back in any order. The reason you are seeing it sorted by _id here is just due to circumstance - they are likely to be in that order on the underlying database files so that is the order SQLite reads them back in. However it is not safe to assume that will always be so.
So the actual answer to your question is no, you can't assume SQLite will return your rows in any particular order without an ORDER BY clause. If you are unable to provide such a clause (which appears to be the case here) you'll have to sort them in code after getting all the data from the cursor.
It is not defaulting to _id, it is giving you the records as they are in the db (which happen to be sorted by id). Pass your own sorting order if you don't want this.

Android - Sort calls by cached name if exist (use of ifnull in sqlite)

i would like sort the call logs by cached name. The cached name can be null.
So in the case of a null cached name, i would like to have the phone number for an alias.
in sqlite, there is the ifnull() function.
ifnull details
I try :
String[] projections = new String[] { Calls._ID, Calls.NUMBER, Calls.DATE, Calls.TYPE, Calls.DURATION, "ifnull("+Calls.CACHED_NAME+","+Calls.NUMBER+") as display_name" };
Cursor cursor_call = ctx.getContentResolver().query(URI_CALL_LOGS,
projections,
null,
null,
null);
But i have an error with my use of ifnull an i don't find a sample of this function.
java.lang.IllegalArgumentException: Invalid column ifnull(name,number) as display_name
Because of the way you're creating the query, things like parentheses are being auto-escaped to prevent SQL-injection attacks.
In situations where you have direct access to the raw DB (not through a contentProvider), you can use a SQLiteDatabase.rawQuery, like so:
myDB.rawquery("select * from a, b where a.id=b.someid", null);
But in this case it looks like you want to access the contact app's call_log database, which you can only do through the provided ContentResolver. Since it's intentionally designed to only let you send values and not SQL commands as variables, ifnull(...) isn't going to work, and you'll need to choose between name and number using logic when you're pulling data from the cursor.

Can I perform this Android query with ContentResolver.query()? (LEFT JOIN and CASE)

I am looking to perform the following query (in pseudo-code) on Android:
SELECT C.ID, C.NAME, CASE ISNULL(G.GROUPID,0) = 0 THEN 0 ELSE 1 END INGROUP
FROM CONTACTS C
LEFT JOIN GROUPMEMBERSHIP G ON G.CONTACTID = C.ID AND G.GROUPID = ?
I am looking to select the ID and Name of ALL contacts in the system address book, via the default Contacts ContentProvider, along with a
0/1 field indicating whether the contact is a member of group ? .
I could of course get all contacts easily enough, then loop through and query the membership separately easy enough in my Adapter class, but I'd imagine performing the two queries as one outer joined query would yield much better performance.
Can I do this with the standard high-level string-projection and ContentResolver.query() method? Or would this kind of query require digging into more direct SQL execution?
Edit: Okay, so this doesn't actually solve the question asked, because eidylon is tied to an existing ContentProvider as mentioned in their question. However, this does cover how you do a JOIN if you own the ContentProvider source and API. So I'll leave it for those who want to know how to handle that case.
This is easy! But unintuitive... :)
query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
Okay, so what is URI? Typically, you have one URI per table.
content://com.example.coolapp.contacts serves data out of your CONTACTS table.
content://com.example.coolapp.groupmembers serves data out of your GROUPMEMBERSHIP table.
But URI is really just a string. Use it however you like. Make a block of code in your ContentProvider that responds to content://com.example.coolapp.contacts_in_group. Within that block of code in the ContentProvider, you can get raw access to your SQLite DB, unfettered by the limited query() data model. Feel free to use it!
Define your selection fields however you like. They don't have to map to table column names -- map them how you need to, in order to get your parameters in.
Define your projection how you need -- It may contain columns from both tables after the join.
Bing, you're done. Google does this same model internally in their own code -- Go look at the Contacts provider API -- you see "bla.RawContact" and "bla.Contact" and etc as content URIs. Each serves data out of the same table in the DB -- the different URIs just provide different views of that same table!
Nope, you can't do that kind of queries with the ContentResolver.query() method.
You will need to write something like this:
SQLiteDatabase db = YourActivity.getDbHelper().getReadableDatabase();
String query = yourLongQuery;
Cursor c = db.rawQuery(query, null);
YourActivity.startManagingCursor(c);
c.setNotificationUri(YourActivity.getContentResolver(), YourContentProvider.CONTENT_URI);
You can't do that because ContentResolver has only one query method:
query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
there's no parameter for tables or FROM clauses.

Android CallLog.Calls provider query problem

I am trying to get call log by using the CallLog.Calls content provider. However, I am little lost about the query I need to make. I am able to make a query and load the result in a ListView. But the query return all types of call.
Of course I can use a switch-case and take appropriate action as per the types of call returned. But for my program I only need the outgoing call logs.
So, how do I modify the query to get only outgoing call types. (I believe I have to use CallLog.Calls.OUTGOING_TYPE somewhere?). I have tried to modify the query in various ways but I keep getting error. If I try to supply the CallLog.Calls.OUTGOING_TYPE as an selection arg I get an error as its an int type and the query looks for String type.
I may be missing something simple, but can't figure it out. Any help would be much appreciated. Thank You. Here's my query below,
getContentResolver().query(CallLog.Calls.CONTENT_URI,null, null, null, ORDER_BY );
Try this code snippet:
Cursor c = getContentResolver().query(CallLog.Calls.CONTENT_URI, null,
CallLog.Calls.TYPE + "=?",
new String[] { String.valueOf(CallLog.Calls.OUTGOING_TYPE) },
ORDER_BY);

Categories

Resources