2 * Copyright (C) 2009 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package android.content;
19 import android.content.ContentProvider;
20 import android.database.Cursor;
21 import android.net.Uri;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.text.TextUtils;
25 import android.util.Log;
27 import java.util.ArrayList;
28 import java.util.HashMap;
32 * Represents a single operation to be performed as part of a batch of operations.
34 * @see ContentProvider#applyBatch(ArrayList)
36 public class ContentProviderOperation implements Parcelable {
37 /** @hide exposed for unit tests */
38 public final static int TYPE_INSERT = 1;
39 /** @hide exposed for unit tests */
40 public final static int TYPE_UPDATE = 2;
41 /** @hide exposed for unit tests */
42 public final static int TYPE_DELETE = 3;
43 /** @hide exposed for unit tests */
44 public final static int TYPE_ASSERT = 4;
46 private final int mType;
47 private final Uri mUri;
48 private final String mSelection;
49 private final String[] mSelectionArgs;
50 private final ContentValues mValues;
51 private final Integer mExpectedCount;
52 private final ContentValues mValuesBackReferences;
53 private final Map<Integer, Integer> mSelectionArgsBackReferences;
54 private final boolean mYieldAllowed;
56 private final static String TAG = "ContentProviderOperation";
59 * Creates a {@link ContentProviderOperation} by copying the contents of a
62 private ContentProviderOperation(Builder builder) {
63 mType = builder.mType;
65 mValues = builder.mValues;
66 mSelection = builder.mSelection;
67 mSelectionArgs = builder.mSelectionArgs;
68 mExpectedCount = builder.mExpectedCount;
69 mSelectionArgsBackReferences = builder.mSelectionArgsBackReferences;
70 mValuesBackReferences = builder.mValuesBackReferences;
71 mYieldAllowed = builder.mYieldAllowed;
74 private ContentProviderOperation(Parcel source) {
75 mType = source.readInt();
76 mUri = Uri.CREATOR.createFromParcel(source);
77 mValues = source.readInt() != 0 ? ContentValues.CREATOR.createFromParcel(source) : null;
78 mSelection = source.readInt() != 0 ? source.readString() : null;
79 mSelectionArgs = source.readInt() != 0 ? source.readStringArray() : null;
80 mExpectedCount = source.readInt() != 0 ? source.readInt() : null;
81 mValuesBackReferences = source.readInt() != 0
82 ? ContentValues.CREATOR.createFromParcel(source)
84 mSelectionArgsBackReferences = source.readInt() != 0
85 ? new HashMap<Integer, Integer>()
87 if (mSelectionArgsBackReferences != null) {
88 final int count = source.readInt();
89 for (int i = 0; i < count; i++) {
90 mSelectionArgsBackReferences.put(source.readInt(), source.readInt());
93 mYieldAllowed = source.readInt() != 0;
97 public ContentProviderOperation(ContentProviderOperation cpo, Uri withUri) {
100 mValues = cpo.mValues;
101 mSelection = cpo.mSelection;
102 mSelectionArgs = cpo.mSelectionArgs;
103 mExpectedCount = cpo.mExpectedCount;
104 mSelectionArgsBackReferences = cpo.mSelectionArgsBackReferences;
105 mValuesBackReferences = cpo.mValuesBackReferences;
106 mYieldAllowed = cpo.mYieldAllowed;
109 public void writeToParcel(Parcel dest, int flags) {
110 dest.writeInt(mType);
111 Uri.writeToParcel(dest, mUri);
112 if (mValues != null) {
114 mValues.writeToParcel(dest, 0);
118 if (mSelection != null) {
120 dest.writeString(mSelection);
124 if (mSelectionArgs != null) {
126 dest.writeStringArray(mSelectionArgs);
130 if (mExpectedCount != null) {
132 dest.writeInt(mExpectedCount);
136 if (mValuesBackReferences != null) {
138 mValuesBackReferences.writeToParcel(dest, 0);
142 if (mSelectionArgsBackReferences != null) {
144 dest.writeInt(mSelectionArgsBackReferences.size());
145 for (Map.Entry<Integer, Integer> entry : mSelectionArgsBackReferences.entrySet()) {
146 dest.writeInt(entry.getKey());
147 dest.writeInt(entry.getValue());
152 dest.writeInt(mYieldAllowed ? 1 : 0);
156 * Create a {@link Builder} suitable for building an insert {@link ContentProviderOperation}.
157 * @param uri The {@link Uri} that is the target of the insert.
158 * @return a {@link Builder}
160 public static Builder newInsert(Uri uri) {
161 return new Builder(TYPE_INSERT, uri);
165 * Create a {@link Builder} suitable for building an update {@link ContentProviderOperation}.
166 * @param uri The {@link Uri} that is the target of the update.
167 * @return a {@link Builder}
169 public static Builder newUpdate(Uri uri) {
170 return new Builder(TYPE_UPDATE, uri);
174 * Create a {@link Builder} suitable for building a delete {@link ContentProviderOperation}.
175 * @param uri The {@link Uri} that is the target of the delete.
176 * @return a {@link Builder}
178 public static Builder newDelete(Uri uri) {
179 return new Builder(TYPE_DELETE, uri);
183 * Create a {@link Builder} suitable for building a
184 * {@link ContentProviderOperation} to assert a set of values as provided
185 * through {@link Builder#withValues(ContentValues)}.
187 public static Builder newAssertQuery(Uri uri) {
188 return new Builder(TYPE_ASSERT, uri);
192 * Gets the Uri for the target of the operation.
194 public Uri getUri() {
199 * Returns true if the operation allows yielding the database to other transactions
200 * if the database is contended.
202 * @see android.database.sqlite.SQLiteDatabase#yieldIfContendedSafely()
204 public boolean isYieldAllowed() {
205 return mYieldAllowed;
208 /** @hide exposed for unit tests */
209 public int getType() {
214 * Returns true if the operation represents an insertion.
218 public boolean isInsert() {
219 return mType == TYPE_INSERT;
223 * Returns true if the operation represents a deletion.
227 public boolean isDelete() {
228 return mType == TYPE_DELETE;
232 * Returns true if the operation represents an update.
236 public boolean isUpdate() {
237 return mType == TYPE_UPDATE;
241 * Returns true if the operation represents an assert query.
243 * @see #newAssertQuery
245 public boolean isAssertQuery() {
246 return mType == TYPE_ASSERT;
250 * Returns true if the operation represents an insertion, deletion, or update.
256 public boolean isWriteOperation() {
257 return mType == TYPE_DELETE || mType == TYPE_INSERT || mType == TYPE_UPDATE;
261 * Returns true if the operation represents an assert query.
263 * @see #isAssertQuery
265 public boolean isReadOperation() {
266 return mType == TYPE_ASSERT;
270 * Applies this operation using the given provider. The backRefs array is used to resolve any
271 * back references that were requested using
272 * {@link Builder#withValueBackReferences(ContentValues)} and
273 * {@link Builder#withSelectionBackReference}.
274 * @param provider the {@link ContentProvider} on which this batch is applied
275 * @param backRefs a {@link ContentProviderResult} array that will be consulted
276 * to resolve any requested back references.
277 * @param numBackRefs the number of valid results on the backRefs array.
278 * @return a {@link ContentProviderResult} that contains either the {@link Uri} of the inserted
279 * row if this was an insert otherwise the number of rows affected.
280 * @throws OperationApplicationException thrown if either the insert fails or
281 * if the number of rows affected didn't match the expected count
283 public ContentProviderResult apply(ContentProvider provider, ContentProviderResult[] backRefs,
284 int numBackRefs) throws OperationApplicationException {
285 ContentValues values = resolveValueBackReferences(backRefs, numBackRefs);
286 String[] selectionArgs =
287 resolveSelectionArgsBackReferences(backRefs, numBackRefs);
289 if (mType == TYPE_INSERT) {
290 Uri newUri = provider.insert(mUri, values);
291 if (newUri == null) {
292 throw new OperationApplicationException("insert failed");
294 return new ContentProviderResult(newUri);
298 if (mType == TYPE_DELETE) {
299 numRows = provider.delete(mUri, mSelection, selectionArgs);
300 } else if (mType == TYPE_UPDATE) {
301 numRows = provider.update(mUri, values, mSelection, selectionArgs);
302 } else if (mType == TYPE_ASSERT) {
303 // Assert that all rows match expected values
304 String[] projection = null;
305 if (values != null) {
306 // Build projection map from expected values
307 final ArrayList<String> projectionList = new ArrayList<String>();
308 for (Map.Entry<String, Object> entry : values.valueSet()) {
309 projectionList.add(entry.getKey());
311 projection = projectionList.toArray(new String[projectionList.size()]);
313 final Cursor cursor = provider.query(mUri, projection, mSelection, selectionArgs, null);
315 numRows = cursor.getCount();
316 if (projection != null) {
317 while (cursor.moveToNext()) {
318 for (int i = 0; i < projection.length; i++) {
319 final String cursorValue = cursor.getString(i);
320 final String expectedValue = values.getAsString(projection[i]);
321 if (!TextUtils.equals(cursorValue, expectedValue)) {
322 // Throw exception when expected values don't match
323 Log.e(TAG, this.toString());
324 throw new OperationApplicationException("Found value " + cursorValue
325 + " when expected " + expectedValue + " for column "
335 Log.e(TAG, this.toString());
336 throw new IllegalStateException("bad type, " + mType);
339 if (mExpectedCount != null && mExpectedCount != numRows) {
340 Log.e(TAG, this.toString());
341 throw new OperationApplicationException("wrong number of rows: " + numRows);
344 return new ContentProviderResult(numRows);
348 * The ContentValues back references are represented as a ContentValues object where the
349 * key refers to a column and the value is an index of the back reference whose
350 * valued should be associated with the column.
352 * This is intended to be a private method but it is exposed for
353 * unit testing purposes
354 * @param backRefs an array of previous results
355 * @param numBackRefs the number of valid previous results in backRefs
356 * @return the ContentValues that should be used in this operation application after
357 * expansion of back references. This can be called if either mValues or mValuesBackReferences
360 public ContentValues resolveValueBackReferences(
361 ContentProviderResult[] backRefs, int numBackRefs) {
362 if (mValuesBackReferences == null) {
365 final ContentValues values;
366 if (mValues == null) {
367 values = new ContentValues();
369 values = new ContentValues(mValues);
371 for (Map.Entry<String, Object> entry : mValuesBackReferences.valueSet()) {
372 String key = entry.getKey();
373 Integer backRefIndex = mValuesBackReferences.getAsInteger(key);
374 if (backRefIndex == null) {
375 Log.e(TAG, this.toString());
376 throw new IllegalArgumentException("values backref " + key + " is not an integer");
378 values.put(key, backRefToValue(backRefs, numBackRefs, backRefIndex));
384 * The Selection Arguments back references are represented as a Map of Integer->Integer where
385 * the key is an index into the selection argument array (see {@link Builder#withSelection})
386 * and the value is the index of the previous result that should be used for that selection
387 * argument array slot.
389 * This is intended to be a private method but it is exposed for
390 * unit testing purposes
391 * @param backRefs an array of previous results
392 * @param numBackRefs the number of valid previous results in backRefs
393 * @return the ContentValues that should be used in this operation application after
394 * expansion of back references. This can be called if either mValues or mValuesBackReferences
397 public String[] resolveSelectionArgsBackReferences(
398 ContentProviderResult[] backRefs, int numBackRefs) {
399 if (mSelectionArgsBackReferences == null) {
400 return mSelectionArgs;
402 String[] newArgs = new String[mSelectionArgs.length];
403 System.arraycopy(mSelectionArgs, 0, newArgs, 0, mSelectionArgs.length);
404 for (Map.Entry<Integer, Integer> selectionArgBackRef
405 : mSelectionArgsBackReferences.entrySet()) {
406 final Integer selectionArgIndex = selectionArgBackRef.getKey();
407 final int backRefIndex = selectionArgBackRef.getValue();
408 newArgs[selectionArgIndex] =
409 String.valueOf(backRefToValue(backRefs, numBackRefs, backRefIndex));
415 public String toString() {
416 return "mType: " + mType + ", mUri: " + mUri +
417 ", mSelection: " + mSelection +
418 ", mExpectedCount: " + mExpectedCount +
419 ", mYieldAllowed: " + mYieldAllowed +
420 ", mValues: " + mValues +
421 ", mValuesBackReferences: " + mValuesBackReferences +
422 ", mSelectionArgsBackReferences: " + mSelectionArgsBackReferences;
426 * Return the string representation of the requested back reference.
427 * @param backRefs an array of results
428 * @param numBackRefs the number of items in the backRefs array that are valid
429 * @param backRefIndex which backRef to be used
430 * @throws ArrayIndexOutOfBoundsException thrown if the backRefIndex is larger than
432 * @return the string representation of the requested back reference.
434 private long backRefToValue(ContentProviderResult[] backRefs, int numBackRefs,
435 Integer backRefIndex) {
436 if (backRefIndex >= numBackRefs) {
437 Log.e(TAG, this.toString());
438 throw new ArrayIndexOutOfBoundsException("asked for back ref " + backRefIndex
439 + " but there are only " + numBackRefs + " back refs");
441 ContentProviderResult backRef = backRefs[backRefIndex];
443 if (backRef.uri != null) {
444 backRefValue = ContentUris.parseId(backRef.uri);
446 backRefValue = backRef.count;
451 public int describeContents() {
455 public static final Creator<ContentProviderOperation> CREATOR =
456 new Creator<ContentProviderOperation>() {
457 public ContentProviderOperation createFromParcel(Parcel source) {
458 return new ContentProviderOperation(source);
461 public ContentProviderOperation[] newArray(int size) {
462 return new ContentProviderOperation[size];
467 * Used to add parameters to a {@link ContentProviderOperation}. The {@link Builder} is
468 * first created by calling {@link ContentProviderOperation#newInsert(android.net.Uri)},
469 * {@link ContentProviderOperation#newUpdate(android.net.Uri)},
470 * {@link ContentProviderOperation#newDelete(android.net.Uri)} or
471 * {@link ContentProviderOperation#newAssertQuery(Uri)}. The withXXX methods
472 * can then be used to add parameters to the builder. See the specific methods to find for
473 * which {@link Builder} type each is allowed. Call {@link #build} to create the
474 * {@link ContentProviderOperation} once all the parameters have been supplied.
476 public static class Builder {
477 private final int mType;
478 private final Uri mUri;
479 private String mSelection;
480 private String[] mSelectionArgs;
481 private ContentValues mValues;
482 private Integer mExpectedCount;
483 private ContentValues mValuesBackReferences;
484 private Map<Integer, Integer> mSelectionArgsBackReferences;
485 private boolean mYieldAllowed;
487 /** Create a {@link Builder} of a given type. The uri must not be null. */
488 private Builder(int type, Uri uri) {
490 throw new IllegalArgumentException("uri must not be null");
496 /** Create a ContentProviderOperation from this {@link Builder}. */
497 public ContentProviderOperation build() {
498 if (mType == TYPE_UPDATE) {
499 if ((mValues == null || mValues.isEmpty())
500 && (mValuesBackReferences == null || mValuesBackReferences.isEmpty())) {
501 throw new IllegalArgumentException("Empty values");
504 if (mType == TYPE_ASSERT) {
505 if ((mValues == null || mValues.isEmpty())
506 && (mValuesBackReferences == null || mValuesBackReferences.isEmpty())
507 && (mExpectedCount == null)) {
508 throw new IllegalArgumentException("Empty values");
511 return new ContentProviderOperation(this);
515 * Add a {@link ContentValues} of back references. The key is the name of the column
516 * and the value is an integer that is the index of the previous result whose
517 * value should be used for the column. The value is added as a {@link String}.
518 * A column value from the back references takes precedence over a value specified in
519 * {@link #withValues}.
520 * This can only be used with builders of type insert, update, or assert.
521 * @return this builder, to allow for chaining.
523 public Builder withValueBackReferences(ContentValues backReferences) {
524 if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
525 throw new IllegalArgumentException(
526 "only inserts, updates, and asserts can have value back-references");
528 mValuesBackReferences = backReferences;
533 * Add a ContentValues back reference.
534 * A column value from the back references takes precedence over a value specified in
535 * {@link #withValues}.
536 * This can only be used with builders of type insert, update, or assert.
537 * @return this builder, to allow for chaining.
539 public Builder withValueBackReference(String key, int previousResult) {
540 if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
541 throw new IllegalArgumentException(
542 "only inserts, updates, and asserts can have value back-references");
544 if (mValuesBackReferences == null) {
545 mValuesBackReferences = new ContentValues();
547 mValuesBackReferences.put(key, previousResult);
552 * Add a back references as a selection arg. Any value at that index of the selection arg
553 * that was specified by {@link #withSelection} will be overwritten.
554 * This can only be used with builders of type update, delete, or assert.
555 * @return this builder, to allow for chaining.
557 public Builder withSelectionBackReference(int selectionArgIndex, int previousResult) {
558 if (mType != TYPE_UPDATE && mType != TYPE_DELETE && mType != TYPE_ASSERT) {
559 throw new IllegalArgumentException("only updates, deletes, and asserts "
560 + "can have selection back-references");
562 if (mSelectionArgsBackReferences == null) {
563 mSelectionArgsBackReferences = new HashMap<Integer, Integer>();
565 mSelectionArgsBackReferences.put(selectionArgIndex, previousResult);
570 * The ContentValues to use. This may be null. These values may be overwritten by
571 * the corresponding value specified by {@link #withValueBackReference} or by
572 * future calls to {@link #withValues} or {@link #withValue}.
573 * This can only be used with builders of type insert, update, or assert.
574 * @return this builder, to allow for chaining.
576 public Builder withValues(ContentValues values) {
577 if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
578 throw new IllegalArgumentException(
579 "only inserts, updates, and asserts can have values");
581 if (mValues == null) {
582 mValues = new ContentValues();
584 mValues.putAll(values);
589 * A value to insert or update. This value may be overwritten by
590 * the corresponding value specified by {@link #withValueBackReference}.
591 * This can only be used with builders of type insert, update, or assert.
592 * @param key the name of this value
593 * @param value the value itself. the type must be acceptable for insertion by
594 * {@link ContentValues#put}
595 * @return this builder, to allow for chaining.
597 public Builder withValue(String key, Object value) {
598 if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
599 throw new IllegalArgumentException("only inserts and updates can have values");
601 if (mValues == null) {
602 mValues = new ContentValues();
605 mValues.putNull(key);
606 } else if (value instanceof String) {
607 mValues.put(key, (String) value);
608 } else if (value instanceof Byte) {
609 mValues.put(key, (Byte) value);
610 } else if (value instanceof Short) {
611 mValues.put(key, (Short) value);
612 } else if (value instanceof Integer) {
613 mValues.put(key, (Integer) value);
614 } else if (value instanceof Long) {
615 mValues.put(key, (Long) value);
616 } else if (value instanceof Float) {
617 mValues.put(key, (Float) value);
618 } else if (value instanceof Double) {
619 mValues.put(key, (Double) value);
620 } else if (value instanceof Boolean) {
621 mValues.put(key, (Boolean) value);
622 } else if (value instanceof byte[]) {
623 mValues.put(key, (byte[]) value);
625 throw new IllegalArgumentException("bad value type: " + value.getClass().getName());
631 * The selection and arguments to use. An occurrence of '?' in the selection will be
632 * replaced with the corresponding occurence of the selection argument. Any of the
633 * selection arguments may be overwritten by a selection argument back reference as
634 * specified by {@link #withSelectionBackReference}.
635 * This can only be used with builders of type update, delete, or assert.
636 * @return this builder, to allow for chaining.
638 public Builder withSelection(String selection, String[] selectionArgs) {
639 if (mType != TYPE_UPDATE && mType != TYPE_DELETE && mType != TYPE_ASSERT) {
640 throw new IllegalArgumentException(
641 "only updates, deletes, and asserts can have selections");
643 mSelection = selection;
644 if (selectionArgs == null) {
645 mSelectionArgs = null;
647 mSelectionArgs = new String[selectionArgs.length];
648 System.arraycopy(selectionArgs, 0, mSelectionArgs, 0, selectionArgs.length);
654 * If set then if the number of rows affected by this operation does not match
655 * this count {@link OperationApplicationException} will be throw.
656 * This can only be used with builders of type update, delete, or assert.
657 * @return this builder, to allow for chaining.
659 public Builder withExpectedCount(int count) {
660 if (mType != TYPE_UPDATE && mType != TYPE_DELETE && mType != TYPE_ASSERT) {
661 throw new IllegalArgumentException(
662 "only updates, deletes, and asserts can have expected counts");
664 mExpectedCount = count;
669 * If set to true then the operation allows yielding the database to other transactions
670 * if the database is contended.
671 * @return this builder, to allow for chaining.
672 * @see android.database.sqlite.SQLiteDatabase#yieldIfContendedSafely()
674 public Builder withYieldAllowed(boolean yieldAllowed) {
675 mYieldAllowed = yieldAllowed;