OrmLite Select within select using SelectArg to escape characters - android

I'm using the QueryBuilder to construct the inner SQL that later is used in a raw SQL to avoid escaping invalid characters manually.
SelectArg friendsIN = new SelectArg(friendsUsernames);
QueryBuilder<MyObject, Integer> qb = myObjectDao.queryBuilder();
qb.selectRaw("username", "MAX(time) AS latestTime").groupBy("username").where()
.in("username", friendsIN);
String innerSelect = pq.getStatement();
friendsUsernames is defined as ArrayList<String>.
Then I use the innerSelect to build the outer select:
String select = "SELECT w.id FROM (" + innerSelect +") AS x INNER JOIN myObject AS w on w.username = x.username AND w.time = x.latestTime";
GenericRawResults<String[]> results = myObjectDao.queryRaw(select);
But, as expected, the innerString has '?' and when I call queryRaw on myObjectDao I don't get any result. I tried to give friendsUsername as an array to queryRaw:
GenericRawResults<String[]> results =
myObjectrDao.queryRaw(select,
friendsUsernames.toArray(new String[friendsUsernames.size()]));
But I get the following error:
android.database.sqlite.SQLiteBindOrColumnIndexOutOfRangeException:
bind or column index out of range: handle 0x17a22e8
Any suggestions on how to accomplish this kind of queries with OrmLite?

Yeah that's not going to work. There is only one ? in your query and yet you are trying to pass in an array of user-names. There must be a 1-to-1 correspondence between the number of ? SQL arguments and the number of arguments passed to the queryRaw(...) method exactly.
If the friendsUsernames is a fixed size then you should be able to do something like the following which will generate SQL something like "in (?, ?, ?, ?)":
List<SelectArg> friendsInList = new ArrayList<SelectArg>();
for (int i = 0; i < NUM_FRIENDS; i++) {
// it doesn't matter what the value is since you just want the ?
fieldsInList.add(new SelectArg());
}
...in("name", friendsInList);
However if the list of names is dynamic then you are going to have to do this on the fly since, again, the number of ? must match the number of arguments passed to the queryRaw(...) method exactly.

Related

android sqlite query "!=" multiple whereargs [duplicate]

I have a rather big query that is returning data when executed outside android while returning nothing when executed within android.
I split the query in several pieces and determined that the union was ok.
I tried on a smaller set of data with the same behavior.
I've tested with different hardware and API versions.
I'm using the rawQuery method with constant values.
http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#rawQuery(java.lang.String, java.lang.String[])
This query was meant to replace a FULL OUTER JOIN which is not currently supported.
SELECT IFNULL(stype, gtype) AS type, IFNULL(sdate, gdate) AS date, IFNULL(sroute, groute) AS route FROM (
SELECT sp.type AS stype, sp.date AS sdate, -1 AS gtype, gp.date AS gdate, sp.route AS sroute, gp.route AS groute
FROM Sensor_Point AS sp LEFT JOIN GPS_Point AS gp ON gp._id IS NULL AND sp.sent=0 AND sp.route=gp.route AND sp.route=1
UNION ALL
SELECT sp.type AS stype, sp.date AS sdate, -1 AS gtype, gp.date AS gdate, sp.route AS sroute, gp.route AS groute
FROM GPS_Point AS gp LEFT JOIN Sensor_Point AS sp ON sp._id IS NULL AND gp.sent=0 AND sp.route=gp.route AND gp.route=1
) WHERE route=1 ORDER BY date ASC LIMIT 255
Any hints would be greatly appreciated.
Update:
Look's like the problem is finally with the query parameters, if I set it this way:
String[] args = new String[3];
args[0] = args[1] = args[2] = "1";
Cursor data dataBase.rawQuery(SELECT_POINTS, args);
It doesn't work, while it works when hardcoding values directly in the query.
Cursor data = dataBase.rawQuery(SELECT_POINTS, null);
In the Android database API, all query parameters are strings.
(This is a horrible design mistake.)
Your query corresponds to:
... AND sp.route='1'
Try to convert the parameter strings back into a number like this:
... AND sp.route = CAST(? AS INT)
or just put the number directly into the query string.

