Merge "Revert SQLiteQueryBuilder for now."
This commit is contained in:
@@ -16,39 +16,17 @@
|
||||
|
||||
package android.database.sqlite;
|
||||
|
||||
import static android.content.ContentResolver.QUERY_ARG_SQL_GROUP_BY;
|
||||
import static android.content.ContentResolver.QUERY_ARG_SQL_HAVING;
|
||||
import static android.content.ContentResolver.QUERY_ARG_SQL_LIMIT;
|
||||
import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION;
|
||||
import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS;
|
||||
import static android.content.ContentResolver.QUERY_ARG_SQL_SORT_ORDER;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.OperationCanceledException;
|
||||
import android.provider.BaseColumns;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
|
||||
import dalvik.system.VMRuntime;
|
||||
|
||||
import libcore.util.EmptyArray;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -56,7 +34,8 @@ import java.util.regex.Pattern;
|
||||
* This is a convenience class that helps build SQL queries to be sent to
|
||||
* {@link SQLiteDatabase} objects.
|
||||
*/
|
||||
public class SQLiteQueryBuilder {
|
||||
public class SQLiteQueryBuilder
|
||||
{
|
||||
private static final String TAG = "SQLiteQueryBuilder";
|
||||
private static final Pattern sLimitPattern =
|
||||
Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
|
||||
@@ -64,7 +43,6 @@ public class SQLiteQueryBuilder {
|
||||
private Map<String, String> mProjectionMap = null;
|
||||
private String mTables = "";
|
||||
private StringBuilder mWhereClause = null; // lazily created
|
||||
private String[] mWhereArgs = EmptyArray.STRING;
|
||||
private boolean mDistinct;
|
||||
private SQLiteDatabase.CursorFactory mFactory;
|
||||
private boolean mStrict;
|
||||
@@ -104,131 +82,43 @@ public class SQLiteQueryBuilder {
|
||||
mTables = inTables;
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public @Nullable String getWhere() {
|
||||
return (mWhereClause != null) ? mWhereClause.toString() : null;
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public String[] getWhereArgs() {
|
||||
return mWhereArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a chunk to the {@code WHERE} clause of the query. All chunks
|
||||
* appended are surrounded by parenthesis and {@code AND}ed with the
|
||||
* selection passed to {@link #query}. The final {@code WHERE} clause looks
|
||||
* like:
|
||||
* Append a chunk to the WHERE clause of the query. All chunks appended are surrounded
|
||||
* by parenthesis and ANDed with the selection passed to {@link #query}. The final
|
||||
* WHERE clause looks like:
|
||||
*
|
||||
* <pre>
|
||||
* WHERE (<append chunk 1><append chunk2>) AND (<query() selection parameter>)
|
||||
* </pre>
|
||||
*
|
||||
* @param inWhere the chunk of text to append to the {@code WHERE} clause.
|
||||
* @param inWhere the chunk of text to append to the WHERE clause.
|
||||
*/
|
||||
public void appendWhere(@NonNull CharSequence inWhere) {
|
||||
appendWhere(inWhere, EmptyArray.STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a chunk to the {@code WHERE} clause of the query. All chunks
|
||||
* appended are surrounded by parenthesis and {@code AND}ed with the
|
||||
* selection passed to {@link #query}. The final {@code WHERE} clause looks
|
||||
* like:
|
||||
*
|
||||
* <pre>
|
||||
* WHERE (<append chunk 1><append chunk2>) AND (<query() selection parameter>)
|
||||
* </pre>
|
||||
*
|
||||
* @param inWhere the chunk of text to append to the {@code WHERE} clause.
|
||||
* @param inWhereArgs list of arguments to be bound to any '?' occurrences
|
||||
* in the where clause.
|
||||
*/
|
||||
public void appendWhere(@NonNull CharSequence inWhere, String... inWhereArgs) {
|
||||
public void appendWhere(CharSequence inWhere) {
|
||||
if (mWhereClause == null) {
|
||||
mWhereClause = new StringBuilder(inWhere.length() + 16);
|
||||
}
|
||||
if (mWhereClause.length() == 0) {
|
||||
mWhereClause.append('(');
|
||||
}
|
||||
mWhereClause.append(inWhere);
|
||||
mWhereArgs = ArrayUtils.concat(String.class, mWhereArgs, inWhereArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a standalone expression to the {@code WHERE} clause of this query.
|
||||
* <p>
|
||||
* This method differs from {@link #appendWhere(CharSequence)} in that it
|
||||
* automatically appends {@code AND} to any existing {@code WHERE} clause
|
||||
* already under construction before appending the given standalone
|
||||
* expression.
|
||||
* Append a chunk to the WHERE clause of the query. All chunks appended are surrounded
|
||||
* by parenthesis and ANDed with the selection passed to {@link #query}. The final
|
||||
* WHERE clause looks like:
|
||||
*
|
||||
* @param inWhere the standalone expression to append to the {@code WHERE}
|
||||
* clause. It will be wrapped in parentheses when it's appended.
|
||||
*/
|
||||
public void appendWhereExpression(@NonNull CharSequence inWhere) {
|
||||
appendWhereExpression(inWhere, EmptyArray.STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a standalone expression to the {@code WHERE} clause of this query.
|
||||
* <p>
|
||||
* This method differs from {@link #appendWhere(CharSequence)} in that it
|
||||
* automatically appends {@code AND} to any existing {@code WHERE} clause
|
||||
* already under construction before appending the given standalone
|
||||
* expression.
|
||||
* WHERE (<append chunk 1><append chunk2>) AND (<query() selection parameter>)
|
||||
*
|
||||
* @param inWhere the standalone expression to append to the {@code WHERE}
|
||||
* clause. It will be wrapped in parentheses when it's appended.
|
||||
* @param inWhereArgs list of arguments to be bound to any '?' occurrences
|
||||
* in the standalone expression.
|
||||
* @param inWhere the chunk of text to append to the WHERE clause. it will be escaped
|
||||
* to avoid SQL injection attacks
|
||||
*/
|
||||
public void appendWhereExpression(@NonNull CharSequence inWhere, String... inWhereArgs) {
|
||||
public void appendWhereEscapeString(String inWhere) {
|
||||
if (mWhereClause == null) {
|
||||
mWhereClause = new StringBuilder(inWhere.length() + 16);
|
||||
}
|
||||
if (mWhereClause.length() > 0) {
|
||||
mWhereClause.append(" AND ");
|
||||
}
|
||||
mWhereClause.append('(').append(inWhere).append(')');
|
||||
mWhereArgs = ArrayUtils.concat(String.class, mWhereArgs, inWhereArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a chunk to the {@code WHERE} clause of the query. All chunks
|
||||
* appended are surrounded by parenthesis and {@code AND}ed with the
|
||||
* selection passed to {@link #query}. The final {@code WHERE} clause looks
|
||||
* like this:
|
||||
*
|
||||
* <pre>
|
||||
* WHERE (<append chunk 1><append chunk2>) AND (<query() selection parameter>)
|
||||
* </pre>
|
||||
*
|
||||
* @param inWhere the chunk of text to append to the {@code WHERE} clause.
|
||||
* It will be escaped to avoid SQL injection attacks.
|
||||
*/
|
||||
public void appendWhereEscapeString(@NonNull String inWhere) {
|
||||
appendWhereEscapeString(inWhere, EmptyArray.STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a chunk to the {@code WHERE} clause of the query. All chunks
|
||||
* appended are surrounded by parenthesis and {@code AND}ed with the
|
||||
* selection passed to {@link #query}. The final {@code WHERE} clause looks
|
||||
* like this:
|
||||
*
|
||||
* <pre>
|
||||
* WHERE (<append chunk 1><append chunk2>) AND (<query() selection parameter>)
|
||||
* </pre>
|
||||
*
|
||||
* @param inWhere the chunk of text to append to the {@code WHERE} clause.
|
||||
* It will be escaped to avoid SQL injection attacks.
|
||||
* @param inWhereArgs list of arguments to be bound to any '?' occurrences
|
||||
* in the where clause.
|
||||
*/
|
||||
public void appendWhereEscapeString(@NonNull String inWhere, String... inWhereArgs) {
|
||||
if (mWhereClause == null) {
|
||||
mWhereClause = new StringBuilder(inWhere.length() + 16);
|
||||
if (mWhereClause.length() == 0) {
|
||||
mWhereClause.append('(');
|
||||
}
|
||||
DatabaseUtils.appendEscapedSQLString(mWhereClause, inWhere);
|
||||
mWhereArgs = ArrayUtils.concat(String.class, mWhereArgs, inWhereArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,8 +168,8 @@ public class SQLiteQueryBuilder {
|
||||
* </ul>
|
||||
* By default, this value is false.
|
||||
*/
|
||||
public void setStrict(boolean strict) {
|
||||
mStrict = strict;
|
||||
public void setStrict(boolean flag) {
|
||||
mStrict = flag;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -373,7 +263,7 @@ public class SQLiteQueryBuilder {
|
||||
* information passed into this method.
|
||||
*
|
||||
* @param db the database to query on
|
||||
* @param projection A list of which columns to return. Passing
|
||||
* @param projectionIn A list of which columns to return. Passing
|
||||
* null will return all columns, which is discouraged to prevent
|
||||
* reading data from storage that isn't going to be used.
|
||||
* @param selection A filter declaring which rows to return,
|
||||
@@ -398,14 +288,10 @@ public class SQLiteQueryBuilder {
|
||||
* @see android.content.ContentResolver#query(android.net.Uri, String[],
|
||||
* String, String[], String)
|
||||
*/
|
||||
public @Nullable Cursor query(@NonNull SQLiteDatabase db,
|
||||
@Nullable String[] projection,
|
||||
@Nullable String selection,
|
||||
@Nullable String[] selectionArgs,
|
||||
@Nullable String groupBy,
|
||||
@Nullable String having,
|
||||
@Nullable String sortOrder) {
|
||||
return query(db, projection, selection, selectionArgs, groupBy, having, sortOrder,
|
||||
public Cursor query(SQLiteDatabase db, String[] projectionIn,
|
||||
String selection, String[] selectionArgs, String groupBy,
|
||||
String having, String sortOrder) {
|
||||
return query(db, projectionIn, selection, selectionArgs, groupBy, having, sortOrder,
|
||||
null /* limit */, null /* cancellationSignal */);
|
||||
}
|
||||
|
||||
@@ -414,7 +300,7 @@ public class SQLiteQueryBuilder {
|
||||
* information passed into this method.
|
||||
*
|
||||
* @param db the database to query on
|
||||
* @param projection A list of which columns to return. Passing
|
||||
* @param projectionIn A list of which columns to return. Passing
|
||||
* null will return all columns, which is discouraged to prevent
|
||||
* reading data from storage that isn't going to be used.
|
||||
* @param selection A filter declaring which rows to return,
|
||||
@@ -441,15 +327,10 @@ public class SQLiteQueryBuilder {
|
||||
* @see android.content.ContentResolver#query(android.net.Uri, String[],
|
||||
* String, String[], String)
|
||||
*/
|
||||
public @Nullable Cursor query(@NonNull SQLiteDatabase db,
|
||||
@Nullable String[] projection,
|
||||
@Nullable String selection,
|
||||
@Nullable String[] selectionArgs,
|
||||
@Nullable String groupBy,
|
||||
@Nullable String having,
|
||||
@Nullable String sortOrder,
|
||||
@Nullable String limit) {
|
||||
return query(db, projection, selection, selectionArgs,
|
||||
public Cursor query(SQLiteDatabase db, String[] projectionIn,
|
||||
String selection, String[] selectionArgs, String groupBy,
|
||||
String having, String sortOrder, String limit) {
|
||||
return query(db, projectionIn, selection, selectionArgs,
|
||||
groupBy, having, sortOrder, limit, null);
|
||||
}
|
||||
|
||||
@@ -458,42 +339,7 @@ public class SQLiteQueryBuilder {
|
||||
* information passed into this method.
|
||||
*
|
||||
* @param db the database to query on
|
||||
* @param projection A list of which columns to return. Passing
|
||||
* null will return all columns, which is discouraged to prevent
|
||||
* reading data from storage that isn't going to be used.
|
||||
* @param selection A filter declaring which rows to return,
|
||||
* formatted as an SQL WHERE clause (excluding the WHERE
|
||||
* itself). Passing null will return all rows for the given URL.
|
||||
* @param selectionArgs 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.
|
||||
* @param sortOrder How to order the rows, formatted as an SQL
|
||||
* ORDER BY clause (excluding the ORDER BY itself). Passing null
|
||||
* will use the default sort order, which may be unordered.
|
||||
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
|
||||
* If the operation is canceled, then {@link OperationCanceledException} will be thrown
|
||||
* when the query is executed.
|
||||
* @return a cursor over the result set
|
||||
* @see android.content.ContentResolver#query(android.net.Uri, String[],
|
||||
* String, String[], String)
|
||||
*/
|
||||
public @Nullable Cursor query(@NonNull SQLiteDatabase db,
|
||||
@Nullable String[] projection,
|
||||
@Nullable String selection,
|
||||
@Nullable String[] selectionArgs,
|
||||
@Nullable String sortOrder,
|
||||
@Nullable CancellationSignal cancellationSignal) {
|
||||
return query(db, projection, selection, selectionArgs, null, null, sortOrder, null,
|
||||
cancellationSignal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a query by combining all current settings and the
|
||||
* information passed into this method.
|
||||
*
|
||||
* @param db the database to query on
|
||||
* @param projection A list of which columns to return. Passing
|
||||
* @param projectionIn A list of which columns to return. Passing
|
||||
* null will return all columns, which is discouraged to prevent
|
||||
* reading data from storage that isn't going to be used.
|
||||
* @param selection A filter declaring which rows to return,
|
||||
@@ -523,69 +369,14 @@ public class SQLiteQueryBuilder {
|
||||
* @see android.content.ContentResolver#query(android.net.Uri, String[],
|
||||
* String, String[], String)
|
||||
*/
|
||||
public @Nullable Cursor query(@NonNull SQLiteDatabase db,
|
||||
@Nullable String[] projection,
|
||||
@Nullable String selection,
|
||||
@Nullable String[] selectionArgs,
|
||||
@Nullable String groupBy,
|
||||
@Nullable String having,
|
||||
@Nullable String sortOrder,
|
||||
@Nullable String limit,
|
||||
@Nullable CancellationSignal cancellationSignal) {
|
||||
final Bundle queryArgs = new Bundle();
|
||||
maybePutString(queryArgs, QUERY_ARG_SQL_SELECTION, selection);
|
||||
maybePutStringArray(queryArgs, QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs);
|
||||
maybePutString(queryArgs, QUERY_ARG_SQL_GROUP_BY, groupBy);
|
||||
maybePutString(queryArgs, QUERY_ARG_SQL_HAVING, having);
|
||||
maybePutString(queryArgs, QUERY_ARG_SQL_SORT_ORDER, sortOrder);
|
||||
maybePutString(queryArgs, QUERY_ARG_SQL_LIMIT, limit);
|
||||
return query(db, projection, queryArgs, cancellationSignal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a query by combining all current settings and the information
|
||||
* passed into this method.
|
||||
*
|
||||
* @param db the database to query on
|
||||
* @param projection A list of which columns to return. Passing null will
|
||||
* return all columns, which is discouraged to prevent reading
|
||||
* data from storage that isn't going to be used.
|
||||
* @param queryArgs A collection of arguments for the query, defined using
|
||||
* keys such as {@link ContentResolver#QUERY_ARG_SQL_SELECTION}
|
||||
* and {@link ContentResolver#QUERY_ARG_SQL_SELECTION_ARGS}.
|
||||
* @param cancellationSignal A signal to cancel the operation in progress,
|
||||
* or null if none. If the operation is canceled, then
|
||||
* {@link OperationCanceledException} will be thrown when the
|
||||
* query is executed.
|
||||
* @return a cursor over the result set
|
||||
*/
|
||||
public Cursor query(@NonNull SQLiteDatabase db,
|
||||
@Nullable String[] projection,
|
||||
@Nullable Bundle queryArgs,
|
||||
@Nullable CancellationSignal cancellationSignal) {
|
||||
Objects.requireNonNull(db, "No database defined");
|
||||
|
||||
if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q) {
|
||||
Objects.requireNonNull(mTables, "No tables defined");
|
||||
} else if (mTables == null) {
|
||||
public Cursor query(SQLiteDatabase db, String[] projectionIn,
|
||||
String selection, String[] selectionArgs, String groupBy,
|
||||
String having, String sortOrder, String limit, CancellationSignal cancellationSignal) {
|
||||
if (mTables == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (queryArgs == null) {
|
||||
queryArgs = Bundle.EMPTY;
|
||||
}
|
||||
|
||||
// Final SQL that we will execute
|
||||
final String sql;
|
||||
|
||||
final String unwrappedSql = buildQuery(projection,
|
||||
queryArgs.getString(QUERY_ARG_SQL_SELECTION),
|
||||
queryArgs.getString(QUERY_ARG_SQL_GROUP_BY),
|
||||
queryArgs.getString(QUERY_ARG_SQL_HAVING),
|
||||
queryArgs.getString(QUERY_ARG_SQL_SORT_ORDER),
|
||||
queryArgs.getString(QUERY_ARG_SQL_LIMIT));
|
||||
|
||||
if (mStrict) {
|
||||
if (mStrict && selection != null && selection.length() > 0) {
|
||||
// Validate the user-supplied selection to detect syntactic anomalies
|
||||
// in the selection string that could indicate a SQL injection attempt.
|
||||
// The idea is to ensure that the selection clause is a valid SQL expression
|
||||
@@ -593,135 +384,24 @@ public class SQLiteQueryBuilder {
|
||||
// originally specified. An attacker cannot create an expression that
|
||||
// would escape the SQL expression while maintaining balanced parentheses
|
||||
// in both the wrapped and original forms.
|
||||
|
||||
// NOTE: The ordering of the below operations is important; we must
|
||||
// execute the wrapped query to ensure the untrusted clause has been
|
||||
// fully isolated.
|
||||
|
||||
// TODO: decode SORT ORDER and LIMIT clauses, since they can contain
|
||||
// "expr" inside that need to be validated
|
||||
|
||||
final String wrappedSql = buildQuery(projection,
|
||||
wrap(queryArgs.getString(QUERY_ARG_SQL_SELECTION)),
|
||||
queryArgs.getString(QUERY_ARG_SQL_GROUP_BY),
|
||||
queryArgs.getString(QUERY_ARG_SQL_HAVING),
|
||||
queryArgs.getString(QUERY_ARG_SQL_SORT_ORDER),
|
||||
queryArgs.getString(QUERY_ARG_SQL_LIMIT));
|
||||
|
||||
// Validate the unwrapped query
|
||||
db.validateSql(unwrappedSql, cancellationSignal);
|
||||
|
||||
// Execute wrapped query for extra protection
|
||||
sql = wrappedSql;
|
||||
} else {
|
||||
// Execute unwrapped query
|
||||
sql = unwrappedSql;
|
||||
String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy,
|
||||
having, sortOrder, limit);
|
||||
db.validateSql(sqlForValidation, cancellationSignal); // will throw if query is invalid
|
||||
}
|
||||
|
||||
final String[] sqlArgs = ArrayUtils.concat(String.class,
|
||||
queryArgs.getStringArray(QUERY_ARG_SQL_SELECTION_ARGS), mWhereArgs);
|
||||
String sql = buildQuery(
|
||||
projectionIn, selection, groupBy, having,
|
||||
sortOrder, limit);
|
||||
|
||||
if (Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs));
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "Performing query: " + sql);
|
||||
}
|
||||
|
||||
return db.rawQueryWithFactory(
|
||||
mFactory, sql, sqlArgs,
|
||||
mFactory, sql, selectionArgs,
|
||||
SQLiteDatabase.findEditTable(mTables),
|
||||
cancellationSignal); // will throw if query is invalid
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an update by combining all current settings and the
|
||||
* information passed into this method.
|
||||
*
|
||||
* @param db the database to update on
|
||||
* @param selection A filter declaring which rows to return,
|
||||
* formatted as an SQL WHERE clause (excluding the WHERE
|
||||
* itself). Passing null will return all rows for the given URL.
|
||||
* @param selectionArgs 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.
|
||||
* @return the number of rows updated
|
||||
*/
|
||||
public int update(@NonNull SQLiteDatabase db, @NonNull ContentValues values,
|
||||
@Nullable String selection, @Nullable String[] selectionArgs) {
|
||||
Objects.requireNonNull(mTables, "No tables defined");
|
||||
Objects.requireNonNull(db, "No database defined");
|
||||
Objects.requireNonNull(values, "No values defined");
|
||||
|
||||
if (mStrict) {
|
||||
// Validate the user-supplied selection to detect syntactic anomalies
|
||||
// in the selection string that could indicate a SQL injection attempt.
|
||||
// The idea is to ensure that the selection clause is a valid SQL expression
|
||||
// by compiling it twice: once wrapped in parentheses and once as
|
||||
// originally specified. An attacker cannot create an expression that
|
||||
// would escape the SQL expression while maintaining balanced parentheses
|
||||
// in both the wrapped and original forms.
|
||||
final String sql = buildUpdate(values, wrap(selection));
|
||||
db.validateSql(sql, null); // will throw if query is invalid
|
||||
}
|
||||
|
||||
final ArrayMap<String, Object> rawValues = values.getValues();
|
||||
final String[] updateArgs = new String[rawValues.size()];
|
||||
for (int i = 0; i < updateArgs.length; i++) {
|
||||
final Object arg = rawValues.valueAt(i);
|
||||
updateArgs[i] = (arg != null) ? arg.toString() : null;
|
||||
}
|
||||
|
||||
final String sql = buildUpdate(values, selection);
|
||||
final String[] sqlArgs = ArrayUtils.concat(String.class, updateArgs,
|
||||
ArrayUtils.concat(String.class, selectionArgs, mWhereArgs));
|
||||
|
||||
if (Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs));
|
||||
}
|
||||
|
||||
return db.executeSql(sql, sqlArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a delete by combining all current settings and the
|
||||
* information passed into this method.
|
||||
*
|
||||
* @param db the database to delete on
|
||||
* @param selection A filter declaring which rows to return,
|
||||
* formatted as an SQL WHERE clause (excluding the WHERE
|
||||
* itself). Passing null will return all rows for the given URL.
|
||||
* @param selectionArgs 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.
|
||||
* @return the number of rows deleted
|
||||
*/
|
||||
public int delete(@NonNull SQLiteDatabase db, @Nullable String selection,
|
||||
@Nullable String[] selectionArgs) {
|
||||
Objects.requireNonNull(mTables, "No tables defined");
|
||||
Objects.requireNonNull(db, "No database defined");
|
||||
|
||||
if (mStrict) {
|
||||
// Validate the user-supplied selection to detect syntactic anomalies
|
||||
// in the selection string that could indicate a SQL injection attempt.
|
||||
// The idea is to ensure that the selection clause is a valid SQL expression
|
||||
// by compiling it twice: once wrapped in parentheses and once as
|
||||
// originally specified. An attacker cannot create an expression that
|
||||
// would escape the SQL expression while maintaining balanced parentheses
|
||||
// in both the wrapped and original forms.
|
||||
final String sql = buildDelete(wrap(selection));
|
||||
db.validateSql(sql, null); // will throw if query is invalid
|
||||
}
|
||||
|
||||
final String sql = buildDelete(selection);
|
||||
final String[] sqlArgs = ArrayUtils.concat(String.class, selectionArgs, mWhereArgs);
|
||||
|
||||
if (Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs));
|
||||
}
|
||||
|
||||
return db.executeSql(sql, sqlArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a SELECT statement suitable for use in a group of
|
||||
* SELECT statements that will be joined through UNION operators
|
||||
@@ -754,10 +434,28 @@ public class SQLiteQueryBuilder {
|
||||
String[] projectionIn, String selection, String groupBy,
|
||||
String having, String sortOrder, String limit) {
|
||||
String[] projection = computeProjection(projectionIn);
|
||||
String where = computeWhere(selection);
|
||||
|
||||
StringBuilder where = new StringBuilder();
|
||||
boolean hasBaseWhereClause = mWhereClause != null && mWhereClause.length() > 0;
|
||||
|
||||
if (hasBaseWhereClause) {
|
||||
where.append(mWhereClause.toString());
|
||||
where.append(')');
|
||||
}
|
||||
|
||||
// Tack on the user's selection, if present.
|
||||
if (selection != null && selection.length() > 0) {
|
||||
if (hasBaseWhereClause) {
|
||||
where.append(" AND ");
|
||||
}
|
||||
|
||||
where.append('(');
|
||||
where.append(selection);
|
||||
where.append(')');
|
||||
}
|
||||
|
||||
return buildQueryString(
|
||||
mDistinct, mTables, projection, where,
|
||||
mDistinct, mTables, projection, where.toString(),
|
||||
groupBy, having, sortOrder, limit);
|
||||
}
|
||||
|
||||
@@ -774,42 +472,6 @@ public class SQLiteQueryBuilder {
|
||||
return buildQuery(projectionIn, selection, groupBy, having, sortOrder, limit);
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public String buildUpdate(ContentValues values, String selection) {
|
||||
if (values == null || values.isEmpty()) {
|
||||
throw new IllegalArgumentException("Empty values");
|
||||
}
|
||||
|
||||
StringBuilder sql = new StringBuilder(120);
|
||||
sql.append("UPDATE ");
|
||||
sql.append(mTables);
|
||||
sql.append(" SET ");
|
||||
|
||||
final ArrayMap<String, Object> rawValues = values.getValues();
|
||||
for (int i = 0; i < rawValues.size(); i++) {
|
||||
if (i > 0) {
|
||||
sql.append(',');
|
||||
}
|
||||
sql.append(rawValues.keyAt(i));
|
||||
sql.append("=?");
|
||||
}
|
||||
|
||||
final String where = computeWhere(selection);
|
||||
appendClause(sql, " WHERE ", where);
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public String buildDelete(String selection) {
|
||||
StringBuilder sql = new StringBuilder(120);
|
||||
sql.append("DELETE FROM ");
|
||||
sql.append(mTables);
|
||||
|
||||
final String where = computeWhere(selection);
|
||||
appendClause(sql, " WHERE ", where);
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a SELECT statement suitable for use in a group of
|
||||
* SELECT statements that will be joined through UNION operators
|
||||
@@ -934,7 +596,7 @@ public class SQLiteQueryBuilder {
|
||||
return query.toString();
|
||||
}
|
||||
|
||||
private @Nullable String[] computeProjection(@Nullable String[] projectionIn) {
|
||||
private String[] computeProjection(String[] projectionIn) {
|
||||
if (projectionIn != null && projectionIn.length > 0) {
|
||||
if (mProjectionMap != null) {
|
||||
String[] projection = new String[projectionIn.length];
|
||||
@@ -957,7 +619,7 @@ public class SQLiteQueryBuilder {
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Invalid column "
|
||||
+ projectionIn[i] + " from tables " + mTables);
|
||||
+ projectionIn[i]);
|
||||
}
|
||||
return projection;
|
||||
} else {
|
||||
@@ -983,53 +645,4 @@ public class SQLiteQueryBuilder {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private @NonNull String computeWhere(@Nullable String selection) {
|
||||
final boolean hasUser = selection != null && selection.length() > 0;
|
||||
final boolean hasInternal = mWhereClause != null && mWhereClause.length() > 0;
|
||||
|
||||
if (hasUser || hasInternal) {
|
||||
final StringBuilder where = new StringBuilder();
|
||||
if (hasUser) {
|
||||
where.append('(').append(selection).append(')');
|
||||
}
|
||||
if (hasUser && hasInternal) {
|
||||
where.append(" AND ");
|
||||
}
|
||||
if (hasInternal) {
|
||||
where.append('(').append(mWhereClause.toString()).append(')');
|
||||
}
|
||||
return where.toString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap given argument in parenthesis, unless it's {@code null} or
|
||||
* {@code ()}, in which case return it verbatim.
|
||||
*/
|
||||
private @Nullable String wrap(@Nullable String arg) {
|
||||
if (arg == null) {
|
||||
return null;
|
||||
} else if (arg.equals("")) {
|
||||
return arg;
|
||||
} else {
|
||||
return "(" + arg + ")";
|
||||
}
|
||||
}
|
||||
|
||||
private static void maybePutString(@NonNull Bundle bundle, @NonNull String key,
|
||||
@Nullable String value) {
|
||||
if (value != null) {
|
||||
bundle.putString(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void maybePutStringArray(@NonNull Bundle bundle, @NonNull String key,
|
||||
@Nullable String[] value) {
|
||||
if (value != null) {
|
||||
bundle.putStringArray(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1036
core/java/android/database/sqlite/SQLiteStatementBuilder.java
Normal file
1036
core/java/android/database/sqlite/SQLiteStatementBuilder.java
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user