summaryrefslogtreecommitdiff
path: root/core/java/android/database/DatabaseUtils.java
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2018-07-26 14:36:59 -0600
committerJeff Sharkey <jsharkey@google.com>2018-12-01 17:23:05 -0700
commit0da04839b7b796c32c01f15bcf34ecb9097addac (patch)
treed4550eec0c564d79f83cb8ddf0299ae8ecbddbdb /core/java/android/database/DatabaseUtils.java
parent8a634372b343b3e09ac44544199b27fc9f58c300 (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.java89
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