SugarORM findWithQuery and WHERE IN

I'm working on an android app that uses sugarORM. I want to get a multiple items that match the ids in a list.
However when i call
findWithQuery(A.class, "SELECT * FROM <table> WHERE <column> in (?)", "1,2,3")
I always get an empty list(although I double checked the query with SQLite DB Browser and it worked).
Splitting this query into multiple findById seems inefficient. Any thoughts on getting WHERE IN to work using SugarORM?
After more attempts I found that there is a problem with replacing the placeholders.
Switching from:
findWithQuery(A.class, "SELECT * FROM <table> WHERE <column> in (?)", "1,2,3")
To:
findWithQuery(A.class, "SELECT * FROM <table> WHERE <column> in (1,2,3)", null)
fixes the issue.
The issue is that SQLite escapes the arguments "1,2,3" and turns it into a single value that is then used to replace the single ? placeholder in your query string. The correct way to supply multiple arguments would be to have a placeholder for every individual argument. Your original line of code would then have to change to:
findWithQuery(A.class, "SELECT * FROM <table> WHERE <column> in (?,?,?)", new String[] { "1","2","3" })
You later pointed out that the number of arguments is dynamic. This you can easily be accomplished by generating the query (the where clause in particular) at runtime based on the arguments that you want to query for.
For the most general case, it only takes a few lines of code to do so:
final String[] args = new String[] { /* ... */ };
final String query = "SELECT * FROM <table> WHERE <column> in " +
"(" + TextUtils.join(",", Collections.nCopies(args.length, "?")) + ")";
final List<A> result = A.findWithQuery(A.class, query, args);
(note that you could take a shortcut and inject the arguments directly into the query string - instead of using placeholders - but then you'll loose SQLite's built-in escaping, so I decided against that)
All that's left to do is to generate a String[] out of your arguments. A simple helper method like this should cover most scenarios:
static String[] toStringArray(Object... args) {
final String[] array = new String[args.length];
for (int i = 0; i < args.length; i++) array[i] = args[i].toString();
return array;
}
You'll probably want to add some null checks in there and potentially set up a few overloads if you plan on using primitive arrays as arguments.
Disclaimer: I typed everything straight into the browser, so no guarantees that everything works and the first try. :)

ORMLite alias in rawQuery

