Using buildQuery to call query method of database exposed through ContentProvider - android

I am inserting a ContentProvider layer in between the ORM of SugarORM and the underlying SQLite database in order to be able to use a SyncAdapter with it.
The ORM has a method like this:
public static <T extends SugarRecord<?>> List<T> find(Class<T> type,String whereClause,String[] whereArgs,String groupBy, String orderBy, String limit)
which matches nicely a query method of the SQLiteDatabase:
public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
the problem is that the ContentProvider only has one query method which doesn't match the necessary parameters:
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
So I was thinking of turning the parameters from the ORM into a SQL query, and then just passing that to my method, which can then run the raw query and return a cursor, like this:
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
{
switch (uriMatcher.match(uri))
{
case RAW_QUERY:
cursor = db.rawQuery(selection, selectionArgs);//query, arguments
...
the first issue is that this method of SQLiteQueryBuilder is deprecated:
buildQuery (String[] projectionIn, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder, String limit)
so then I tried this:
Object args[] = whereArgs;
String where_query = String.format(whereClause, args);
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables(getTableName(type));
String query = builder.buildQuery(null, whereClause, whereArgs, groupBy, null, orderBy, limit);
but the resulting query string is null.
Any suggestions?

public Cursor query(Uri iUri, String[] iProjection, String iSelection,
String[] iSelectionArgs, String iSortOrder) {
SQLiteQueryBuilder mBuilder = new SQLiteQueryBuilder();
mBuilder.setTables(Database.KEY_TABLE);
switch (uriMatcher.match(iUri)) {
case RAW_QUERY:
mBuilder.appendWhere(Database.KEY_ROWID);
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + iUri);
}
Cursor cursor = mBuilder.query(db, iProjection, iSelection,
iSelectionArgs, null, null, iSortOrder);
return cursor;
}
Try this in your code i think this will solve your problem.

Related

SQLite retrieve foreign key values with content provider

I have a sqlite database with "label" and "idea" tables in my Android app. made a foreign key on idea table as idea_label with int values that connected to label_table on its _id.
I use Loader to load my Cursor on the mainActivity that loads my idea table from the provider. As obvious it load idea_label int (But what I seek is to load the value from label_table which sets in label_body).
My loader on mainActivity class
#Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
String[] projection = {
DatabaseContract.IdeaEntry._ID,
DatabaseContract.IdeaEntry.COLUMN_IDEA_NAME,
DatabaseContract.IdeaEntry.COLUMN_IDEA_DESCRIPTION,
DatabaseContract.IdeaEntry.COLUMN_IDEA_DATE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_LABEL,
DatabaseContract.IdeaEntry.COLUMN_IDEA_ICON,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_ACTIVE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_FAVORITE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_DONE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_ARCHIVED,
DatabaseContract.IdeaEntry.COLUMN_IDEA_ORDER,
};
return new CursorLoader(this,
DatabaseContract.IdeaEntry.CONTENT_URI_IDEA,
projection,
null, // selection
null, // selectionArgs
DatabaseContract.IdeaEntry.COLUMN_IDEA_ORDER // order
);
}
That calls this section on my provider class
#Nullable
#Override
public Cursor query(#NonNull Uri uri, #Nullable String[] projection, #Nullable String selection, #Nullable String[] selectionArgs, #Nullable String sortOrder) {
SQLiteDatabase database = mDatabaseHelper.getReadableDatabase();
Cursor cursor;
int match = sUriMatcher.match(uri);
switch (match){
case IDEAS:
cursor = database.query(DatabaseContract.IdeaEntry.IDEA_TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
Is there a way? trigger another loader? or my implementation is wrong in this case? any way or help that direct me to the right direction, will be very appreciated.
With SQLiteQueryBuilder you can join tables in setTables method and then just specify label_body column of label table in projection.
String[] projection = {
DatabaseContract.IdeaEntry._ID,
....
DatabaseContract.LabelEntry.COLUMN_LABEL_BODY,
};
...
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables("IDEA JOIN LABEL ON IDEA.IDEA_LABEL = LABEL._ID");
builder.query(database, projection, selection, selectionArgs, null, null, sortOrder);
Thanks to #gar_r to help and guide me , I did like this with DatabaseContract constants:
#Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
String[] projection = {
DatabaseContract.IdeaEntry.IDEA_TABLE_NAME + "." + DatabaseContract.IdeaEntry._ID,
DatabaseContract.IdeaEntry.COLUMN_IDEA_NAME,
DatabaseContract.IdeaEntry.COLUMN_IDEA_DESCRIPTION,
DatabaseContract.IdeaEntry.COLUMN_IDEA_DATE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_LABEL,
DatabaseContract.IdeaEntry.COLUMN_IDEA_ICON,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_ACTIVE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_FAVORITE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_DONE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_ARCHIVED,
DatabaseContract.IdeaEntry.COLUMN_IDEA_ORDER,
DatabaseContract.LabelEntry.COLUMN_LABEL_BODY,
};
return new CursorLoader(this,
DatabaseContract.IdeaEntry.CONTENT_URI_IDEA,
projection,
null, // selection
null, // selectionArgs
DatabaseContract.IdeaEntry.COLUMN_IDEA_ORDER // order
);
}
That calls this section on my provider class
#Nullable
#Override
public Cursor query(#NonNull Uri uri, #Nullable String[] projection, #Nullable String selection, #Nullable String[] selectionArgs, #Nullable String sortOrder) {
SQLiteDatabase database = mDatabaseHelper.getReadableDatabase();
Cursor cursor;
int match = sUriMatcher.match(uri);
switch (match){
case IDEAS:
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables(DatabaseContract.IdeaEntry.IDEA_TABLE_NAME + " JOIN " + DatabaseContract.LabelEntry.LABEL_TABLE_NAME
+ " ON " + DatabaseContract.IdeaEntry.COLUMN_IDEA_LABEL + " = " + DatabaseContract.LabelEntry.LABEL_TABLE_NAME + "." + DatabaseContract.LabelEntry._ID);
cursor = builder.query(database, projection, selection, selectionArgs, null, null, sortOrder);
break;

How do implicit joined columns work with Android contacts data?

I'm querying the ContactsContract.Data table to find phone records.
I get an error when I create a new CursorLoader:
java.lang.IllegalArgumentException: Invalid column deleted
My code:
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Data;
...
String[] projection = {
Phone.DELETED,
Phone.LOOKUP_KEY,
Phone.NUMBER,
Phone.TYPE,
Phone.LABEL,
Data.MIMETYPE,
Data.DISPLAY_NAME_PRIMARY
};
// "mimetype = ? AND deleted = ?"
String selection = Data.MIMETYPE + " = ? AND " Phone.DELETED + " = ?";
String[] args = {Phone.CONTENT_ITEM_TYPE, "0"};
return new CursorLoader(
this,
Data.CONTENT_URI,
projection,
selection,
args,
null);
Any idea why the Phone.DELETED column isn't included in the cursor? The documentation does say -
Some columns from the associated raw contact are also available
through an implicit join.
Looks like you've found a feature that has been documented in many places, but hadn't been implemented yet. I opened a bug for tracking this issue - lets see what AOSP guys have to say on the subject (bug report).
Meanwhile, you can use the following workaround:
Uri uri = ContactsContract.RawContactsEntity.CONTENT_URI;
String[] projection = {
Phone._ID,
Phone.DELETED,
//Phone.LOOKUP_KEY,
Phone.NUMBER,
Phone.TYPE,
Phone.LABEL,
Data.MIMETYPE,
Data.DISPLAY_NAME_PRIMARY
};
String selection = Data.MIMETYPE + " = ? AND " + Data.DELETED + " = ?";
String[] args = {
Phone.CONTENT_ITEM_TYPE, "0"
};
return new CursorLoader(
this,
uri,
projection,
selection,
args,
null);
Changes:
Use RawContactsEntity's URI
LOOKUP_KEY is not accessible via above URI - you'll have to execute additional query if you absolutely need this column
_ID column will be required if you are going to use the resulting Cursor in CursorAdapter.
Edit: following #MichaelAlanHuff's request I'm posting the parts of code which this answer is based upon
From com.android.providers.contacts.ContactsProvider2#queryLocal() (source code of ContactsProvider2):
protected Cursor queryLocal(final Uri uri, final String[] projection, String selection,
String[] selectionArgs, String sortOrder, final long directoryId,
final CancellationSignal cancellationSignal) {
final SQLiteDatabase db = mDbHelper.get().getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String groupBy = null;
String having = null;
String limit = getLimit(uri);
boolean snippetDeferred = false;
// The expression used in bundleLetterCountExtras() to get count.
String addressBookIndexerCountExpression = null;
final int match = sUriMatcher.match(uri);
switch (match) {
...
case DATA:
case PROFILE_DATA:
{
final String usageType = uri.getQueryParameter(DataUsageFeedback.USAGE_TYPE);
final int typeInt = getDataUsageFeedbackType(usageType, USAGE_TYPE_ALL);
setTablesAndProjectionMapForData(qb, uri, projection, false, typeInt);
if (uri.getBooleanQueryParameter(Data.VISIBLE_CONTACTS_ONLY, false)) {
qb.appendWhere(" AND " + Data.CONTACT_ID + " in " + Tables.DEFAULT_DIRECTORY);
}
break;
}
...
}
qb.setStrict(true);
// Auto-rewrite SORT_KEY_{PRIMARY, ALTERNATIVE} sort orders.
String localizedSortOrder = getLocalizedSortOrder(sortOrder);
Cursor cursor = query(db, qb, projection, selection, selectionArgs, localizedSortOrder, groupBy,
having, limit, cancellationSignal);
if (readBooleanQueryParameter(uri, Contacts.EXTRA_ADDRESS_BOOK_INDEX, false)) {
bundleFastScrollingIndexExtras(cursor, uri, db, qb, selection,
selectionArgs, sortOrder, addressBookIndexerCountExpression,
cancellationSignal);
}
if (snippetDeferred) {
cursor = addDeferredSnippetingExtra(cursor);
}
return cursor;
}
As you can see, there are two additional methods where SQLiteQueryBuilder used to build the query could be changed: setTablesAndProjectionMapForData() and additional query() method.
Source of com.android.providers.contacts.ContactsProvider2#setTablesAndProjectionMapForData():
private void setTablesAndProjectionMapForData(SQLiteQueryBuilder qb, Uri uri,
String[] projection, boolean distinct, boolean addSipLookupColumns, Integer usageType) {
StringBuilder sb = new StringBuilder();
sb.append(Views.DATA);
sb.append(" data");
appendContactPresenceJoin(sb, projection, RawContacts.CONTACT_ID);
appendContactStatusUpdateJoin(sb, projection, ContactsColumns.LAST_STATUS_UPDATE_ID);
appendDataPresenceJoin(sb, projection, DataColumns.CONCRETE_ID);
appendDataStatusUpdateJoin(sb, projection, DataColumns.CONCRETE_ID);
appendDataUsageStatJoin(
sb, usageType == null ? USAGE_TYPE_ALL : usageType, DataColumns.CONCRETE_ID);
qb.setTables(sb.toString());
boolean useDistinct = distinct || !ContactsDatabaseHelper.isInProjection(
projection, DISTINCT_DATA_PROHIBITING_COLUMNS);
qb.setDistinct(useDistinct);
final ProjectionMap projectionMap;
if (addSipLookupColumns) {
projectionMap =
useDistinct ? sDistinctDataSipLookupProjectionMap : sDataSipLookupProjectionMap;
} else {
projectionMap = useDistinct ? sDistinctDataProjectionMap : sDataProjectionMap;
}
qb.setProjectionMap(projectionMap);
appendAccountIdFromParameter(qb, uri);
}
Here you see the construction of table argument of the final query using StringBuilder which is being passed to several append*() methods. I'm not going to post their source code, but they really join the tables that appear in methods' names. If rawContacts table would be joined in, I'd expect to see a call to something like appendRawContactJoin() here...
For completeness: the other query() method that I mentioned does not modify table argument:
private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection,
String selection, String[] selectionArgs, String sortOrder, String groupBy,
String having, String limit, CancellationSignal cancellationSignal) {
if (projection != null && projection.length == 1
&& BaseColumns._COUNT.equals(projection[0])) {
qb.setProjectionMap(sCountProjectionMap);
}
final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, having,
sortOrder, limit, cancellationSignal);
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), ContactsContract.AUTHORITY_URI);
}
return c;
}
The inspection of the above chain of methods led me to the conclusion that there is an officially documented feature which is not implemented.

