17

In Android, android.database.sqlite.SQLiteStatement allows me to use prepared statements in SQLite to avoid injection attacks. Its execute method is suitable for create/update/delete operations, but there does not seem to be any method for queries that returns a cursor or the like.

Now in iOS I can create prepared statements of type sqlite3_stmt* and use them for queries, so I know this is not a limitation of SQLite. How can I perform queries with prepared statements in Android?

shadowmatter
  • 1,352
  • 2
  • 18
  • 30

1 Answers1

42

a prepared statement allows you to do two things

  • speed up the performance since the database does not need to parse the statement each time
  • bind & escape arguments in the statement so you are save against injection attacks

I don't know exactly where/when Androids SQLite implementation actually uses sqlite3_prepare (afiak not sqlite3_prepare_v2 - see here) but it does use it otherwise you could not get Reached MAX size for compiled-sql statement cache errors.

So if you want to query the database you have to rely on the implementation there is no way I know of to do it with SQLiteStatement.

Regarding the injection safety, every database query, insert, etc method has (sometimes alternative) versions that allow you to bind arguments.

E.g. if you want to get a Cursor out of

SELECT * FROM table WHERE column1='value1' OR column2='value2'

Cursor SQLiteDatabase#rawQuery(

  • String sql, : full SELECT statment which can include ? everywhere
  • String[] selectionArgs : list of values that replace ?, in order they appear

)

Cursor c1 = db.rawQuery(
    "SELECT * FROM table WHERE column1=? OR column2=?",
    new String[] {"value1", "value2"}
);

Cursor SQLiteDatabase#query (

  • String table, : table name, can include JOIN etc
  • String[] columns, : list of the columns required, null = *
  • String selection, : WHERE clause withouth WHERE can / should include ?
  • String[] selectionArgs, : list of values that replace ?, in order they appear
  • String groupBy, : GROUP BY clause w/o GROUP BY
  • String having, : HAVING clause w/o HAVING
  • String orderBy : ORDER BY clause w/o ORDER BY

)

Cursor c2 = db.query("table", null, 
     "column1=? OR column2=?", 
      new String[] {"value1", "value2"},
      null, null, null);

Via ContentProviders - that case is slightly different since you interact with an abstract provider, not a database. There is acutally no guarantee that there is a sqlite database backing the ContentProvider. So unless you know what columns there are / how the provider works internally you should stick to what the documentation says.

Cursor ContentResolver#query(

  • Uri uri, : an URI representing the data source (internally translated to a table)
  • String[] projection, : list of the columns required, null = *
  • String selection, : WHERE clause withouth WHERE can / should include ?
  • String[] selectionArgs, : list of values that replace ?, in order they appear
  • String sortOrder : ORDER BY clause w/o ORDER BY

)

Cursor c3 = getContentResolver().query(
     Uri.parse("content://provider/table"), null,
     "column=? OR column2=?", 
      new String[] {"value1", "value2"},
      null);

Hint: if you want to LIMIT here you can add it to the ORDER BY clause:

String sortOrder = "somecolumn LIMIT 5";

or depending on the implementation of the ContentProvider add it as a parameter to the Uri:

Uri.parse("content://provider/table?limit=5");
// or better via buildUpon()
Uri audio = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
audio.buildUpon().appendQueryParameter("limit", "5");

In all cases ? will be replaced by the escaped version of what you put in the bind argument.

? + "hack'me" = 'hack''me'

Community
  • 1
  • 1
zapl
  • 63,179
  • 10
  • 123
  • 154
  • 1
    Outstanding post, thank you, helped me out a lot. Times like these make me think we need to be able to mod up more than once. – lsl Jul 03 '12 at 07:11
  • 1
    Even if this answer is quiet old, is there a workaround to bind anything else than a String. My queries request a numeric value to be bind but every methods from SqliteDatabase use a String array. And if I create the PreparedStatement myself, I can execute the query to recover the Cursor... I can't believe there is no solution but I have'nt find it yer. – AxelH Aug 25 '15 at 07:06
  • @AxelH The solution is that everything can be expressed as string. For example use `String.valueOf(yourNumericThingHere)`. – zapl Aug 25 '15 at 07:27
  • 1
    Of course, but my query request a numerical comparaison Example "columnX < ?". columnX is a number so the comparaison won't be correct. I even tried to CAST the converted number but this won't wok because the function is converted in String by the binding... In fact ... The solution could be to add the cast in the selectionClause, this won't be pretty.... but this could work – AxelH Aug 25 '15 at 07:32
  • Using a prepared query with the whereClause 'columnX < CAST(? as decimal)' works. This could be a solution. It's like shooting myself in the foot in matter of performance... Thanks for the brainstorming. I'm looking for a solution since yestreday. – AxelH Aug 25 '15 at 07:39
  • @AxelH Ah if you have those kind of problems you could also change your column to be a numeric type (in case you have to option to change the db schema). As long as one side of a comparison is numeric (which you enfore via the cast) you'll get numeric comparison: see 3.3 https://www.sqlite.org/datatype3.html - I guess since that's the typical case nobody complains about binding the arguments as string. Ps: performance should not suffer a lot. sqlite converts between types all the time and CAST just tells it which conversion to use. – zapl Aug 25 '15 at 09:20
  • Forget it ... my comparison was in fact 'a + b < ?'. So the comparison should be cast in numeric (thanks for the intel by the way). But I forget to set the type of b ... so I was doing a comparison of two string. – AxelH Aug 25 '15 at 09:45