Is it possible to use an alias (AS) in a query for ORMLite in Android? I am trying to use it with the following code:
String query =
"SELECT *, (duration - elapsed) AS remaining FROM KitchenTimer ORDER BY remaining";
GenericRawResults<KitchenTimer> rawResults =
getHelper().getKitchenTimerDao().queryRaw(
query, getHelper().getKitchenTimerDao().getRawRowMapper());
But when this codes gets executed it gives the following error:
java.lang.IllegalArgumentException: Unknown column name 'remaining' in table kitchentimer
java.lang.IllegalArgumentException: Unknown column name 'remaining' in table kitchentimer
The raw-row-mapper associated with your KitchenTimerDao expects the results to correspond directly with the KitchenTimer entity columns. However, since you are adding your remaining column, it doesn't no where to put that result column, hence the exception. This is a raw-query so you will need to come up with your own results mapper -- you can't use the DAO's. See the docs on raw queries.
For instance, if you want to map the results into your own object Foo then you could do something like:
String query =
"SELECT *, (duration - elapsed) AS remaining FROM KitchenTimer ORDER BY remaining";
GenericRawResults<Foo> rawResults =
orderDao.queryRaw(query, new RawRowMapper<Foo>() {
public Foo mapRow(String[] columnNames, String[] resultColumns) {
// assuming 0th field is the * and 1st field is remaining
return new Foo(resultColumns[0], Integer.parseInt(resultColumns[1]));
}
});
// page through the results
for (Foo foo : rawResults) {
System.out.println("Name " + foo.name + " has " + foo.remaining + " remaining seconds");
}
rawResults.close();
I had the same problem. I wanted to get a list of objects but adding a new attribute with an alias.
To continue using the object mapper from OrmLite I used a RawRowMapper to receive columns and results. But instead of convert all columns manually I read the alias first and remove its reference in the column arrays. Then it is possible to use the OrmLite Dao mapper.
I write it in Kotlin code:
val rawResults = dao.queryRaw<Foo>(sql, RawRowMapper { columnNames, resultColumns ->
// convert array to list
val listNames = columnNames.toMutableList()
val listResults = resultColumns.toMutableList()
// get the index of the column not included in dao
val index = listNames.indexOf(ALIAS)
if (index == -1) {
// There is an error in the request because Alias was not received
return#RawRowMapper Foo()
}
// save the result
val aliasValue = listResults[index]
// remove the name and column
listNames.removeAt(index)
listResults.removeAt(index)
// map row
val foo = dao.rawRowMapper.mapRow(
listNames.toTypedArray(),
listResults.toTypedArray()
) as Foo
// add alias value. In my case I save it in the same object
// but another way is to create outside of mapping a list and
// add this value in the list if you don't want value and object together
foo.aliasValue = aliasValue
// return the generated object
return#RawRowMapper foo
})
It is not the shortest solution but for me it is very important to keep using the same mappers. It avoid errors when an attribute is added to a table and you don't remember to update the mapping.

Android development with sqlite: query result shouldn't be empty

I have a rather big query that is returning data when executed outside android while returning nothing when executed within android.
I split the query in several pieces and determined that the union was ok.
I tried on a smaller set of data with the same behavior.
I've tested with different hardware and API versions.
I'm using the rawQuery method with constant values.
http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#rawQuery(java.lang.String, java.lang.String[])
This query was meant to replace a FULL OUTER JOIN which is not currently supported.
SELECT IFNULL(stype, gtype) AS type, IFNULL(sdate, gdate) AS date, IFNULL(sroute, groute) AS route FROM (
SELECT sp.type AS stype, sp.date AS sdate, -1 AS gtype, gp.date AS gdate, sp.route AS sroute, gp.route AS groute
FROM Sensor_Point AS sp LEFT JOIN GPS_Point AS gp ON gp._id IS NULL AND sp.sent=0 AND sp.route=gp.route AND sp.route=1
UNION ALL
SELECT sp.type AS stype, sp.date AS sdate, -1 AS gtype, gp.date AS gdate, sp.route AS sroute, gp.route AS groute
FROM GPS_Point AS gp LEFT JOIN Sensor_Point AS sp ON sp._id IS NULL AND gp.sent=0 AND sp.route=gp.route AND gp.route=1
) WHERE route=1 ORDER BY date ASC LIMIT 255
Any hints would be greatly appreciated.
Update:
Look's like the problem is finally with the query parameters, if I set it this way:
String[] args = new String[3];
args[0] = args[1] = args[2] = "1";
Cursor data dataBase.rawQuery(SELECT_POINTS, args);
It doesn't work, while it works when hardcoding values directly in the query.
Cursor data = dataBase.rawQuery(SELECT_POINTS, null);
In the Android database API, all query parameters are strings.
(This is a horrible design mistake.)
Your query corresponds to:
... AND sp.route='1'
Try to convert the parameter strings back into a number like this:
... AND sp.route = CAST(? AS INT)
or just put the number directly into the query string.

ORMLite joins queries and Order by