After startQuery, onQueryComplete not be called

I used Content Provider to query some information(Android). After I called the startQuery(), I can output the result of my query in Log. But after all the result finished, the function onQueryComplete() was not called. I cannot find the reason.
The code below is my startQuery and onQueryComplete implementations.
public void queryAsync(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, IContinue<Cursor> callback) {
this.startQuery(0, callback, uri, projection, selection, selectionArgs, sortOrder);
}
#Override
public void onQueryComplete(int token, Object cookie, Cursor c) {
Log.e("debug","query complete!");
if (cookie != null) {
#SuppressWarnings("unchecked")
IContinue<Cursor> callback = (IContinue<Cursor>) cookie;
callback.kontinue(c);
}
}
The code below is part of my query code in content provider:
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch (uriMatcher.match(uri)){
...
case SINGLE_MESSAGE:
String selection3 = SEQNUM + "=?";
String[] selectArgs2 = { "0" };
Cursor cm = db.query(MESSAGE_TABLE, null, selection3, selectArgs2, null, null, null);
Log.i("debug",Integer.toString(cm.getCount()));
return cm;
}
}
How can I fix this problem?

Android: SQLite query with selection WHERE clause is not processed

I am trying to limit the database result by defining a SQL WHERE clause in the selection string of the query of the ContentResolver.
Cursor cursor = getContentResolver().query(uri, null, getSelectionString(), null, null);
...
public String getSelectionString() {
// TODO Replace latitude and longitude with database reference.
StringBuilder string = new StringBuilder();
string.append("latitude >= ").append(northEast.getLatitudeE6() / 1e6);
string.append(" AND ");
string.append("latitude <= ").append(southWest.getLatitudeE6() / 1e6);
string.append(" AND ");
string.append("longitude >= ").append(southWest.getLongitudeE6() / 1e6);
string.append(" AND ");
string.append("longitude <= ").append(northEast.getLongitudeE6() / 1e6);
return string.toString();
}
The database columns are defined as follows ...
public class CustomDatabase {
public static final class Contract {
public static final String COLUMN_NAME = "name";
public static final String COLUMN_LATITUDE = "latitude";
public static final String COLUMN_LONGITUDE = "longitude";
}
}
...
I am not particularly sure that I can inspect the query sent in cursor. If so, it does not contain the WHERE clause I sent:
SQLiteQuery: SELECT * FROM custom_db ORDER BY number ASC
Here is an example of the selection string:
latitude >= 48.203927 AND latitude <= 48.213851 AND longitude >= 16.36735 AND longitude <= 16.377648
Questions:
Is the WHERE clause syntactically correct?
Do I need apostrophs?
Am I forced to use both parameters (selection, selectionArgs) at a time in a query?
Where can I debug the whole query?
EDIT:
Here is the query() method of the ContentProvider ...
public class CustomProvider extends ContentProvider {
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch (URI_MATCHER.match(uri)) {
case URI_CODE_LOCATIONS:
return mCustomDatabase.getLocations();
}
return null;
}
... obviously, as biegleux guessed, I forgot to pass the parameters. Doh!
Here is the current implementation of the database method ...
public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
public Cursor getLocation() {
return mDatabaseHelper.getReadableDatabase().query(
CustomSQLiteOpenHelper.TABLE_NAME,
null, null, null, null, null, null);
}
Do you suggest that I change the method signature to the following to pass all parameters? Am I not exposing to much of the database interface this way?
public Cursor getLocations(String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) {
return mDatabaseHelper.getReadableDatabase().query(
CustomSQLiteOpenHelper.TABLE_NAME,
columns, selection, selectionArgs, groupBy, having, orderBy);
}
Ok, so it seems query() method of your provider is causing problems.
Make sure it looks like following.
#Override
public abstract Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
...
// run the query
db.query(db, projection, selection, selectionArgs, groupBy, having, sortOrder, limit);
You are not forced to use selectionArgs parameter, but with it code is more readable.
To debug a query you can use
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
Log.e(TAG, qb.buildQuery(projection, selection, selectionArgs, groupBy, having, sortOrder, limit);
EDIT:
If you have a ContentProvider implemented you don't need to expose getLocations() method as you/users can use ContentProvider's query() method.
You should pass at least those arguments whose can be passed in query() method and those are projection, selection, selectionArgs and sortOrder.
Shouldn't that line be:
Cursor cursor = getContentResolver().query(uri, null, getSelectionString(), null, null);
Note the added parens after getSelectionString to indicate it's a method call, not a string (although I do wonder why that wouldn't throw an error as getSelectionString wouldn't exist as a string if my theory is correct...).

how to GET the SINGLE ROW with cursor in android?

i want to get the row from databas which name = jiten
but is gives error in syntax can anyone tell me what is the right syntax for
getting the single row
here is my code for database class
public void getdata()
{
Cursor cursor = myDataBase.query("emp", new String[] {"email","name"}, " name='jiten'",new String[]{}, null, null, null);
Log.e("running", "cursor run");
if(cursor!=null)
{
Log.e("running", "curosr is not null");
while(cursor.moveToFirst())
{
Log.e("running", "curosr while loop enter");
String temp = (cursor.getString(cursor.getColumnIndex(email)));
String temp2 =(cursor.getString(cursor.getColumnIndex(name)));
Log.e("running", "id email" +temp+ " name"+temp2);
}
}
}
You are missing the selectionArgs
public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
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.
http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html
in your case it would be:
Cursor cursor = myDataBase.query("emp", new String[] {"email","name"}, "name=?",new String[]{"jiten"}, null, null, null);

Categories

Resources