4646import com .google .spanner .v1 .ExecuteBatchDmlResponse ;
4747import com .google .spanner .v1 .ExecuteSqlRequest ;
4848import com .google .spanner .v1 .ExecuteSqlRequest .QueryMode ;
49+ import com .google .spanner .v1 .MultiplexedSessionPrecommitToken ;
4950import com .google .spanner .v1 .RequestOptions ;
5051import com .google .spanner .v1 .ResultSet ;
5152import com .google .spanner .v1 .ResultSetStats ;
@@ -179,6 +180,11 @@ public void removeListener(Runnable listener) {
179180 @ GuardedBy ("committingLock" )
180181 private volatile boolean committing ;
181182
183+ private final Object precommitTokenLock = new Object ();
184+
185+ @ GuardedBy ("precommitTokenLock" )
186+ private MultiplexedSessionPrecommitToken latestPrecommitToken ;
187+
182188 @ GuardedBy ("lock" )
183189 private volatile SettableApiFuture <Void > finishedAsyncOperations = SettableApiFuture .create ();
184190
@@ -439,6 +445,10 @@ public void run() {
439445 }
440446 requestBuilder .setRequestOptions (requestOptionsBuilder .build ());
441447 }
448+ if (session .getIsMultiplexed () && getLatestPrecommitToken () != null ) {
449+ // Set the precommit token in the CommitRequest for multiplexed sessions.
450+ requestBuilder .setPrecommitToken (getLatestPrecommitToken ());
451+ }
442452 final CommitRequest commitRequest = requestBuilder .build ();
443453 span .addAnnotation ("Starting Commit" );
444454 final ApiFuture <com .google .spanner .v1 .CommitResponse > commitFuture ;
@@ -643,6 +653,25 @@ public void onTransactionMetadata(Transaction transaction, boolean shouldInclude
643653 }
644654 }
645655
656+ /**
657+ * In read-write transactions, the precommit token with the highest sequence number from this
658+ * transaction attempt will be tracked and included in the
659+ * [Commit][google.spanner.v1.Spanner.Commit] request for the transaction.
660+ */
661+ @ Override
662+ public void onPrecommitToken (MultiplexedSessionPrecommitToken token ) {
663+ if (token == null ) {
664+ return ;
665+ }
666+ synchronized (precommitTokenLock ) {
667+ if (this .latestPrecommitToken == null
668+ || token .getSeqNum () > this .latestPrecommitToken .getSeqNum ()) {
669+ this .latestPrecommitToken = token ;
670+ txnLogger .log (Level .FINE , "Updating precommit token to " + this .latestPrecommitToken );
671+ }
672+ }
673+ }
674+
646675 @ Nullable
647676 String getTransactionTag () {
648677 if (this .options .hasTag ()) {
@@ -651,6 +680,13 @@ String getTransactionTag() {
651680 return null ;
652681 }
653682
683+ @ Nullable
684+ MultiplexedSessionPrecommitToken getLatestPrecommitToken () {
685+ synchronized (precommitTokenLock ) {
686+ return this .latestPrecommitToken ;
687+ }
688+ }
689+
654690 @ Override
655691 public SpannerException onError (SpannerException e , boolean withBeginTransaction ) {
656692 e = super .onError (e , withBeginTransaction );
@@ -829,6 +865,9 @@ private ResultSet internalExecuteUpdate(
829865 throw new IllegalArgumentException (
830866 "DML response missing stats possibly due to non-DML statement as input" );
831867 }
868+ if (resultSet .hasPrecommitToken ()) {
869+ onPrecommitToken (resultSet .getPrecommitToken ());
870+ }
832871 return resultSet ;
833872 } catch (Throwable t ) {
834873 throw onError (
@@ -903,6 +942,9 @@ public ApiFuture<Long> executeUpdateAsync(Statement statement, UpdateOption... u
903942 resultSet .get ().getMetadata ().getTransaction (),
904943 builder .getTransaction ().hasBegin ());
905944 }
945+ if (resultSet .get ().hasPrecommitToken ()) {
946+ onPrecommitToken (resultSet .get ().getPrecommitToken ());
947+ }
906948 } catch (Throwable e ) {
907949 // Ignore this error here as it is handled by the future that is returned by the
908950 // executeUpdateAsync method.
@@ -958,6 +1000,10 @@ public long[] batchUpdate(Iterable<Statement> statements, UpdateOption... update
9581000 }
9591001 }
9601002
1003+ if (response .hasPrecommitToken ()) {
1004+ onPrecommitToken (response .getPrecommitToken ());
1005+ }
1006+
9611007 // If one of the DML statements was aborted, we should throw an aborted exception.
9621008 // In all other cases, we should throw a BatchUpdateException.
9631009 if (response .getStatus ().getCode () == Code .ABORTED_VALUE ) {
@@ -1022,6 +1068,9 @@ public ApiFuture<long[]> batchUpdateAsync(
10221068 builder .getTransaction ().hasBegin ());
10231069 }
10241070 }
1071+ if (batchDmlResponse .hasPrecommitToken ()) {
1072+ onPrecommitToken (batchDmlResponse .getPrecommitToken ());
1073+ }
10251074 // If one of the DML statements was aborted, we should throw an aborted exception.
10261075 // In all other cases, we should throw a BatchUpdateException.
10271076 if (batchDmlResponse .getStatus ().getCode () == Code .ABORTED_VALUE ) {
0 commit comments