I'm tring to make join in two tables and get all columns in both, I did this:
QueryBuilder<A, Integer> aQb = aDao.queryBuilder();
QueryBuilder<B, Integer> bQb = bDao.queryBuilder();
aQb.join(bQb).prepare();
This equates to:
SELECT 'A'.* FROM A INNER JOIN B WHERE A.id = B.id;
But I want:
SELECT * FROM A INNER JOIN B WHERE A.id = B.id;
Other problem is when taking order by a field of B, like:
aQb.orderBy(B.COLUMN, true);
I get an error saying "no table column B".
When you are using the QueryBuilder, it is expecting to return B objects. They cannot contain all of the fields from A in B. It will not flesh out foreign sub-fields if that is what you mean. That feature has not crossed the lite barrier for ORMLite.
Ordering on join-table is also not supported. You can certainly add the bQb.orderBy(B.COLUMN, true) but I don't think that will do what you want.
You can certainly use raw-queries for this although it is not optimal.
Actually, I managed to do it without writing my whole query as raw query. This way, I didn't need to replace my query builder codes (which is pretty complicated). To achieve that, I followed the following steps:
(Assuming I have two tables, my_table and my_join_table and their daos, I want to order my query on my_table by the column order_column_1 of the my_join_table)
1- Joined two query builders & used QueryBuilder.selectRaw(String... columns) method to include the original table's + the columns I want to use in foreign sort. Example:
QueryBuilder<MyJoinTable, MyJoinPK> myJoinQueryBuilder = myJoinDao.queryBuilder();
QueryBuilder<MyTable, MyPK> myQueryBuilder = myDao.queryBuilder().join(myJoinQueryBuilder).selectRaw("`my_table`.*", "`my_join_table`.`order_column` as `order_column_1`");
2- Included my order by clauses like this:
myQueryBuilder.orderByRaw("`order_column_1` ASC");
3- After setting all the select columns & order by clauses, it's time to prepare the statement:
String statement = myQueryBuilder.prepare().getStatement();
4- Get the table info from the dao:
TableInfo tableInfo = ((BaseDaoImpl) myDao).getTableInfo();
5- Created my custom column-to-object mapper which just ignores the unknown column names. We avoid the mapping error of our custon columns (order_column_1 in this case) by doing this. Example:
RawRowMapper<MyTable> mapper = new UnknownColumnIgnoringGenericRowMapper<>(tableInfo);
6- Query the table for the results:
GenericRawResults<MyTable> results = activityDao.queryRaw(statement, mapper);
7- Finally, convert the generic raw results to list:
List<MyTable> myObjects = new ArrayList<>();
for (MyTable myObject : results) {
myObjects.add(myObject);
}
Here's the custom row mapper I created by modifying (just swallowed the exception) com.j256.ormlite.stmt.RawRowMapperImpl to avoid the unknown column mapping errors. You can copy&paste this into your project:
import com.j256.ormlite.dao.RawRowMapper;
import com.j256.ormlite.field.FieldType;
import com.j256.ormlite.table.TableInfo;
import java.sql.SQLException;
public class UnknownColumnIgnoringGenericRowMapper<T, ID> implements RawRowMapper<T> {
private final TableInfo<T, ID> tableInfo;
public UnknownColumnIgnoringGenericRowMapper(TableInfo<T, ID> tableInfo) {
this.tableInfo = tableInfo;
}
public T mapRow(String[] columnNames, String[] resultColumns) throws SQLException {
// create our object
T rowObj = tableInfo.createObject();
for (int i = 0; i < columnNames.length; i++) {
// sanity check, prolly will never happen but let's be careful out there
if (i >= resultColumns.length) {
continue;
}
try {
// run through and convert each field
FieldType fieldType = tableInfo.getFieldTypeByColumnName(columnNames[i]);
Object fieldObj = fieldType.convertStringToJavaField(resultColumns[i], i);
// assign it to the row object
fieldType.assignField(rowObj, fieldObj, false, null);
} catch (IllegalArgumentException e) {
// log this or do whatever you want
}
}
return rowObj;
}
}
It's pretty hacky & seems like overkill for this operation but I definitely needed it and this method worked well.

Categories

Resources