3030import com .google .cloud .spanner .Options .ReadOption ;
3131import com .google .cloud .spanner .SessionImpl .SessionTransaction ;
3232import com .google .cloud .spanner .spi .v1 .SpannerRpc ;
33+ import com .google .common .annotations .VisibleForTesting ;
3334import com .google .protobuf .ByteString ;
3435import com .google .spanner .v1 .BeginTransactionRequest ;
3536import com .google .spanner .v1 .ExecuteBatchDmlRequest ;
3637import com .google .spanner .v1 .ExecuteSqlRequest ;
3738import com .google .spanner .v1 .ExecuteSqlRequest .QueryMode ;
39+ import com .google .spanner .v1 .ExecuteSqlRequest .QueryOptions ;
3840import com .google .spanner .v1 .PartialResultSet ;
3941import com .google .spanner .v1 .ReadRequest ;
4042import com .google .spanner .v1 .Transaction ;
5355 */
5456abstract class AbstractReadContext
5557 implements ReadContext , AbstractResultSet .Listener , SessionTransaction {
58+
59+ abstract static class Builder <B extends Builder <?, T >, T extends AbstractReadContext > {
60+ private SessionImpl session ;
61+ private SpannerRpc rpc ;
62+ private Span span = Tracing .getTracer ().getCurrentSpan ();
63+ private int defaultPrefetchChunks = SpannerOptions .Builder .DEFAULT_PREFETCH_CHUNKS ;
64+ private QueryOptions defaultQueryOptions = SpannerOptions .Builder .DEFAULT_QUERY_OPTIONS ;
65+
66+ Builder () {}
67+
68+ @ SuppressWarnings ("unchecked" )
69+ B self () {
70+ return (B ) this ;
71+ }
72+
73+ B setSession (SessionImpl session ) {
74+ this .session = session ;
75+ return self ();
76+ }
77+
78+ B setRpc (SpannerRpc rpc ) {
79+ this .rpc = rpc ;
80+ return self ();
81+ }
82+
83+ B setSpan (Span span ) {
84+ this .span = span ;
85+ return self ();
86+ }
87+
88+ B setDefaultPrefetchChunks (int defaultPrefetchChunks ) {
89+ this .defaultPrefetchChunks = defaultPrefetchChunks ;
90+ return self ();
91+ }
92+
93+ B setDefaultQueryOptions (QueryOptions defaultQueryOptions ) {
94+ this .defaultQueryOptions = defaultQueryOptions ;
95+ return self ();
96+ }
97+
98+ abstract T build ();
99+ }
100+
56101 /**
57102 * A {@code ReadContext} for standalone reads. This can only be used for a single operation, since
58103 * each standalone read may see a different timestamp of Cloud Spanner data.
59104 */
60105 static class SingleReadContext extends AbstractReadContext {
106+ static class Builder extends AbstractReadContext .Builder <Builder , SingleReadContext > {
107+ private TimestampBound bound ;
108+
109+ private Builder () {}
110+
111+ Builder setTimestampBound (TimestampBound bound ) {
112+ this .bound = bound ;
113+ return self ();
114+ }
115+
116+ @ Override
117+ SingleReadContext build () {
118+ return new SingleReadContext (this );
119+ }
120+
121+ SingleUseReadOnlyTransaction buildSingleUseReadOnlyTransaction () {
122+ return new SingleUseReadOnlyTransaction (this );
123+ }
124+ }
125+
126+ static Builder newBuilder () {
127+ return new Builder ();
128+ }
129+
61130 final TimestampBound bound ;
62131
63132 @ GuardedBy ("lock" )
64133 private boolean used ;
65134
66- SingleReadContext (
67- SessionImpl session , TimestampBound bound , SpannerRpc rpc , int defaultPrefetchChunks ) {
68- super (session , rpc , defaultPrefetchChunks );
69- this .bound = bound ;
135+ private SingleReadContext (Builder builder ) {
136+ super (builder );
137+ this .bound = builder .bound ;
70138 }
71139
72140 @ GuardedBy ("lock" )
@@ -99,9 +167,8 @@ static class SingleUseReadOnlyTransaction extends SingleReadContext
99167 @ GuardedBy ("lock" )
100168 private Timestamp timestamp ;
101169
102- SingleUseReadOnlyTransaction (
103- SessionImpl session , TimestampBound bound , SpannerRpc rpc , int defaultPrefetchChunks ) {
104- super (session , bound , rpc , defaultPrefetchChunks );
170+ private SingleUseReadOnlyTransaction (SingleReadContext .Builder builder ) {
171+ super (builder );
105172 }
106173
107174 @ Override
@@ -139,6 +206,38 @@ public void onTransactionMetadata(Transaction transaction) {
139206
140207 static class MultiUseReadOnlyTransaction extends AbstractReadContext
141208 implements ReadOnlyTransaction {
209+ static class Builder extends AbstractReadContext .Builder <Builder , MultiUseReadOnlyTransaction > {
210+ private TimestampBound bound ;
211+ private Timestamp timestamp ;
212+ private ByteString transactionId ;
213+
214+ private Builder () {}
215+
216+ Builder setTimestampBound (TimestampBound bound ) {
217+ this .bound = bound ;
218+ return this ;
219+ }
220+
221+ Builder setTimestamp (Timestamp timestamp ) {
222+ this .timestamp = timestamp ;
223+ return this ;
224+ }
225+
226+ Builder setTransactionId (ByteString transactionId ) {
227+ this .transactionId = transactionId ;
228+ return this ;
229+ }
230+
231+ @ Override
232+ MultiUseReadOnlyTransaction build () {
233+ return new MultiUseReadOnlyTransaction (this );
234+ }
235+ }
236+
237+ static Builder newBuilder () {
238+ return new Builder ();
239+ }
240+
142241 private TimestampBound bound ;
143242 private final Object txnLock = new Object ();
144243
@@ -148,27 +247,24 @@ static class MultiUseReadOnlyTransaction extends AbstractReadContext
148247 @ GuardedBy ("txnLock" )
149248 private ByteString transactionId ;
150249
151- MultiUseReadOnlyTransaction (
152- SessionImpl session , TimestampBound bound , SpannerRpc rpc , int defaultPrefetchChunks ) {
153- super (session , rpc , defaultPrefetchChunks );
250+ MultiUseReadOnlyTransaction (Builder builder ) {
251+ super (builder );
154252 checkArgument (
155- bound .getMode () != TimestampBound .Mode .MAX_STALENESS
156- && bound .getMode () != TimestampBound .Mode .MIN_READ_TIMESTAMP ,
157- "Bounded staleness mode %s is not supported for multi-use read-only transactions."
158- + " Create a single-use read or read-only transaction instead." ,
159- bound .getMode ());
160- this .bound = bound ;
161- }
162-
163- MultiUseReadOnlyTransaction (
164- SessionImpl session ,
165- ByteString transactionId ,
166- Timestamp timestamp ,
167- SpannerRpc rpc ,
168- int defaultPrefetchChunks ) {
169- super (session , rpc , defaultPrefetchChunks );
170- this .transactionId = transactionId ;
171- this .timestamp = timestamp ;
253+ !(builder .bound != null && builder .transactionId != null )
254+ && !(builder .bound == null && builder .transactionId == null ),
255+ "Either TimestampBound or TransactionId must be specified" );
256+ if (builder .bound != null ) {
257+ checkArgument (
258+ builder .bound .getMode () != TimestampBound .Mode .MAX_STALENESS
259+ && builder .bound .getMode () != TimestampBound .Mode .MIN_READ_TIMESTAMP ,
260+ "Bounded staleness mode %s is not supported for multi-use read-only transactions."
261+ + " Create a single-use read or read-only transaction instead." ,
262+ builder .bound .getMode ());
263+ this .bound = builder .bound ;
264+ } else {
265+ this .timestamp = builder .timestamp ;
266+ this .transactionId = builder .transactionId ;
267+ }
172268 }
173269
174270 @ Override
@@ -256,6 +352,7 @@ void initTransaction() {
256352 final SpannerRpc rpc ;
257353 final Span span ;
258354 private final int defaultPrefetchChunks ;
355+ private final QueryOptions defaultQueryOptions ;
259356
260357 @ GuardedBy ("lock" )
261358 private boolean isValid = true ;
@@ -271,16 +368,12 @@ void initTransaction() {
271368 // much more frequently.
272369 private static final int MAX_BUFFERED_CHUNKS = 512 ;
273370
274- AbstractReadContext (SessionImpl session , SpannerRpc rpc , int defaultPrefetchChunks ) {
275- this (session , rpc , defaultPrefetchChunks , Tracing .getTracer ().getCurrentSpan ());
276- }
277-
278- private AbstractReadContext (
279- SessionImpl session , SpannerRpc rpc , int defaultPrefetchChunks , Span span ) {
280- this .session = session ;
281- this .rpc = rpc ;
282- this .defaultPrefetchChunks = defaultPrefetchChunks ;
283- this .span = span ;
371+ AbstractReadContext (Builder <?, ?> builder ) {
372+ this .session = builder .session ;
373+ this .rpc = builder .rpc ;
374+ this .defaultPrefetchChunks = builder .defaultPrefetchChunks ;
375+ this .defaultQueryOptions = builder .defaultQueryOptions ;
376+ this .span = builder .span ;
284377 }
285378
286379 long getSeqNo () {
@@ -341,9 +434,36 @@ private ResultSet executeQueryInternal(
341434 Statement statement ,
342435 com .google .spanner .v1 .ExecuteSqlRequest .QueryMode queryMode ,
343436 QueryOption ... options ) {
344- Options readOptions = Options .fromQueryOptions (options );
437+ Options queryOptions = Options .fromQueryOptions (options );
345438 return executeQueryInternalWithOptions (
346- statement , queryMode , readOptions , null /*partitionToken*/ );
439+ statement , queryMode , queryOptions , null /*partitionToken*/ );
440+ }
441+
442+ /**
443+ * Determines the {@link QueryOptions} to use for a query. This is determined using the following
444+ * precedence:
445+ *
446+ * <ol>
447+ * <li>Specific {@link QueryOptions} passed in for this query.
448+ * <li>Any value specified in a valid environment variable when the {@link SpannerOptions}
449+ * instance was created.
450+ * <li>The default {@link SpannerOptions#getDefaultQueryOptions()} specified for the database
451+ * where the query is executed.
452+ * </ol>
453+ */
454+ @ VisibleForTesting
455+ QueryOptions buildQueryOptions (QueryOptions requestOptions ) {
456+ // Shortcut for the most common return value.
457+ if (defaultQueryOptions .equals (QueryOptions .getDefaultInstance ()) && requestOptions == null ) {
458+ return QueryOptions .getDefaultInstance ();
459+ }
460+ // Create a builder based on the default query options.
461+ QueryOptions .Builder builder = defaultQueryOptions .toBuilder ();
462+ // Then overwrite with specific options for this query.
463+ if (requestOptions != null ) {
464+ builder .mergeFrom (requestOptions );
465+ }
466+ return builder .build ();
347467 }
348468
349469 ExecuteSqlRequest .Builder getExecuteSqlRequestBuilder (Statement statement , QueryMode queryMode ) {
@@ -365,6 +485,7 @@ ExecuteSqlRequest.Builder getExecuteSqlRequestBuilder(Statement statement, Query
365485 builder .setTransaction (selector );
366486 }
367487 builder .setSeqno (getSeqNo ());
488+ builder .setQueryOptions (buildQueryOptions (statement .getQueryOptions ()));
368489 return builder ;
369490 }
370491
@@ -400,15 +521,15 @@ ExecuteBatchDmlRequest.Builder getExecuteBatchDmlRequestBuilder(Iterable<Stateme
400521 ResultSet executeQueryInternalWithOptions (
401522 Statement statement ,
402523 com .google .spanner .v1 .ExecuteSqlRequest .QueryMode queryMode ,
403- Options readOptions ,
524+ Options options ,
404525 ByteString partitionToken ) {
405526 beforeReadOrQuery ();
406527 final ExecuteSqlRequest .Builder request = getExecuteSqlRequestBuilder (statement , queryMode );
407528 if (partitionToken != null ) {
408529 request .setPartitionToken (partitionToken );
409530 }
410531 final int prefetchChunks =
411- readOptions .hasPrefetchChunks () ? readOptions .prefetchChunks () : defaultPrefetchChunks ;
532+ options .hasPrefetchChunks () ? options .prefetchChunks () : defaultPrefetchChunks ;
412533 ResumableStreamIterator stream =
413534 new ResumableStreamIterator (MAX_BUFFERED_CHUNKS , SpannerImpl .QUERY , span ) {
414535 @ Override
0 commit comments