Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0da04839 authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Jeff Sharkey
Browse files

Support for appending "standalone" WHERE chunks.

The existing appendWhere() methods aren't very friendly for
developers, since they require manual tracking of state to decide if
subsequent standalone chunks should be prefixed with "AND".

While it's tempting to offer direct argument binding on the builder
class, we can't really deliver on that API in a secure way, so instead
add separate bindSelection() method which explicitly burns arguments
into a standalone selection string, which can then be appended to
the builder.

This was the last piece of new functionality being used by
SQLiteStatementBuilder, so we can delete that class and migrate
users back to SQLiteQueryBuilder.

Bug: 111268862
Test: atest frameworks/base/core/tests/coretests/src/android/database/DatabaseUtilsTest.java
Test: atest frameworks/base/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
Test: atest cts/tests/tests/provider/src/android/provider/cts/MediaStore*
Test: atest cts/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java
Merged-In: I418f24338c90bae8a9dad473fa76329cea00a8c5
Change-Id: I418f24338c90bae8a9dad473fa76329cea00a8c5
parent 8a634372
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -12669,6 +12669,7 @@ package android.database.sqlite {
    method public static void appendColumns(java.lang.StringBuilder, java.lang.String[]);
    method public void appendWhere(java.lang.CharSequence);
    method public void appendWhereEscapeString(java.lang.String);
    method public void appendWhereStandalone(java.lang.CharSequence);
    method public java.lang.String buildQuery(java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String);
    method public deprecated java.lang.String buildQuery(java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String);
    method public static java.lang.String buildQueryString(boolean, java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String);
+89 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.database;

import android.annotation.UnsupportedAppUsage;
import android.annotation.Nullable;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
@@ -35,6 +36,8 @@ import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;

import com.android.internal.util.ArrayUtils;

import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.text.Collator;
@@ -216,6 +219,92 @@ public class DatabaseUtils {
        }
    }

    /**
     * Bind the given selection with the given selection arguments.
     * <p>
     * Internally assumes that '?' is only ever used for arguments, and doesn't
     * appear as a literal or escaped value.
     * <p>
     * This method is typically useful for trusted code that needs to cook up a
     * fully-bound selection.
     *
     * @hide
     */
    public static @Nullable String bindSelection(@Nullable String selection,
            @Nullable Object... selectionArgs) {
        if (selection == null) return null;
        // If no arguments provided, so we can't bind anything
        if (ArrayUtils.isEmpty(selectionArgs)) return selection;
        // If no bindings requested, so we can shortcut
        if (selection.indexOf('?') == -1) return selection;

        // Track the chars immediately before and after each bind request, to
        // decide if it needs additional whitespace added
        char before = ' ';
        char after = ' ';

        int argIndex = 0;
        final int len = selection.length();
        final StringBuilder res = new StringBuilder(len);
        for (int i = 0; i < len; ) {
            char c = selection.charAt(i++);
            if (c == '?') {
                // Assume this bind request is guarded until we find a specific
                // trailing character below
                after = ' ';

                // Sniff forward to see if the selection is requesting a
                // specific argument index
                int start = i;
                for (; i < len; i++) {
                    c = selection.charAt(i);
                    if (c < '0' || c > '9') {
                        after = c;
                        break;
                    }
                }
                if (start != i) {
                    argIndex = Integer.parseInt(selection.substring(start, i)) - 1;
                }

                // Manually bind the argument into the selection, adding
                // whitespace when needed for clarity
                final Object arg = selectionArgs[argIndex++];
                if (before != ' ' && before != '=') res.append(' ');
                switch (DatabaseUtils.getTypeOfObject(arg)) {
                    case Cursor.FIELD_TYPE_NULL:
                        res.append("NULL");
                        break;
                    case Cursor.FIELD_TYPE_INTEGER:
                        res.append(((Number) arg).longValue());
                        break;
                    case Cursor.FIELD_TYPE_FLOAT:
                        res.append(((Number) arg).doubleValue());
                        break;
                    case Cursor.FIELD_TYPE_BLOB:
                        throw new IllegalArgumentException("Blobs not supported");
                    case Cursor.FIELD_TYPE_STRING:
                    default:
                        if (arg instanceof Boolean) {
                            // Provide compatibility with legacy applications which may pass
                            // Boolean values in bind args.
                            res.append(((Boolean) arg).booleanValue() ? 1 : 0);
                        } else {
                            res.append('\'');
                            res.append(arg.toString());
                            res.append('\'');
                        }
                        break;
                }
                if (after != ' ') res.append(' ');
            } else {
                res.append(c);
                before = c;
            }
        }
        return res.toString();
    }

    /**
     * Returns data type of the given object's value.
     *<p>
+24 −4
Original line number Diff line number Diff line
@@ -44,8 +44,7 @@ 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*)?");
@@ -104,7 +103,7 @@ public class SQLiteQueryBuilder
     *
     * @param inWhere the chunk of text to append to the WHERE clause.
     */
    public void appendWhere(CharSequence inWhere) {
    public void appendWhere(@NonNull CharSequence inWhere) {
        if (mWhereClause == null) {
            mWhereClause = new StringBuilder(inWhere.length() + 16);
        }
@@ -121,13 +120,34 @@ public class SQLiteQueryBuilder
     * @param inWhere the chunk of text to append to the WHERE clause. it will be escaped
     * to avoid SQL injection attacks
     */
    public void appendWhereEscapeString(String inWhere) {
    public void appendWhereEscapeString(@NonNull String inWhere) {
        if (mWhereClause == null) {
            mWhereClause = new StringBuilder(inWhere.length() + 16);
        }
        DatabaseUtils.appendEscapedSQLString(mWhereClause, inWhere);
    }

    /**
     * Add a standalone chunk 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 wrapped in parentheses.
     *
     * @param inWhere the standalone expression to append to the {@code WHERE}
     *            clause. It will be wrapped in parentheses when it's appended.
     */
    public void appendWhereStandalone(@NonNull CharSequence inWhere) {
        if (mWhereClause == null) {
            mWhereClause = new StringBuilder(inWhere.length() + 16);
        }
        if (mWhereClause.length() > 0) {
            mWhereClause.append(" AND ");
        }
        mWhereClause.append('(').append(inWhere).append(')');
    }

    /**
     * Sets the projection map for the query.  The projection map maps
     * from column names that the caller passes into query to database
+0 −1036

File deleted.

Preview size limit exceeded, changes collapsed.

+1 −1
Original line number Diff line number Diff line
@@ -309,7 +309,7 @@ public class ArrayUtils {
    }

    @SuppressWarnings("unchecked")
    public static @NonNull <T> T[] concat(Class<T> kind, @Nullable T[] a, @Nullable T[] b) {
    public static @NonNull <T> T[] concatElements(Class<T> kind, @Nullable T[] a, @Nullable T[] b) {
        final int an = (a != null) ? a.length : 0;
        final int bn = (b != null) ? b.length : 0;
        if (an == 0 && bn == 0) {
Loading