|
29 | 29 | import static org.junit.Assert.fail;
|
30 | 30 |
|
31 | 31 | import com.google.cloud.Timestamp;
|
| 32 | +import com.google.cloud.Tuple; |
32 | 33 | import com.google.cloud.datastore.AggregationQuery;
|
33 | 34 | import com.google.cloud.datastore.Batch;
|
34 | 35 | import com.google.cloud.datastore.BooleanValue;
|
@@ -335,57 +336,82 @@ public void testNewTransactionCommit() {
|
335 | 336 | }
|
336 | 337 |
|
337 | 338 | @Test
|
338 |
| - public void testTransactionWithRead() { |
339 |
| - Transaction transaction = DATASTORE.newTransaction(); |
340 |
| - assertNull(transaction.get(KEY3)); |
341 |
| - transaction.add(ENTITY3); |
342 |
| - transaction.commit(); |
| 339 | + public void testTransactionWithRead() throws Exception { |
| 340 | + StatementExecutor statementExecutor = new StatementExecutor(); |
| 341 | + Transaction baseTransaction = DATASTORE.newTransaction(); |
| 342 | + assertNull(baseTransaction.get(KEY3)); |
| 343 | + baseTransaction.add(ENTITY3); |
| 344 | + baseTransaction.commit(); |
343 | 345 | assertEquals(ENTITY3, DATASTORE.get(KEY3));
|
344 | 346 |
|
345 |
| - transaction = DATASTORE.newTransaction(); |
346 |
| - assertEquals(ENTITY3, transaction.get(KEY3)); |
347 |
| - // update entity3 during the transaction |
348 |
| - DATASTORE.put(Entity.newBuilder(ENTITY2).clear().set("from", "datastore").build()); |
349 |
| - transaction.update(Entity.newBuilder(ENTITY2).clear().set("from", "transaction").build()); |
350 |
| - try { |
351 |
| - transaction.commit(); |
352 |
| - fail("Expecting a failure"); |
353 |
| - } catch (DatastoreException expected) { |
354 |
| - assertEquals("ABORTED", expected.getReason()); |
355 |
| - } |
| 347 | + Transaction transaction = DATASTORE.newTransaction(); |
| 348 | + statementExecutor.execute( |
| 349 | + Tuple.of("T1", () -> assertEquals(ENTITY3, transaction.get(KEY3))), |
| 350 | + // update entity3 during the transaction, will be blocked in case of pessimistic concurrency |
| 351 | + Tuple.of( |
| 352 | + "T2", |
| 353 | + () -> |
| 354 | + DATASTORE.put(Entity.newBuilder(ENTITY3).clear().set("from", "datastore").build())), |
| 355 | + Tuple.of( |
| 356 | + "T1", |
| 357 | + () -> |
| 358 | + transaction.update( |
| 359 | + Entity.newBuilder(ENTITY3).clear().set("from", "transaction").build())), |
| 360 | + Tuple.of("T1", transaction::commit) // T1 will throw error in case of optimistic concurrency |
| 361 | + ); |
| 362 | + |
| 363 | + boolean t1AllPassed = statementExecutor.didAllPass("T1"); |
| 364 | + boolean t2AllPassed = statementExecutor.didAllPass("T2"); |
| 365 | + // If two transactions conflict with each other, the database guarantees that only |
| 366 | + // one can commit successfully at a time. Please refer to StatementExecutor class for more info. |
| 367 | + // Using XOR to ensure that only one of transaction group is successful, |
| 368 | + boolean onlyOneTransactionIsSuccessful = t1AllPassed ^ t2AllPassed; |
| 369 | + |
| 370 | + assertThat(onlyOneTransactionIsSuccessful).isTrue(); |
356 | 371 | }
|
357 | 372 |
|
358 | 373 | @Test
|
359 |
| - public void testTransactionWithQuery() { |
| 374 | + public void testTransactionWithQuery() throws Exception { |
| 375 | + StatementExecutor statementExecutor = new StatementExecutor(); |
360 | 376 | Query<Entity> query =
|
361 | 377 | Query.newEntityQueryBuilder()
|
362 | 378 | .setKind(KIND2)
|
363 | 379 | .setFilter(PropertyFilter.hasAncestor(KEY2))
|
364 | 380 | .setNamespace(NAMESPACE)
|
365 | 381 | .build();
|
366 |
| - Transaction transaction = DATASTORE.newTransaction(); |
367 |
| - QueryResults<Entity> results = transaction.run(query); |
368 |
| - assertTrue(results.hasNext()); |
369 |
| - assertEquals(ENTITY2, results.next()); |
370 |
| - assertFalse(results.hasNext()); |
371 |
| - transaction.add(ENTITY3); |
372 |
| - transaction.commit(); |
| 382 | + Transaction baseTransaction = DATASTORE.newTransaction(); |
| 383 | + QueryResults<Entity> baseResults = baseTransaction.run(query); |
| 384 | + assertTrue(baseResults.hasNext()); |
| 385 | + assertEquals(ENTITY2, baseResults.next()); |
| 386 | + assertFalse(baseResults.hasNext()); |
| 387 | + baseTransaction.add(ENTITY3); |
| 388 | + baseTransaction.commit(); |
373 | 389 | assertEquals(ENTITY3, DATASTORE.get(KEY3));
|
374 | 390 |
|
375 |
| - transaction = DATASTORE.newTransaction(); |
376 |
| - results = transaction.run(query); |
377 |
| - assertTrue(results.hasNext()); |
378 |
| - assertEquals(ENTITY2, results.next()); |
379 |
| - assertFalse(results.hasNext()); |
380 |
| - transaction.delete(ENTITY3.getKey()); |
381 |
| - // update entity2 during the transaction |
382 |
| - DATASTORE.put(Entity.newBuilder(ENTITY2).clear().build()); |
383 |
| - try { |
384 |
| - transaction.commit(); |
385 |
| - fail("Expecting a failure"); |
386 |
| - } catch (DatastoreException expected) { |
387 |
| - assertEquals("ABORTED", expected.getReason()); |
388 |
| - } |
| 391 | + Transaction transaction = DATASTORE.newTransaction(); |
| 392 | + statementExecutor.execute( |
| 393 | + Tuple.of( |
| 394 | + "T1", |
| 395 | + () -> { |
| 396 | + QueryResults<Entity> results = transaction.run(query); |
| 397 | + assertTrue(results.hasNext()); |
| 398 | + assertEquals(ENTITY2, results.next()); |
| 399 | + assertFalse(results.hasNext()); |
| 400 | + }), |
| 401 | + Tuple.of("T1", () -> transaction.delete(ENTITY3.getKey())), |
| 402 | + // update entity2 during the transaction, will be blocked in case of pessimistic concurrency |
| 403 | + Tuple.of("T2", () -> DATASTORE.put(Entity.newBuilder(ENTITY2).clear().build())), |
| 404 | + Tuple.of("T1", transaction::commit) // T1 will throw error in case of optimistic concurrency |
| 405 | + ); |
| 406 | + |
| 407 | + boolean t1AllPassed = statementExecutor.didAllPass("T1"); |
| 408 | + boolean t2AllPassed = statementExecutor.didAllPass("T2"); |
| 409 | + // If two transactions conflict with each other, the database guarantees that only |
| 410 | + // one can commit successfully at a time. Please refer to StatementExecutor class for more info. |
| 411 | + // Using XOR to ensure that only one of transaction group is successful, |
| 412 | + boolean onlyOneTransactionIsSuccessful = t1AllPassed ^ t2AllPassed; |
| 413 | + |
| 414 | + assertThat(onlyOneTransactionIsSuccessful).isTrue(); |
389 | 415 | }
|
390 | 416 |
|
391 | 417 | @Test
|
|
0 commit comments