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 ;
@@ -171,6 +172,11 @@ public void removeListener(Runnable listener) {
171172 @ GuardedBy ("committingLock" )
172173 private volatile boolean committing ;
173174
175+ private final Object precommitTokenLock = new Object ();
176+
177+ @ GuardedBy ("precommitTokenLock" )
178+ private MultiplexedSessionPrecommitToken latestPrecommitToken ;
179+
174180 @ GuardedBy ("lock" )
175181 private volatile SettableApiFuture <Void > finishedAsyncOperations = SettableApiFuture .create ();
176182
@@ -625,6 +631,24 @@ public void onTransactionMetadata(Transaction transaction, boolean shouldInclude
625631 }
626632 }
627633
634+ /**
635+ * In read-write transactions, the precommit token with the highest sequence number from this
636+ * transaction attempt will be tracked and included in the
637+ * [Commit][google.spanner.v1.Spanner.Commit] request for the transaction.
638+ */
639+ @ Override
640+ public void onPrecommitToken (MultiplexedSessionPrecommitToken token ) {
641+ if (token == null ) return ;
642+ synchronized (precommitTokenLock ) {
643+ if (this .latestPrecommitToken == null
644+ || token .getSeqNum () > this .latestPrecommitToken .getSeqNum ()) {
645+ this .latestPrecommitToken = token ;
646+ System .out .println ("Updating precommit token to " + this .latestPrecommitToken );
647+ txnLogger .log (Level .ALL , "Updating precommit token to " + this .latestPrecommitToken );
648+ }
649+ }
650+ }
651+
628652 @ Nullable
629653 String getTransactionTag () {
630654 if (this .options .hasTag ()) {
@@ -633,6 +657,13 @@ String getTransactionTag() {
633657 return null ;
634658 }
635659
660+ @ Nullable
661+ MultiplexedSessionPrecommitToken getLatestPrecommitToken () {
662+ synchronized (precommitTokenLock ) {
663+ return this .latestPrecommitToken ;
664+ }
665+ }
666+
636667 @ Override
637668 public SpannerException onError (SpannerException e , boolean withBeginTransaction ) {
638669 e = super .onError (e , withBeginTransaction );
@@ -811,6 +842,9 @@ private ResultSet internalExecuteUpdate(
811842 throw new IllegalArgumentException (
812843 "DML response missing stats possibly due to non-DML statement as input" );
813844 }
845+ if (resultSet .hasPrecommitToken ()) {
846+ onPrecommitToken (resultSet .getPrecommitToken ());
847+ }
814848 return resultSet ;
815849 } catch (Throwable t ) {
816850 throw onError (
@@ -885,6 +919,9 @@ public ApiFuture<Long> executeUpdateAsync(Statement statement, UpdateOption... u
885919 resultSet .get ().getMetadata ().getTransaction (),
886920 builder .getTransaction ().hasBegin ());
887921 }
922+ if (resultSet .get ().hasPrecommitToken ()) {
923+ onPrecommitToken (resultSet .get ().getPrecommitToken ());
924+ }
888925 } catch (Throwable e ) {
889926 // Ignore this error here as it is handled by the future that is returned by the
890927 // executeUpdateAsync method.
@@ -940,6 +977,11 @@ public long[] batchUpdate(Iterable<Statement> statements, UpdateOption... update
940977 }
941978 }
942979
980+ // TODO(sriharshach): check if we need to get precommit_token from response.getResultSets
981+ if (response .hasPrecommitToken ()) {
982+ onPrecommitToken (response .getPrecommitToken ());
983+ }
984+
943985 // If one of the DML statements was aborted, we should throw an aborted exception.
944986 // In all other cases, we should throw a BatchUpdateException.
945987 if (response .getStatus ().getCode () == Code .ABORTED_VALUE ) {
@@ -1004,6 +1046,9 @@ public ApiFuture<long[]> batchUpdateAsync(
10041046 builder .getTransaction ().hasBegin ());
10051047 }
10061048 }
1049+ if (batchDmlResponse .hasPrecommitToken ()) {
1050+ onPrecommitToken (batchDmlResponse .getPrecommitToken ());
1051+ }
10071052 // If one of the DML statements was aborted, we should throw an aborted exception.
10081053 // In all other cases, we should throw a BatchUpdateException.
10091054 if (batchDmlResponse .getStatus ().getCode () == Code .ABORTED_VALUE ) {
0 commit comments