Android - passing field name as selectionArgs to rawQuery - android

I'm passing the below statement as a rawQuery in Android:
SELECT DISTINCT ltUsers._id,ltUsers.NAME,ltUsers.GLOBAL_ID, ltGroups.GROUP_NAME
FROM ltUsers
JOIN ltGroups ON (ltUsers.GROUP_ID = ltGroups.GLOBAL_ID)
WHERE ltgroups.GLOBAL_ID = ? " +
ORDER BY ltUsers.NAME ASC,ltgroups.GLOBAL_ID ASC;
With the rawQuery as follows:
Cursor c = db.rawQuery(sql,args)
It works just fine if I pass a value to the parameter, e.g.
String[] args = new String[]{"2"}
However, I also want to be able to show all rows, unlimited by the GLOBAL_ID in the WHERE clause. Testing on a dump of my SQLite database outside of Android - as well as in Android by just writing the parameter directly into the statement - shows the following clause to be a valid way to do this:
WHERE ltGroups.GLOBAL_ID = ltGroups.GLOBAL_ID
Yet when I pass the field reference ltGroups.GLOBAL_ID or [ltGroups].[GLOBAL_ID] as a parameter it fails to return any rows in the rawQuery. Any ideas on why this might be happening? Happy to produce any extra information.

Parameters always replace specific values, not anything else.
When you put the string "ltGroups.GLOBAL_ID" into the parameters array, it is interpreted as exactly that, a string.
(To show all records, just omit the WHERE clause.)

Related

Sqlite: SqliteDatabase.delete() vs a raw query

Conclusion: Android's database APIs work but the documentation is horribly incomplete.
I have recently run into a brain wrecking situation due to the flexibility Sqlite provides by not forcing you to specify the data type when creating the table. The problem was my mindset that assumed that every data type would be a general character sequence if not specified and therefore the way to talk to database is through java.lang.String.
But you can't blame me either when you see methods like the below:
int delete (String table,
String whereClause,
String[] whereArgs)
in the SqlDatabase class from Android docs.
I have a table consisting of Phone No(that I stored as java.lang.String) and Timestamp as a long field. When I tried deleting a record using this method, it just never got deleted despite countless debugging.
I checked everything and query was alright and table is existent and all the checklist until by chance, I discovered that removing the '' around the timestamp while querying in a raw manner instead of using the above method yields a successful deletion, something like this:
DELETE FROM messages_records_table WHERE messageTimestamp = 1508494606000;
instead of the following:
DELETE FROM messages_records_table WHERE messageTimestamp = '1508494606000';
or,
DELETE FROM messages_records_table WHERE messageTimestamp = "1508494606000";
Phone No isn't a problem; it's the timestamp that was creating the problem in INSERTION/DELETION
So, I tried running a raw deletion query with quotes removed(that are required with a string/varchar type) and it yielded successful deletion. I used the following method for this:
db.execSQL(String sql, Object[] whereArgs)
The key thing to notice here is that Object[] is different from String[] when compared to delete(). I passed a Long to Object to make it work but passing a Long.toString() in delete() seems to be useless.
So my question is, Is my analysis correct and delete() API is basically useless or have I missed some bigger picture..after all, it's provided by Android team carefully?
SQLite supports multiple data types; and while column types are not strictly enforced, values might be automatically converted in some cases (this is called affinity).
When your values are stored as numbers, you should access them as numbers, not as strings.
The Android database API does not allow you to use parameter types other than strings in most functions. This is a horrible design bug.
To search for a number, either use execSQL(), which allows you to use number parameters, or convert the string value back into a number:
db.delete(..., "timestamp = CAST(? AS NUMBER)",
new String[]{ String.valueOf(ts) });
The problem was my mindset that assumed that every data type would be
a general character sequence if not specified and therefore the way to
talk to database is through java.lang.String.
I think that's the real issue.
If you specify no type e.g.
CREATE TABLE mytable (col1,col2,col3)
Then according to Determination of Column Affinity(3.1) rule 3:-
3) If the declared type for a column contains the string "BLOB" or if no
type is specified then the column has affinity BLOB.
And then according to Section 3
A column with affinity BLOB does not prefer one storage class over
another and no attempt is made to coerce data from one storage class
into another.
I've personally never had an issue with delete. However I do have a tendency to always delete according to rowid.
Here's a working example usage that shows that delete isn't useless and is deleting according to a long. However the columns are all of type INTEGER :-
int pudeletes;
int sldeletes;
int rdeletes;
int pdeletes;
if(doesProductExist(productid)) {
// if not in a transaction then begin a transaction
if(!intransaction) {
db.beginTransaction();
}
String whereargs[] = { Long.toString(productid)};
// Delete ProductUsage rows that use this product
pudeletes = db.delete(
DBProductusageTableConstants.PRODUCTUSAGE_TABLE,
DBProductusageTableConstants.PRODUCTUSAGE_PRODUCTREF_COL +
" = ?",
whereargs
);
// Delete ShopList rows that use this product
sldeletes = db.delete(
DBShopListTableConstants.SHOPLIST_TABLE,
DBShopListTableConstants.SHOPLIST_PRODUCTREF_COL +
" = ?",
whereargs
);
// Delete Rules rows that use this product
rdeletes = db.delete(
DBRulesTableConstants.RULES_TABLE,
DBRulesTableConstants.RULES_PRODUCTREF_COL +
" = ?",
whereargs
);
// Delete the Product
pdeletes = db.delete(
DBProductsTableConstants.PRODUCTS_TABLE,
DBProductsTableConstants.PRODUCTS_ID_COL +
" = ?",
whereargs
);
// if originally not in a transaction then as one was started
// complete and end the transaction
if(!intransaction) {
db.setTransactionSuccessful();
db.endTransaction();
}
}

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");

