1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17 package android.content; 18 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; 26 27 import java.util.ArrayList; 28 import java.util.HashMap; 29 import java.util.Map; 30 31 /** 32 * Represents a single operation to be performed as part of a batch of operations. 33 * 34 * @see ContentProvider#applyBatch(ArrayList) 35 */ 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; 45 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; 55 56 private final static String TAG = "ContentProviderOperation"; 57 58 /** 59 * Creates a {@link ContentProviderOperation} by copying the contents of a 60 * {@link Builder}. 61 */ ContentProviderOperation(Builder builder)62 private ContentProviderOperation(Builder builder) { 63 mType = builder.mType; 64 mUri = builder.mUri; 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; 72 } 73 ContentProviderOperation(Parcel source)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) 83 : null; 84 mSelectionArgsBackReferences = source.readInt() != 0 85 ? new HashMap<Integer, Integer>() 86 : null; 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()); 91 } 92 } 93 mYieldAllowed = source.readInt() != 0; 94 } 95 96 /** @hide */ ContentProviderOperation(ContentProviderOperation cpo, Uri withUri)97 public ContentProviderOperation(ContentProviderOperation cpo, Uri withUri) { 98 mType = cpo.mType; 99 mUri = 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; 107 } 108 writeToParcel(Parcel dest, int flags)109 public void writeToParcel(Parcel dest, int flags) { 110 dest.writeInt(mType); 111 Uri.writeToParcel(dest, mUri); 112 if (mValues != null) { 113 dest.writeInt(1); 114 mValues.writeToParcel(dest, 0); 115 } else { 116 dest.writeInt(0); 117 } 118 if (mSelection != null) { 119 dest.writeInt(1); 120 dest.writeString(mSelection); 121 } else { 122 dest.writeInt(0); 123 } 124 if (mSelectionArgs != null) { 125 dest.writeInt(1); 126 dest.writeStringArray(mSelectionArgs); 127 } else { 128 dest.writeInt(0); 129 } 130 if (mExpectedCount != null) { 131 dest.writeInt(1); 132 dest.writeInt(mExpectedCount); 133 } else { 134 dest.writeInt(0); 135 } 136 if (mValuesBackReferences != null) { 137 dest.writeInt(1); 138 mValuesBackReferences.writeToParcel(dest, 0); 139 } else { 140 dest.writeInt(0); 141 } 142 if (mSelectionArgsBackReferences != null) { 143 dest.writeInt(1); 144 dest.writeInt(mSelectionArgsBackReferences.size()); 145 for (Map.Entry<Integer, Integer> entry : mSelectionArgsBackReferences.entrySet()) { 146 dest.writeInt(entry.getKey()); 147 dest.writeInt(entry.getValue()); 148 } 149 } else { 150 dest.writeInt(0); 151 } 152 dest.writeInt(mYieldAllowed ? 1 : 0); 153 } 154 155 /** 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} 159 */ newInsert(Uri uri)160 public static Builder newInsert(Uri uri) { 161 return new Builder(TYPE_INSERT, uri); 162 } 163 164 /** 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} 168 */ newUpdate(Uri uri)169 public static Builder newUpdate(Uri uri) { 170 return new Builder(TYPE_UPDATE, uri); 171 } 172 173 /** 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} 177 */ newDelete(Uri uri)178 public static Builder newDelete(Uri uri) { 179 return new Builder(TYPE_DELETE, uri); 180 } 181 182 /** 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)}. 186 */ newAssertQuery(Uri uri)187 public static Builder newAssertQuery(Uri uri) { 188 return new Builder(TYPE_ASSERT, uri); 189 } 190 191 /** 192 * Gets the Uri for the target of the operation. 193 */ getUri()194 public Uri getUri() { 195 return mUri; 196 } 197 198 /** 199 * Returns true if the operation allows yielding the database to other transactions 200 * if the database is contended. 201 * 202 * @see android.database.sqlite.SQLiteDatabase#yieldIfContendedSafely() 203 */ isYieldAllowed()204 public boolean isYieldAllowed() { 205 return mYieldAllowed; 206 } 207 208 /** @hide exposed for unit tests */ getType()209 public int getType() { 210 return mType; 211 } 212 213 /** 214 * Returns true if the operation represents an insertion. 215 * 216 * @see #newInsert 217 */ isInsert()218 public boolean isInsert() { 219 return mType == TYPE_INSERT; 220 } 221 222 /** 223 * Returns true if the operation represents a deletion. 224 * 225 * @see #newDelete 226 */ isDelete()227 public boolean isDelete() { 228 return mType == TYPE_DELETE; 229 } 230 231 /** 232 * Returns true if the operation represents an update. 233 * 234 * @see #newUpdate 235 */ isUpdate()236 public boolean isUpdate() { 237 return mType == TYPE_UPDATE; 238 } 239 240 /** 241 * Returns true if the operation represents an assert query. 242 * 243 * @see #newAssertQuery 244 */ isAssertQuery()245 public boolean isAssertQuery() { 246 return mType == TYPE_ASSERT; 247 } 248 249 /** 250 * Returns true if the operation represents an insertion, deletion, or update. 251 * 252 * @see #isInsert 253 * @see #isDelete 254 * @see #isUpdate 255 */ isWriteOperation()256 public boolean isWriteOperation() { 257 return mType == TYPE_DELETE || mType == TYPE_INSERT || mType == TYPE_UPDATE; 258 } 259 260 /** 261 * Returns true if the operation represents an assert query. 262 * 263 * @see #isAssertQuery 264 */ isReadOperation()265 public boolean isReadOperation() { 266 return mType == TYPE_ASSERT; 267 } 268 269 /** 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 282 */ apply(ContentProvider provider, ContentProviderResult[] backRefs, int numBackRefs)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); 288 289 if (mType == TYPE_INSERT) { 290 Uri newUri = provider.insert(mUri, values); 291 if (newUri == null) { 292 throw new OperationApplicationException("insert failed"); 293 } 294 return new ContentProviderResult(newUri); 295 } 296 297 int numRows; 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()); 310 } 311 projection = projectionList.toArray(new String[projectionList.size()]); 312 } 313 final Cursor cursor = provider.query(mUri, projection, mSelection, selectionArgs, null); 314 try { 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 " 326 + projection[i]); 327 } 328 } 329 } 330 } 331 } finally { 332 cursor.close(); 333 } 334 } else { 335 Log.e(TAG, this.toString()); 336 throw new IllegalStateException("bad type, " + mType); 337 } 338 339 if (mExpectedCount != null && mExpectedCount != numRows) { 340 Log.e(TAG, this.toString()); 341 throw new OperationApplicationException("wrong number of rows: " + numRows); 342 } 343 344 return new ContentProviderResult(numRows); 345 } 346 347 /** 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. 351 * <p> 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 358 * is null 359 */ resolveValueBackReferences( ContentProviderResult[] backRefs, int numBackRefs)360 public ContentValues resolveValueBackReferences( 361 ContentProviderResult[] backRefs, int numBackRefs) { 362 if (mValuesBackReferences == null) { 363 return mValues; 364 } 365 final ContentValues values; 366 if (mValues == null) { 367 values = new ContentValues(); 368 } else { 369 values = new ContentValues(mValues); 370 } 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"); 377 } 378 values.put(key, backRefToValue(backRefs, numBackRefs, backRefIndex)); 379 } 380 return values; 381 } 382 383 /** 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. 388 * <p> 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 395 * is null 396 */ resolveSelectionArgsBackReferences( ContentProviderResult[] backRefs, int numBackRefs)397 public String[] resolveSelectionArgsBackReferences( 398 ContentProviderResult[] backRefs, int numBackRefs) { 399 if (mSelectionArgsBackReferences == null) { 400 return mSelectionArgs; 401 } 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)); 410 } 411 return newArgs; 412 } 413 414 @Override toString()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; 423 } 424 425 /** 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 431 * the numBackRefs 432 * @return the string representation of the requested back reference. 433 */ backRefToValue(ContentProviderResult[] backRefs, int numBackRefs, Integer backRefIndex)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"); 440 } 441 ContentProviderResult backRef = backRefs[backRefIndex]; 442 long backRefValue; 443 if (backRef.uri != null) { 444 backRefValue = ContentUris.parseId(backRef.uri); 445 } else { 446 backRefValue = backRef.count; 447 } 448 return backRefValue; 449 } 450 describeContents()451 public int describeContents() { 452 return 0; 453 } 454 455 public static final Creator<ContentProviderOperation> CREATOR = 456 new Creator<ContentProviderOperation>() { 457 public ContentProviderOperation createFromParcel(Parcel source) { 458 return new ContentProviderOperation(source); 459 } 460 461 public ContentProviderOperation[] newArray(int size) { 462 return new ContentProviderOperation[size]; 463 } 464 }; 465 466 /** 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. 475 */ 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; 486 487 /** Create a {@link Builder} of a given type. The uri must not be null. */ Builder(int type, Uri uri)488 private Builder(int type, Uri uri) { 489 if (uri == null) { 490 throw new IllegalArgumentException("uri must not be null"); 491 } 492 mType = type; 493 mUri = uri; 494 } 495 496 /** Create a ContentProviderOperation from this {@link Builder}. */ build()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"); 502 } 503 } 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"); 509 } 510 } 511 return new ContentProviderOperation(this); 512 } 513 514 /** 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. 522 */ withValueBackReferences(ContentValues backReferences)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"); 527 } 528 mValuesBackReferences = backReferences; 529 return this; 530 } 531 532 /** 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. 538 */ withValueBackReference(String key, int previousResult)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"); 543 } 544 if (mValuesBackReferences == null) { 545 mValuesBackReferences = new ContentValues(); 546 } 547 mValuesBackReferences.put(key, previousResult); 548 return this; 549 } 550 551 /** 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. 556 */ withSelectionBackReference(int selectionArgIndex, int previousResult)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"); 561 } 562 if (mSelectionArgsBackReferences == null) { 563 mSelectionArgsBackReferences = new HashMap<Integer, Integer>(); 564 } 565 mSelectionArgsBackReferences.put(selectionArgIndex, previousResult); 566 return this; 567 } 568 569 /** 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. 575 */ withValues(ContentValues values)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"); 580 } 581 if (mValues == null) { 582 mValues = new ContentValues(); 583 } 584 mValues.putAll(values); 585 return this; 586 } 587 588 /** 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. 596 */ withValue(String key, Object value)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"); 600 } 601 if (mValues == null) { 602 mValues = new ContentValues(); 603 } 604 if (value == null) { 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); 624 } else { 625 throw new IllegalArgumentException("bad value type: " + value.getClass().getName()); 626 } 627 return this; 628 } 629 630 /** 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. 637 */ withSelection(String selection, String[] selectionArgs)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"); 642 } 643 mSelection = selection; 644 if (selectionArgs == null) { 645 mSelectionArgs = null; 646 } else { 647 mSelectionArgs = new String[selectionArgs.length]; 648 System.arraycopy(selectionArgs, 0, mSelectionArgs, 0, selectionArgs.length); 649 } 650 return this; 651 } 652 653 /** 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. 658 */ withExpectedCount(int count)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"); 663 } 664 mExpectedCount = count; 665 return this; 666 } 667 668 /** 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() 673 */ withYieldAllowed(boolean yieldAllowed)674 public Builder withYieldAllowed(boolean yieldAllowed) { 675 mYieldAllowed = yieldAllowed; 676 return this; 677 } 678 } 679 } 680