Related
I'm attempting to do the following SQL query within Android:
String names = "'name1', 'name2"; // in the code this is dynamically generated
String query = "SELECT * FROM table WHERE name IN (?)";
Cursor cursor = mDb.rawQuery(query, new String[]{names});
However, Android does not replace the question mark with the correct values. I could do the following, however, this does not protect against SQL injection:
String query = "SELECT * FROM table WHERE name IN (" + names + ")";
Cursor cursor = mDb.rawQuery(query, null);
How can I get around this issue and be able to use the IN clause?
A string of the form "?, ?, ..., ?" can be a dynamically created string and safely put into the original SQL query (because it is a restricted form that does not contain external data) and then the placeholders can be used as normal.
Consider a function String makePlaceholders(int len) which returns len question-marks separated with commas, then:
String[] names = { "name1", "name2" }; // do whatever is needed first
String query = "SELECT * FROM table"
+ " WHERE name IN (" + makePlaceholders(names.length) + ")";
Cursor cursor = mDb.rawQuery(query, names);
Just make sure to pass exactly as many values as places. The default maximum limit of host parameters in SQLite is 999 - at least in a normal build, not sure about Android :)
Here is one implementation:
String makePlaceholders(int len) {
if (len < 1) {
// It will lead to an invalid query anyway ..
throw new RuntimeException("No placeholders");
} else {
StringBuilder sb = new StringBuilder(len * 2 - 1);
sb.append("?");
for (int i = 1; i < len; i++) {
sb.append(",?");
}
return sb.toString();
}
}
Short example, based on answer of user166390:
public Cursor selectRowsByCodes(String[] codes) {
try {
SQLiteDatabase db = getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String[] sqlSelect = {COLUMN_NAME_ID, COLUMN_NAME_CODE, COLUMN_NAME_NAME, COLUMN_NAME_PURPOSE, COLUMN_NAME_STATUS};
String sqlTables = "Enumbers";
qb.setTables(sqlTables);
Cursor c = qb.query(db, sqlSelect, COLUMN_NAME_CODE+" IN (" +
TextUtils.join(",", Collections.nCopies(codes.length, "?")) +
")", codes,
null, null, null);
c.moveToFirst();
return c;
} catch (Exception e) {
Log.e(this.getClass().getCanonicalName(), e.getMessage() + e.getStackTrace().toString());
}
return null;
}
Sadly there's no way of doing that (obviously 'name1', 'name2' is not a single value and can therefore not be used in a prepared statement).
So you will have to lower your sights (e.g. by creating very specific, not reusable queries like WHERE name IN (?, ?, ?)) or not using stored procedures and try to prevent SQL injections with some other techniques...
As suggest in accepted answer but without using custom function to generate comma-separated '?'. Please check code below.
String[] names = { "name1", "name2" }; // do whatever is needed first
String query = "SELECT * FROM table"
+ " WHERE name IN (" + TextUtils.join(",", Collections.nCopies(names.length, "?")) + ")";
Cursor cursor = mDb.rawQuery(query, names);
You can use TextUtils.join(",", parameters) to take advantage of sqlite binding parameters, where parameters is a list with "?" placeholders and the result string is something like "?,?,..,?".
Here is a little example:
Set<Integer> positionsSet = membersListCursorAdapter.getCurrentCheckedPosition();
List<String> ids = new ArrayList<>();
List<String> parameters = new ArrayList<>();
for (Integer position : positionsSet) {
ids.add(String.valueOf(membersListCursorAdapter.getItemId(position)));
parameters.add("?");
}
getActivity().getContentResolver().delete(
SharedUserTable.CONTENT_URI,
SharedUserTable._ID + " in (" + TextUtils.join(",", parameters) + ")",
ids.toArray(new String[ids.size()])
);
Actually you could use android's native way of querying instead of rawQuery:
public int updateContactsByServerIds(ArrayList<Integer> serverIds, final long groupId) {
final int serverIdsCount = serverIds.size()-1; // 0 for one and only id, -1 if empty list
final StringBuilder ids = new StringBuilder("");
if (serverIdsCount>0) // ambiguous "if" but -1 leads to endless cycle
for (int i = 0; i < serverIdsCount; i++)
ids.append(String.valueOf(serverIds.get(i))).append(",");
// add last (or one and only) id without comma
ids.append(String.valueOf(serverIds.get(serverIdsCount))); //-1 throws exception
// remove last comma
Log.i(this,"whereIdsList: "+ids);
final String whereClause = Tables.Contacts.USER_ID + " IN ("+ids+")";
final ContentValues args = new ContentValues();
args.put(Tables.Contacts.GROUP_ID, groupId);
int numberOfRowsAffected = 0;
SQLiteDatabase db = dbAdapter.getWritableDatabase());
try {
numberOfRowsAffected = db.update(Tables.Contacts.TABLE_NAME, args, whereClause, null);
} catch (Exception e) {
e.printStackTrace();
}
dbAdapter.closeWritableDB();
Log.d(TAG, "updateContactsByServerIds() numberOfRowsAffected: " + numberOfRowsAffected);
return numberOfRowsAffected;
}
This is not Valid
String subQuery = "SELECT _id FROM tnl_partofspeech where part_of_speech = 'noun'";
Cursor cursor = SQLDataBase.rawQuery(
"SELECT * FROM table_main where part_of_speech_id IN (" +
"?" +
")",
new String[]{subQuery}););
This is Valid
String subQuery = "SELECT _id FROM tbl_partofspeech where part_of_speech = 'noun'";
Cursor cursor = SQLDataBase.rawQuery(
"SELECT * FROM table_main where part_of_speech_id IN (" +
subQuery +
")",
null);
Using ContentResolver
String subQuery = "SELECT _id FROM tbl_partofspeech where part_of_speech = 'noun' ";
final String[] selectionArgs = new String[]{"1","2"};
final String selection = "_id IN ( ?,? )) AND part_of_speech_id IN (( " + subQuery + ") ";
SQLiteDatabase SQLDataBase = DataBaseManage.getReadableDatabase(this);
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables("tableName");
Cursor cursor = queryBuilder.query(SQLDataBase, null, selection, selectionArgs, null,
null, null);
In Kotlin you can use joinToString
val query = "SELECT * FROM table WHERE name IN (${names.joinToString(separator = ",") { "?" }})"
val cursor = mDb.rawQuery(query, names.toTypedArray())
I use the Stream API for this:
final String[] args = Stream.of("some","data","for","args").toArray(String[]::new);
final String placeholders = Stream.generate(() -> "?").limit(args.length).collect(Collectors.joining(","));
final String selection = String.format("SELECT * FROM table WHERE name IN(%s)", placeholders);
db.rawQuery(selection, args);
I have researched a handful of other forums with a similar topic and I have yet to find an answer to this frustrating issue. I am trying to use an array to check if a column in my database has one of the multiple values in the array. My cursor is as follows:
public Cursor notificationQuery(String geoIds) {
Log.e("STRINGS", geoIds);
return mDb.query(Constants.TABLE_POI_NAME,
new String[]{Constants.TABLE_COLUMN_ID, Constants.TABLE_COLUMN_POI_NAME,
Constants.TABLE_COLUMN_LATITUDE, Constants.TABLE_COLUMN_LONGITUDE,
Constants.TABLE_COLUMN_GEO_ID},
Constants.TABLE_COLUMN_GEO_ID + " IN (?)",
new String[]{geoIds},
null, null, null, null);
}
geoIds is currently an array of two values which has been converted into a string. The logged value of that string is below:
21007b0f-6b20-4eff-9a76-b412db8daa2e,26c695d6-6cb4-4c74-9933-281813a06fd9
Those are to separate Id values separated by a comma. When I test each one individually using "= ?" instead of "IN (?)" I get a proper match with the database and my cursor returns a value. However, when combined my cursor returns nothing when it should return two rows from the database. Please help me solve this issue! Thanks!
Consider a function String makePlaceholders(int len) which returns len question-marks separated with commas, then:
public Cursor notificationQuery(String geoId1,String geoId2) {
//assume we split this geoIds to 2 different values. you need to have 2 strings no 1
String[] ids = { geoId1, geoId2 }; // do whatever is needed first depends on your inputs
String query = "SELECT * FROM "+ Constants.TABLE_POI_NAME + " WHERE "+
Constants.TABLE_COLUMN_GEO_ID +" IN (" + makePlaceholders(names.length) + ")";
return mDb.rawQuery(query, ids); // ids is the table above
}
Here is one implementation of makePlaceholders(int len):
String makePlaceholders(int len) {
if (len < 1) {
// It will lead to an invalid query anyway ..
throw new RuntimeException("No placeholders");
} else {
StringBuilder sb = new StringBuilder(len * 2 - 1);
sb.append("?");
for (int i = 1; i < len; i++) {
sb.append(",?");
}
return sb.toString();
}
}
Or more simply, use the geoIds variable directly:
public Cursor notificationQuery(String geoIds) {
Log.e("STRINGS", geoIds);
return mDb.query(Constants.TABLE_POI_NAME,
new String[]{Constants.TABLE_COLUMN_ID, Constants.TABLE_COLUMN_POI_NAME,
Constants.TABLE_COLUMN_LATITUDE, Constants.TABLE_COLUMN_LONGITUDE,
Constants.TABLE_COLUMN_GEO_ID},
Constants.TABLE_COLUMN_GEO_ID + " IN (" + geoIds + ")",
null, null, null, null, null);
}
This approach is less secure but will likely give you the result you expect.
I'm attempting to do the following SQL query within Android:
String names = "'name1', 'name2"; // in the code this is dynamically generated
String query = "SELECT * FROM table WHERE name IN (?)";
Cursor cursor = mDb.rawQuery(query, new String[]{names});
However, Android does not replace the question mark with the correct values. I could do the following, however, this does not protect against SQL injection:
String query = "SELECT * FROM table WHERE name IN (" + names + ")";
Cursor cursor = mDb.rawQuery(query, null);
How can I get around this issue and be able to use the IN clause?
A string of the form "?, ?, ..., ?" can be a dynamically created string and safely put into the original SQL query (because it is a restricted form that does not contain external data) and then the placeholders can be used as normal.
Consider a function String makePlaceholders(int len) which returns len question-marks separated with commas, then:
String[] names = { "name1", "name2" }; // do whatever is needed first
String query = "SELECT * FROM table"
+ " WHERE name IN (" + makePlaceholders(names.length) + ")";
Cursor cursor = mDb.rawQuery(query, names);
Just make sure to pass exactly as many values as places. The default maximum limit of host parameters in SQLite is 999 - at least in a normal build, not sure about Android :)
Here is one implementation:
String makePlaceholders(int len) {
if (len < 1) {
// It will lead to an invalid query anyway ..
throw new RuntimeException("No placeholders");
} else {
StringBuilder sb = new StringBuilder(len * 2 - 1);
sb.append("?");
for (int i = 1; i < len; i++) {
sb.append(",?");
}
return sb.toString();
}
}
Short example, based on answer of user166390:
public Cursor selectRowsByCodes(String[] codes) {
try {
SQLiteDatabase db = getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String[] sqlSelect = {COLUMN_NAME_ID, COLUMN_NAME_CODE, COLUMN_NAME_NAME, COLUMN_NAME_PURPOSE, COLUMN_NAME_STATUS};
String sqlTables = "Enumbers";
qb.setTables(sqlTables);
Cursor c = qb.query(db, sqlSelect, COLUMN_NAME_CODE+" IN (" +
TextUtils.join(",", Collections.nCopies(codes.length, "?")) +
")", codes,
null, null, null);
c.moveToFirst();
return c;
} catch (Exception e) {
Log.e(this.getClass().getCanonicalName(), e.getMessage() + e.getStackTrace().toString());
}
return null;
}
Sadly there's no way of doing that (obviously 'name1', 'name2' is not a single value and can therefore not be used in a prepared statement).
So you will have to lower your sights (e.g. by creating very specific, not reusable queries like WHERE name IN (?, ?, ?)) or not using stored procedures and try to prevent SQL injections with some other techniques...
As suggest in accepted answer but without using custom function to generate comma-separated '?'. Please check code below.
String[] names = { "name1", "name2" }; // do whatever is needed first
String query = "SELECT * FROM table"
+ " WHERE name IN (" + TextUtils.join(",", Collections.nCopies(names.length, "?")) + ")";
Cursor cursor = mDb.rawQuery(query, names);
You can use TextUtils.join(",", parameters) to take advantage of sqlite binding parameters, where parameters is a list with "?" placeholders and the result string is something like "?,?,..,?".
Here is a little example:
Set<Integer> positionsSet = membersListCursorAdapter.getCurrentCheckedPosition();
List<String> ids = new ArrayList<>();
List<String> parameters = new ArrayList<>();
for (Integer position : positionsSet) {
ids.add(String.valueOf(membersListCursorAdapter.getItemId(position)));
parameters.add("?");
}
getActivity().getContentResolver().delete(
SharedUserTable.CONTENT_URI,
SharedUserTable._ID + " in (" + TextUtils.join(",", parameters) + ")",
ids.toArray(new String[ids.size()])
);
Actually you could use android's native way of querying instead of rawQuery:
public int updateContactsByServerIds(ArrayList<Integer> serverIds, final long groupId) {
final int serverIdsCount = serverIds.size()-1; // 0 for one and only id, -1 if empty list
final StringBuilder ids = new StringBuilder("");
if (serverIdsCount>0) // ambiguous "if" but -1 leads to endless cycle
for (int i = 0; i < serverIdsCount; i++)
ids.append(String.valueOf(serverIds.get(i))).append(",");
// add last (or one and only) id without comma
ids.append(String.valueOf(serverIds.get(serverIdsCount))); //-1 throws exception
// remove last comma
Log.i(this,"whereIdsList: "+ids);
final String whereClause = Tables.Contacts.USER_ID + " IN ("+ids+")";
final ContentValues args = new ContentValues();
args.put(Tables.Contacts.GROUP_ID, groupId);
int numberOfRowsAffected = 0;
SQLiteDatabase db = dbAdapter.getWritableDatabase());
try {
numberOfRowsAffected = db.update(Tables.Contacts.TABLE_NAME, args, whereClause, null);
} catch (Exception e) {
e.printStackTrace();
}
dbAdapter.closeWritableDB();
Log.d(TAG, "updateContactsByServerIds() numberOfRowsAffected: " + numberOfRowsAffected);
return numberOfRowsAffected;
}
This is not Valid
String subQuery = "SELECT _id FROM tnl_partofspeech where part_of_speech = 'noun'";
Cursor cursor = SQLDataBase.rawQuery(
"SELECT * FROM table_main where part_of_speech_id IN (" +
"?" +
")",
new String[]{subQuery}););
This is Valid
String subQuery = "SELECT _id FROM tbl_partofspeech where part_of_speech = 'noun'";
Cursor cursor = SQLDataBase.rawQuery(
"SELECT * FROM table_main where part_of_speech_id IN (" +
subQuery +
")",
null);
Using ContentResolver
String subQuery = "SELECT _id FROM tbl_partofspeech where part_of_speech = 'noun' ";
final String[] selectionArgs = new String[]{"1","2"};
final String selection = "_id IN ( ?,? )) AND part_of_speech_id IN (( " + subQuery + ") ";
SQLiteDatabase SQLDataBase = DataBaseManage.getReadableDatabase(this);
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables("tableName");
Cursor cursor = queryBuilder.query(SQLDataBase, null, selection, selectionArgs, null,
null, null);
In Kotlin you can use joinToString
val query = "SELECT * FROM table WHERE name IN (${names.joinToString(separator = ",") { "?" }})"
val cursor = mDb.rawQuery(query, names.toTypedArray())
I use the Stream API for this:
final String[] args = Stream.of("some","data","for","args").toArray(String[]::new);
final String placeholders = Stream.generate(() -> "?").limit(args.length).collect(Collectors.joining(","));
final String selection = String.format("SELECT * FROM table WHERE name IN(%s)", placeholders);
db.rawQuery(selection, args);
I'm attempting to do the following SQL query within Android:
String names = "'name1', 'name2"; // in the code this is dynamically generated
String query = "SELECT * FROM table WHERE name IN (?)";
Cursor cursor = mDb.rawQuery(query, new String[]{names});
However, Android does not replace the question mark with the correct values. I could do the following, however, this does not protect against SQL injection:
String query = "SELECT * FROM table WHERE name IN (" + names + ")";
Cursor cursor = mDb.rawQuery(query, null);
How can I get around this issue and be able to use the IN clause?
A string of the form "?, ?, ..., ?" can be a dynamically created string and safely put into the original SQL query (because it is a restricted form that does not contain external data) and then the placeholders can be used as normal.
Consider a function String makePlaceholders(int len) which returns len question-marks separated with commas, then:
String[] names = { "name1", "name2" }; // do whatever is needed first
String query = "SELECT * FROM table"
+ " WHERE name IN (" + makePlaceholders(names.length) + ")";
Cursor cursor = mDb.rawQuery(query, names);
Just make sure to pass exactly as many values as places. The default maximum limit of host parameters in SQLite is 999 - at least in a normal build, not sure about Android :)
Here is one implementation:
String makePlaceholders(int len) {
if (len < 1) {
// It will lead to an invalid query anyway ..
throw new RuntimeException("No placeholders");
} else {
StringBuilder sb = new StringBuilder(len * 2 - 1);
sb.append("?");
for (int i = 1; i < len; i++) {
sb.append(",?");
}
return sb.toString();
}
}
Short example, based on answer of user166390:
public Cursor selectRowsByCodes(String[] codes) {
try {
SQLiteDatabase db = getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String[] sqlSelect = {COLUMN_NAME_ID, COLUMN_NAME_CODE, COLUMN_NAME_NAME, COLUMN_NAME_PURPOSE, COLUMN_NAME_STATUS};
String sqlTables = "Enumbers";
qb.setTables(sqlTables);
Cursor c = qb.query(db, sqlSelect, COLUMN_NAME_CODE+" IN (" +
TextUtils.join(",", Collections.nCopies(codes.length, "?")) +
")", codes,
null, null, null);
c.moveToFirst();
return c;
} catch (Exception e) {
Log.e(this.getClass().getCanonicalName(), e.getMessage() + e.getStackTrace().toString());
}
return null;
}
Sadly there's no way of doing that (obviously 'name1', 'name2' is not a single value and can therefore not be used in a prepared statement).
So you will have to lower your sights (e.g. by creating very specific, not reusable queries like WHERE name IN (?, ?, ?)) or not using stored procedures and try to prevent SQL injections with some other techniques...
As suggest in accepted answer but without using custom function to generate comma-separated '?'. Please check code below.
String[] names = { "name1", "name2" }; // do whatever is needed first
String query = "SELECT * FROM table"
+ " WHERE name IN (" + TextUtils.join(",", Collections.nCopies(names.length, "?")) + ")";
Cursor cursor = mDb.rawQuery(query, names);
You can use TextUtils.join(",", parameters) to take advantage of sqlite binding parameters, where parameters is a list with "?" placeholders and the result string is something like "?,?,..,?".
Here is a little example:
Set<Integer> positionsSet = membersListCursorAdapter.getCurrentCheckedPosition();
List<String> ids = new ArrayList<>();
List<String> parameters = new ArrayList<>();
for (Integer position : positionsSet) {
ids.add(String.valueOf(membersListCursorAdapter.getItemId(position)));
parameters.add("?");
}
getActivity().getContentResolver().delete(
SharedUserTable.CONTENT_URI,
SharedUserTable._ID + " in (" + TextUtils.join(",", parameters) + ")",
ids.toArray(new String[ids.size()])
);
Actually you could use android's native way of querying instead of rawQuery:
public int updateContactsByServerIds(ArrayList<Integer> serverIds, final long groupId) {
final int serverIdsCount = serverIds.size()-1; // 0 for one and only id, -1 if empty list
final StringBuilder ids = new StringBuilder("");
if (serverIdsCount>0) // ambiguous "if" but -1 leads to endless cycle
for (int i = 0; i < serverIdsCount; i++)
ids.append(String.valueOf(serverIds.get(i))).append(",");
// add last (or one and only) id without comma
ids.append(String.valueOf(serverIds.get(serverIdsCount))); //-1 throws exception
// remove last comma
Log.i(this,"whereIdsList: "+ids);
final String whereClause = Tables.Contacts.USER_ID + " IN ("+ids+")";
final ContentValues args = new ContentValues();
args.put(Tables.Contacts.GROUP_ID, groupId);
int numberOfRowsAffected = 0;
SQLiteDatabase db = dbAdapter.getWritableDatabase());
try {
numberOfRowsAffected = db.update(Tables.Contacts.TABLE_NAME, args, whereClause, null);
} catch (Exception e) {
e.printStackTrace();
}
dbAdapter.closeWritableDB();
Log.d(TAG, "updateContactsByServerIds() numberOfRowsAffected: " + numberOfRowsAffected);
return numberOfRowsAffected;
}
This is not Valid
String subQuery = "SELECT _id FROM tnl_partofspeech where part_of_speech = 'noun'";
Cursor cursor = SQLDataBase.rawQuery(
"SELECT * FROM table_main where part_of_speech_id IN (" +
"?" +
")",
new String[]{subQuery}););
This is Valid
String subQuery = "SELECT _id FROM tbl_partofspeech where part_of_speech = 'noun'";
Cursor cursor = SQLDataBase.rawQuery(
"SELECT * FROM table_main where part_of_speech_id IN (" +
subQuery +
")",
null);
Using ContentResolver
String subQuery = "SELECT _id FROM tbl_partofspeech where part_of_speech = 'noun' ";
final String[] selectionArgs = new String[]{"1","2"};
final String selection = "_id IN ( ?,? )) AND part_of_speech_id IN (( " + subQuery + ") ";
SQLiteDatabase SQLDataBase = DataBaseManage.getReadableDatabase(this);
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables("tableName");
Cursor cursor = queryBuilder.query(SQLDataBase, null, selection, selectionArgs, null,
null, null);
In Kotlin you can use joinToString
val query = "SELECT * FROM table WHERE name IN (${names.joinToString(separator = ",") { "?" }})"
val cursor = mDb.rawQuery(query, names.toTypedArray())
I use the Stream API for this:
final String[] args = Stream.of("some","data","for","args").toArray(String[]::new);
final String placeholders = Stream.generate(() -> "?").limit(args.length).collect(Collectors.joining(","));
final String selection = String.format("SELECT * FROM table WHERE name IN(%s)", placeholders);
db.rawQuery(selection, args);
Is possible to use an IN clause for a content provider?
I am currently using
cursor = contentResolver.query(CONTENT_URI, PROJECTION, "field IN (?)", new String[] { argsBuilder.toString() }, null);
If i remove the (?) and just use ? I get an error.
I get 0 count in my cursor.
If I type manually and execute the query in sqlite3 it works.
Help?
When using the IN operator, you need to have one ? separated by a comma per argument you provide in your selectionArgs array. E.g.:
String[] selectionArgs = {"red", "black"};
String selection = "color IN (?, ?)";
The following picks the right count and the proper args:
int argcount = 2; // number of IN arguments
String[] args = new String[]{ 1, 2 };
StringBuilder inList = new StringBuilder(argcount * 2);
for (int i = 0; i < argcount; i++) {
if(i > 0) {
inList.append(",");
}
inList.append("?");
}
cursor = contentResolver.query(
CONTENT_URI,
PROJECTION,
"field IN (" + inList.toString() + ")",
args,
null);
If your arguments are numbers only, this works as well:
Iterable args = ...; // any array, list, set, ...
cursor = contentResolver.query(CONTENT_URI, PROJECTION, "field IN (" + TextUtils.join(",", args) + ")", null, null);
This is a code snippet from my application built in Kotlin, the params array is for illustration purposes only as the parameters come from elsewhere in the code.
The problem with most of the proposed solutions is that they result in the loss of the SQL injection prevention that the use of placeholders provides, so I only use an expression that returns an array of "?" and whose quantity matches the array of provided parameters and then becomes a comma separated String.
That way, the delivered string is a string of placeholders and not the parameters directly.
Sorry for my first answer write in spanish.
var params = arrayOf("1789","1787","1694","1784")
applicationContext.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
null,
"_ID IN (" + Array(params.size) { "?" }.joinToString() + ")",
params,
null
)