2121import com .google .spanner .v1 .ReadRequest .LockHint ;
2222import com .google .spanner .v1 .ReadRequest .OrderBy ;
2323import com .google .spanner .v1 .RequestOptions .Priority ;
24+ import com .google .spanner .v1 .TransactionOptions .IsolationLevel ;
2425import java .io .Serializable ;
2526import java .time .Duration ;
27+ import java .util .Arrays ;
2628import java .util .Objects ;
29+ import java .util .function .Predicate ;
30+ import java .util .stream .Stream ;
2731
2832/** Specifies options for various spanner operations */
2933public final class Options implements Serializable {
@@ -131,7 +135,29 @@ public interface UpdateAdminApiOption extends AdminApiOption {}
131135 public interface QueryOption {}
132136
133137 /** Marker interface to mark options applicable to write operations */
134- public interface TransactionOption {}
138+ public interface TransactionOption {
139+ Predicate <TransactionOption > isValidIsolationLevelOption =
140+ txnOption ->
141+ txnOption instanceof RepeatableReadOption || txnOption instanceof SerializableOption ;
142+
143+ /**
144+ * Combines two arrays of TransactionOption, with primaryOptions taking precedence in case of
145+ * conflicts. Note that {@link
146+ * com.google.cloud.spanner.SpannerOptions.Builder.TransactionOptions} supports only the {@link
147+ * IsolationLevel} TransactionOption, meaning spannerOptions will contain a maximum of one
148+ * TransactionOption.
149+ */
150+ static TransactionOption [] combine (
151+ TransactionOption [] primaryOptions , TransactionOption [] spannerOptions ) {
152+ if (spannerOptions == null
153+ || Arrays .stream (primaryOptions ).anyMatch (isValidIsolationLevelOption )) {
154+ return primaryOptions ;
155+ } else {
156+ return Stream .concat (Arrays .stream (primaryOptions ), Arrays .stream (spannerOptions ))
157+ .toArray (TransactionOption []::new );
158+ }
159+ }
160+ }
135161
136162 /** Marker interface to mark options applicable to update operation. */
137163 public interface UpdateOption {}
@@ -159,6 +185,22 @@ public static TransactionOption optimisticLock() {
159185 return OPTIMISTIC_LOCK_OPTION ;
160186 }
161187
188+ /**
189+ * Specifying this instructs the transaction to request Repeatable Read Isolation Level from the
190+ * backend.
191+ */
192+ public static TransactionOption repeatableReadIsolationLevel () {
193+ return REPEATABLE_READ_OPTION ;
194+ }
195+
196+ /**
197+ * Specifying this instructs the transaction to request Serializable Isolation Level from the
198+ * backend.
199+ */
200+ public static TransactionOption serializableIsolationLevel () {
201+ return SERIALIZABLE_OPTION ;
202+ }
203+
162204 /**
163205 * Specifying this instructs the transaction to be excluded from being recorded in change streams
164206 * with the DDL option `allow_txn_exclusion=true`. This does not exclude the transaction from
@@ -490,6 +532,26 @@ void appendToOptions(Options options) {
490532 }
491533 }
492534
535+ /** Option to request REPEATABLE READ isolation level for read/write transactions. */
536+ static final class RepeatableReadOption extends InternalOption implements TransactionOption {
537+ @ Override
538+ void appendToOptions (Options options ) {
539+ options .isolationLevel = IsolationLevel .REPEATABLE_READ ;
540+ }
541+ }
542+
543+ static final RepeatableReadOption REPEATABLE_READ_OPTION = new RepeatableReadOption ();
544+
545+ /** Option to request SERIALIZABLE isolation level for read/write transactions. */
546+ static final class SerializableOption extends InternalOption implements TransactionOption {
547+ @ Override
548+ void appendToOptions (Options options ) {
549+ options .isolationLevel = IsolationLevel .SERIALIZABLE ;
550+ }
551+ }
552+
553+ static final SerializableOption SERIALIZABLE_OPTION = new SerializableOption ();
554+
493555 private boolean withCommitStats ;
494556
495557 private Duration maxCommitDelay ;
@@ -512,6 +574,7 @@ void appendToOptions(Options options) {
512574 private RpcOrderBy orderBy ;
513575 private RpcLockHint lockHint ;
514576 private Boolean lastStatement ;
577+ private IsolationLevel isolationLevel ;
515578
516579 // Construction is via factory methods below.
517580 private Options () {}
@@ -664,6 +727,10 @@ LockHint lockHint() {
664727 return lockHint == null ? null : lockHint .proto ;
665728 }
666729
730+ IsolationLevel isolationLevel () {
731+ return isolationLevel ;
732+ }
733+
667734 @ Override
668735 public String toString () {
669736 StringBuilder b = new StringBuilder ();
@@ -726,6 +793,9 @@ public String toString() {
726793 if (lockHint != null ) {
727794 b .append ("lockHint: " ).append (lockHint ).append (' ' );
728795 }
796+ if (isolationLevel != null ) {
797+ b .append ("isolationLevel: " ).append (isolationLevel ).append (' ' );
798+ }
729799 return b .toString ();
730800 }
731801
@@ -767,7 +837,8 @@ public boolean equals(Object o) {
767837 && Objects .equals (directedReadOptions (), that .directedReadOptions ())
768838 && Objects .equals (orderBy (), that .orderBy ())
769839 && Objects .equals (isLastStatement (), that .isLastStatement ())
770- && Objects .equals (lockHint (), that .lockHint ());
840+ && Objects .equals (lockHint (), that .lockHint ())
841+ && Objects .equals (isolationLevel (), that .isolationLevel ());
771842 }
772843
773844 @ Override
@@ -833,6 +904,9 @@ public int hashCode() {
833904 if (lockHint != null ) {
834905 result = 31 * result + lockHint .hashCode ();
835906 }
907+ if (isolationLevel != null ) {
908+ result = 31 * result + isolationLevel .hashCode ();
909+ }
836910 return result ;
837911 }
838912
0 commit comments