|
21 | 21 | import static com.google.common.base.Preconditions.checkNotNull;
|
22 | 22 | import static com.google.common.base.Preconditions.checkState;
|
23 | 23 |
|
| 24 | +import com.google.api.core.ApiFuture; |
| 25 | +import com.google.api.core.ApiFutureCallback; |
| 26 | +import com.google.api.core.ApiFutures; |
| 27 | +import com.google.api.core.SettableApiFuture; |
| 28 | +import com.google.api.gax.core.ExecutorProvider; |
24 | 29 | import com.google.cloud.Timestamp;
|
25 | 30 | import com.google.cloud.spanner.AbstractResultSet.CloseableIterator;
|
26 | 31 | import com.google.cloud.spanner.AbstractResultSet.GrpcResultSet;
|
27 | 32 | import com.google.cloud.spanner.AbstractResultSet.GrpcStreamIterator;
|
28 | 33 | import com.google.cloud.spanner.AbstractResultSet.ResumableStreamIterator;
|
| 34 | +import com.google.cloud.spanner.AsyncResultSet.CallbackResponse; |
| 35 | +import com.google.cloud.spanner.AsyncResultSet.ReadyCallback; |
29 | 36 | import com.google.cloud.spanner.Options.QueryOption;
|
30 | 37 | import com.google.cloud.spanner.Options.ReadOption;
|
31 | 38 | import com.google.cloud.spanner.SessionImpl.SessionTransaction;
|
32 | 39 | import com.google.cloud.spanner.spi.v1.SpannerRpc;
|
33 | 40 | import com.google.common.annotations.VisibleForTesting;
|
| 41 | +import com.google.common.util.concurrent.MoreExecutors; |
34 | 42 | import com.google.protobuf.ByteString;
|
35 | 43 | import com.google.spanner.v1.BeginTransactionRequest;
|
36 | 44 | import com.google.spanner.v1.ExecuteBatchDmlRequest;
|
@@ -62,6 +70,7 @@ abstract static class Builder<B extends Builder<?, T>, T extends AbstractReadCon
|
62 | 70 | private Span span = Tracing.getTracer().getCurrentSpan();
|
63 | 71 | private int defaultPrefetchChunks = SpannerOptions.Builder.DEFAULT_PREFETCH_CHUNKS;
|
64 | 72 | private QueryOptions defaultQueryOptions = SpannerOptions.Builder.DEFAULT_QUERY_OPTIONS;
|
| 73 | +private ExecutorProvider executorProvider; |
65 | 74 |
|
66 | 75 | Builder() {}
|
67 | 76 |
|
@@ -95,9 +104,25 @@ B setDefaultQueryOptions(QueryOptions defaultQueryOptions) {
|
95 | 104 | return self();
|
96 | 105 | }
|
97 | 106 |
|
| 107 | +B setExecutorProvider(ExecutorProvider executorProvider) { |
| 108 | +this.executorProvider = executorProvider; |
| 109 | +return self(); |
| 110 | +} |
| 111 | + |
98 | 112 | abstract T build();
|
99 | 113 | }
|
100 | 114 |
|
| 115 | +/** |
| 116 | +* {@link AsyncResultSet} that supports adding listeners that are called when all rows from the |
| 117 | +* underlying result stream have been fetched. |
| 118 | +*/ |
| 119 | +interface ListenableAsyncResultSet extends AsyncResultSet { |
| 120 | +/** Adds a listener to this {@link AsyncResultSet}. */ |
| 121 | +void addListener(Runnable listener); |
| 122 | + |
| 123 | +void removeListener(Runnable listener); |
| 124 | +} |
| 125 | + |
101 | 126 | /**
|
102 | 127 | * A {@code ReadContext} for standalone reads. This can only be used for a single operation, since
|
103 | 128 | * each standalone read may see a different timestamp of Cloud Spanner data.
|
@@ -350,7 +375,8 @@ void initTransaction() {
|
350 | 375 | final Object lock = new Object();
|
351 | 376 | final SessionImpl session;
|
352 | 377 | final SpannerRpc rpc;
|
353 |
| -final Span span; |
| 378 | +final ExecutorProvider executorProvider; |
| 379 | +Span span; |
354 | 380 | private final int defaultPrefetchChunks;
|
355 | 381 | private final QueryOptions defaultQueryOptions;
|
356 | 382 |
|
@@ -374,6 +400,12 @@ void initTransaction() {
|
374 | 400 | this.defaultPrefetchChunks = builder.defaultPrefetchChunks;
|
375 | 401 | this.defaultQueryOptions = builder.defaultQueryOptions;
|
376 | 402 | this.span = builder.span;
|
| 403 | +this.executorProvider = builder.executorProvider; |
| 404 | +} |
| 405 | + |
| 406 | +@Override |
| 407 | +public void setSpan(Span span) { |
| 408 | +this.span = span; |
377 | 409 | }
|
378 | 410 |
|
379 | 411 | long getSeqNo() {
|
@@ -386,12 +418,38 @@ public final ResultSet read(
|
386 | 418 | return readInternal(table, null, keys, columns, options);
|
387 | 419 | }
|
388 | 420 |
|
| 421 | +@Override |
| 422 | +public ListenableAsyncResultSet readAsync( |
| 423 | +String table, KeySet keys, Iterable<String> columns, ReadOption... options) { |
| 424 | +Options readOptions = Options.fromReadOptions(options); |
| 425 | +final int bufferRows = |
| 426 | +readOptions.hasBufferRows() |
| 427 | +? readOptions.bufferRows() |
| 428 | +: AsyncResultSetImpl.DEFAULT_BUFFER_SIZE; |
| 429 | +return new AsyncResultSetImpl( |
| 430 | +executorProvider, readInternal(table, null, keys, columns, options), bufferRows); |
| 431 | +} |
| 432 | + |
389 | 433 | @Override
|
390 | 434 | public final ResultSet readUsingIndex(
|
391 | 435 | String table, String index, KeySet keys, Iterable<String> columns, ReadOption... options) {
|
392 | 436 | return readInternal(table, checkNotNull(index), keys, columns, options);
|
393 | 437 | }
|
394 | 438 |
|
| 439 | +@Override |
| 440 | +public ListenableAsyncResultSet readUsingIndexAsync( |
| 441 | +String table, String index, KeySet keys, Iterable<String> columns, ReadOption... options) { |
| 442 | +Options readOptions = Options.fromReadOptions(options); |
| 443 | +final int bufferRows = |
| 444 | +readOptions.hasBufferRows() |
| 445 | +? readOptions.bufferRows() |
| 446 | +: AsyncResultSetImpl.DEFAULT_BUFFER_SIZE; |
| 447 | +return new AsyncResultSetImpl( |
| 448 | +executorProvider, |
| 449 | +readInternal(table, checkNotNull(index), keys, columns, options), |
| 450 | +bufferRows); |
| 451 | +} |
| 452 | + |
395 | 453 | @Nullable
|
396 | 454 | @Override
|
397 | 455 | public final Struct readRow(String table, Key key, Iterable<String> columns) {
|
@@ -400,6 +458,13 @@ public final Struct readRow(String table, Key key, Iterable<String> columns) {
|
400 | 458 | }
|
401 | 459 | }
|
402 | 460 |
|
| 461 | +@Override |
| 462 | +public final ApiFuture<Struct> readRowAsync(String table, Key key, Iterable<String> columns) { |
| 463 | +try (AsyncResultSet resultSet = readAsync(table, KeySet.singleKey(key), columns)) { |
| 464 | +return consumeSingleRowAsync(resultSet); |
| 465 | +} |
| 466 | +} |
| 467 | + |
403 | 468 | @Nullable
|
404 | 469 | @Override
|
405 | 470 | public final Struct readRowUsingIndex(
|
@@ -409,12 +474,35 @@ public final Struct readRowUsingIndex(
|
409 | 474 | }
|
410 | 475 | }
|
411 | 476 |
|
| 477 | +@Override |
| 478 | +public final ApiFuture<Struct> readRowUsingIndexAsync( |
| 479 | +String table, String index, Key key, Iterable<String> columns) { |
| 480 | +try (AsyncResultSet resultSet = |
| 481 | +readUsingIndexAsync(table, index, KeySet.singleKey(key), columns)) { |
| 482 | +return consumeSingleRowAsync(resultSet); |
| 483 | +} |
| 484 | +} |
| 485 | + |
412 | 486 | @Override
|
413 | 487 | public final ResultSet executeQuery(Statement statement, QueryOption... options) {
|
414 | 488 | return executeQueryInternal(
|
415 | 489 | statement, com.google.spanner.v1.ExecuteSqlRequest.QueryMode.NORMAL, options);
|
416 | 490 | }
|
417 | 491 |
|
| 492 | +@Override |
| 493 | +public ListenableAsyncResultSet executeQueryAsync(Statement statement, QueryOption... options) { |
| 494 | +Options readOptions = Options.fromQueryOptions(options); |
| 495 | +final int bufferRows = |
| 496 | +readOptions.hasBufferRows() |
| 497 | +? readOptions.bufferRows() |
| 498 | +: AsyncResultSetImpl.DEFAULT_BUFFER_SIZE; |
| 499 | +return new AsyncResultSetImpl( |
| 500 | +executorProvider, |
| 501 | +executeQueryInternal( |
| 502 | +statement, com.google.spanner.v1.ExecuteSqlRequest.QueryMode.NORMAL, options), |
| 503 | +bufferRows); |
| 504 | +} |
| 505 | + |
418 | 506 | @Override
|
419 | 507 | public final ResultSet analyzeQuery(Statement statement, QueryAnalyzeMode readContextQueryMode) {
|
420 | 508 | switch (readContextQueryMode) {
|
@@ -666,4 +754,71 @@ private Struct consumeSingleRow(ResultSet resultSet) {
|
666 | 754 | }
|
667 | 755 | return row;
|
668 | 756 | }
|
| 757 | + |
| 758 | +static ApiFuture<Struct> consumeSingleRowAsync(AsyncResultSet resultSet) { |
| 759 | +final SettableApiFuture<Struct> result = SettableApiFuture.create(); |
| 760 | +// We can safely use a directExecutor here, as we will only be consuming one row, and we will |
| 761 | +// not be doing any blocking stuff in the handler. |
| 762 | +final SettableApiFuture<Struct> row = SettableApiFuture.create(); |
| 763 | +ApiFutures.addCallback( |
| 764 | +resultSet.setCallback(MoreExecutors.directExecutor(), ConsumeSingleRowCallback.create(row)), |
| 765 | +new ApiFutureCallback<Void>() { |
| 766 | +@Override |
| 767 | +public void onFailure(Throwable t) { |
| 768 | +result.setException(t); |
| 769 | +} |
| 770 | + |
| 771 | +@Override |
| 772 | +public void onSuccess(Void input) { |
| 773 | +try { |
| 774 | +result.set(row.get()); |
| 775 | +} catch (Throwable t) { |
| 776 | +result.setException(t); |
| 777 | +} |
| 778 | +} |
| 779 | +}, |
| 780 | +MoreExecutors.directExecutor()); |
| 781 | +return result; |
| 782 | +} |
| 783 | + |
| 784 | +/** |
| 785 | +* {@link ReadyCallback} for returning the first row in a result set as a future {@link Struct}. |
| 786 | +*/ |
| 787 | +private static class ConsumeSingleRowCallback implements ReadyCallback { |
| 788 | +private final SettableApiFuture<Struct> result; |
| 789 | +private Struct row; |
| 790 | + |
| 791 | +static ConsumeSingleRowCallback create(SettableApiFuture<Struct> result) { |
| 792 | +return new ConsumeSingleRowCallback(result); |
| 793 | +} |
| 794 | + |
| 795 | +private ConsumeSingleRowCallback(SettableApiFuture<Struct> result) { |
| 796 | +this.result = result; |
| 797 | +} |
| 798 | + |
| 799 | +@Override |
| 800 | +public CallbackResponse cursorReady(AsyncResultSet resultSet) { |
| 801 | +try { |
| 802 | +switch (resultSet.tryNext()) { |
| 803 | +case DONE: |
| 804 | +result.set(row); |
| 805 | +return CallbackResponse.DONE; |
| 806 | +case NOT_READY: |
| 807 | +return CallbackResponse.CONTINUE; |
| 808 | +case OK: |
| 809 | +if (row != null) { |
| 810 | +throw newSpannerException( |
| 811 | +ErrorCode.INTERNAL, "Multiple rows returned for single key"); |
| 812 | +} |
| 813 | +row = resultSet.getCurrentRowAsStruct(); |
| 814 | +return CallbackResponse.CONTINUE; |
| 815 | +default: |
| 816 | +throw new IllegalStateException(); |
| 817 | +} |
| 818 | +} catch (Throwable t) { |
| 819 | +result.setException(t); |
| 820 | +return CallbackResponse.DONE; |
| 821 | +} |
| 822 | +} |
| 823 | +} |
669 | 824 | }
|
0 commit comments