SQLite bind arguments - are they restricted to the WHERE clause only?

Up to now I've been trying a lot of times to bind SQLite selection arguments in queries like the best practices suggest. But it rarely worked. I'm curious about the exact reason. Now I realized that it may be caused by the sub-queries in the FROM clause. For example...
SELECT X(result) as x, Y(result) as y, Z(result) as z FROM (select Transform(MakePointZ(?, ?, 0.0, ?), ?) as result);
and the Java code is something like...
double x, y, z;
int source, target;
final String[] selectionArgs = new String[] {
String.valueOf(x),
String.valueOf(y),
String.valueOf(z),
String.valueOf(source),
String.valueOf(target) };
Cursor c = db.rawQuery(sql, selectionArgs);
Could someone confirm if using arguments outside the WHERE clause is error or explain me where the arguments are fine to be placed?
P.s. I actually use Spatialite which is an extension to SQLite but it is based on a pure and untouched SQLite.
Parameters can be used in any place where a literal value is allowed; your SQL query itself looks fine.
However, the Android database API forces you to use string parameters, so all parameter values will be seen as strings by the database.
This will make many comparisons fails, or break other operations done on these values.
Parameters are most important for strings, where formatting problems and SQL injection attacks are possible. For plain numbers, you could just insert them directly into the SQL string.
Alternatively, convert the string values back into numbers in the database:
MakePointZ(CAST(? AS REAL), CAST(? AS REAL), ...)
Could someone confirm if using arguments outside the WHERE clause is error or explain me where the arguments are fine to be placed?
? substitution works wherever a literal is valid.
In your example code, the number of ? placeholders and bind arguments don't match and that will cause an exception.

Binding NULL arguments with queryWithFactory

I'm writing an Android app that needs to write to the SQLite database. Currently it's using rawQueryWithString to build the update query, and I'm using ? placeholders in my query combined with the selectionArgs argument to pass in the actual values.
However, sometimes I actually want to update my column (of type Date) to NULL, but if I pass in null in my selectionArgs then I get this error:
IllegalArgumentException: the bind value at index 1 is null
I can't really see how I'm supposed to make it work when the value is actually null. I guess I could pass in an empty string, which in the case of a Date column like this might just work, but suppose it was a string column and I actually did want to mean NULL in contrast to the empty string (or are they considered equivalent in SQLite?)
Here's the code:
String timestampStr = null; // Obviously not really set like this
SQLiteDatabase d = getWritableDatabase();
DBCursor c = (DBCursor) d.rawQueryWithFactory(
new DBCursor.Factory(),
"Update subject set schedulingTimestamp = ? where identifier = ?",
new String[] { timestampStr, subjId.toString() },
null);
d.close();
The column was added with the following query, so I presume it's a nullable column since I didn't specify otherwise:
ALTER TABLE subject ADD schedulingTimestamp DATE;
Wildcards are not meant to be used for inserting/updating values in SQL, AFAIK. In Android, you can use ContentValues instead in conjunction with the update() method, instead of trying to shoehorn it in the raw query method.

Issue in android sqlite rawQuery with ORDER BY?

I am trying to get data in an order by executing rawQuery with join conditions. but i am unable to get expected result.
below is my query.
String queryString = "SELECT DISTINCT B.MakeID , B.Make FROM MakeList B JOIN BodyStyle BS ON B.Makeid = BS.makeid AND BS.Year = 2012 ORDER BY B.Make ASC";
First thing is no need to write ASC coz it is bydefault ascending order to display result so write simple ORDER BY B.Make and second thing specify what you need in results and post database fields also.
Verify that your databases match. You may have db data that your SQLite Manager is accessing that may be different that what is on your Android device. That is probably causing your unexpected behavior.

Categories

Resources