diff options
| author | Jeff Sharkey <jsharkey@android.com> | 2018-07-26 14:36:59 -0600 |
|---|---|---|
| committer | Jeff Sharkey <jsharkey@google.com> | 2018-12-01 17:23:05 -0700 |
| commit | 0da04839b7b796c32c01f15bcf34ecb9097addac (patch) | |
| tree | d4550eec0c564d79f83cb8ddf0299ae8ecbddbdb /core/java/android/database/DatabaseUtils.java | |
| parent | 8a634372b343b3e09ac44544199b27fc9f58c300 (diff) | |
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
Diffstat (limited to 'core/java/android/database/DatabaseUtils.java')
| -rw-r--r-- | core/java/android/database/DatabaseUtils.java | 89 |
1 files changed, 89 insertions, 0 deletions
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index 47567c34baf1..d60e61ac419e 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -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; @@ -217,6 +220,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> * Returned values are |
