|
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