Skip to content

Commit a1ce267

Browse files
feat: support DML auto-batching in Connection API (#3386)
* feat: support DML auto-batching in Connection API Adds support for automatically batching DML statements that are executed on a connection. This can be used in combination with frameworks like Hibernate to improve performance, especially on multi-region instances with high round-trip latencies. * chore: generate libraries at Sun Oct 6 06:00:17 UTC 2024 * feat: add update count verification * fix: add ignored diffs * test: add transaction retry test * chore: cleanup and add comments * chore: generate libraries at Fri Oct 11 15:21:53 UTC 2024 * feat: use connection variables for auto_batch_dml --------- Co-authored-by: cloud-java-bot <cloud-java-bot@google.com>
1 parent 6dfc494 commit a1ce267

23 files changed

+39837
-32551
lines changed

google-cloud-spanner/clirr-ignored-differences.xml

+33
Original file line numberDiff line numberDiff line change
@@ -758,4 +758,37 @@
758758
<className>com/google/cloud/spanner/connection/Connection</className>
759759
<method>boolean isKeepTransactionAlive()</method>
760760
</difference>
761+
762+
<!-- Automatic DML batching -->
763+
<difference>
764+
<differenceType>7012</differenceType>
765+
<className>com/google/cloud/spanner/connection/Connection</className>
766+
<method>void setAutoBatchDml(boolean)</method>
767+
</difference>
768+
<difference>
769+
<differenceType>7012</differenceType>
770+
<className>com/google/cloud/spanner/connection/Connection</className>
771+
<method>boolean isAutoBatchDml()</method>
772+
</difference>
773+
<difference>
774+
<differenceType>7012</differenceType>
775+
<className>com/google/cloud/spanner/connection/Connection</className>
776+
<method>void setAutoBatchDmlUpdateCount(long)</method>
777+
</difference>
778+
<difference>
779+
<differenceType>7012</differenceType>
780+
<className>com/google/cloud/spanner/connection/Connection</className>
781+
<method>long getAutoBatchDmlUpdateCount()</method>
782+
</difference>
783+
<difference>
784+
<differenceType>7012</differenceType>
785+
<className>com/google/cloud/spanner/connection/Connection</className>
786+
<method>void setAutoBatchDmlUpdateCountVerification(boolean)</method>
787+
</difference>
788+
<difference>
789+
<differenceType>7012</differenceType>
790+
<className>com/google/cloud/spanner/connection/Connection</className>
791+
<method>boolean isAutoBatchDmlUpdateCountVerification()</method>
792+
</difference>
793+
761794
</differences>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.spanner;
18+
19+
import com.google.cloud.spanner.connection.Connection;
20+
import java.util.Arrays;
21+
import java.util.stream.Collectors;
22+
23+
/**
24+
* Exception thrown by a {@link Connection} when an automatic DML batch detects that one or more of
25+
* the update counts that it returned during the buffering of DML statements does not match with the
26+
* actual update counts that were returned after execution.
27+
*/
28+
public class DmlBatchUpdateCountVerificationFailedException extends AbortedException {
29+
private static final long serialVersionUID = 1L;
30+
31+
private final long[] expected;
32+
33+
private final long[] actual;
34+
35+
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
36+
DmlBatchUpdateCountVerificationFailedException(
37+
DoNotConstructDirectly token, long[] expected, long[] actual) {
38+
super(
39+
token,
40+
String.format(
41+
"Actual update counts that were returned during execution do not match the previously returned update counts.\n"
42+
+ "Expected: %s\n"
43+
+ "Actual: %s\n"
44+
+ "Set auto_batch_dml_update_count_verification to false to skip this verification.",
45+
Arrays.stream(expected).mapToObj(Long::toString).collect(Collectors.joining()),
46+
Arrays.stream(actual).mapToObj(Long::toString).collect(Collectors.joining())),
47+
/* cause = */ null);
48+
this.expected = expected;
49+
this.actual = actual;
50+
}
51+
52+
/**
53+
* The expected update counts. These were returned to the client application when the DML
54+
* statements were buffered.
55+
*/
56+
public long[] getExpected() {
57+
return Arrays.copyOf(this.expected, this.expected.length);
58+
}
59+
60+
/**
61+
* The actual update counts. These were returned by Spanner to the client when the DML statements
62+
* were actually executed, and are the update counts that the client application would have
63+
* received if auto-batching had not been enabled.
64+
*/
65+
public long[] getActual() {
66+
return Arrays.copyOf(this.actual, this.actual.length);
67+
}
68+
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java

+7
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ public static SpannerBatchUpdateException newSpannerBatchUpdateException(
116116
return new SpannerBatchUpdateException(token, code, message, updateCounts);
117117
}
118118

119+
/** Constructs a specific error that */
120+
public static DmlBatchUpdateCountVerificationFailedException
121+
newDmlBatchUpdateCountVerificationFailedException(long[] expected, long[] actual) {
122+
return new DmlBatchUpdateCountVerificationFailedException(
123+
DoNotConstructDirectly.ALLOWED, expected, actual);
124+
}
125+
119126
/**
120127
* Constructs a specific aborted exception that should only be thrown by a connection after an
121128
* internal retry aborted due to concurrent modifications.

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementValueConverters.java

+29
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,35 @@ public Integer convert(String value) {
177177
}
178178
}
179179

180+
/** Converter from string to a long. */
181+
static class LongConverter implements ClientSideStatementValueConverter<Long> {
182+
static final LongConverter INSTANCE = new LongConverter();
183+
184+
private LongConverter() {}
185+
186+
/** Constructor needed for reflection. */
187+
public LongConverter(String allowedValues) {}
188+
189+
@Override
190+
public Class<Long> getParameterClass() {
191+
return Long.class;
192+
}
193+
194+
@Override
195+
public Long convert(String value) {
196+
try {
197+
long res = Long.parseLong(value);
198+
if (res < 0) {
199+
// The conventions for these converters is to return null if the value is invalid.
200+
return null;
201+
}
202+
return res;
203+
} catch (Exception ignore) {
204+
return null;
205+
}
206+
}
207+
}
208+
180209
/** Converter from string to {@link Duration}. */
181210
static class DurationConverter implements ClientSideStatementValueConverter<Duration> {
182211
static final DurationConverter INSTANCE =

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java

+39
Original file line numberDiff line numberDiff line change
@@ -1165,6 +1165,45 @@ default StatementResult execute(Statement statement, Set<ResultType> allowedResu
11651165
*/
11661166
ResultSet analyzeQuery(Statement query, QueryAnalyzeMode queryMode);
11671167

1168+
/**
1169+
* Enables or disables automatic batching of DML statements. When enabled, DML statements that are
1170+
* executed on this connection will be buffered in memory instead of actually being executed. The
1171+
* buffered DML statements are flushed to Spanner when a statement that cannot be part of a DML
1172+
* batch is executed on the connection. This can be a query, a DDL statement with a THEN RETURN
1173+
* clause, or a Commit call. The update count that is returned for DML statements that are
1174+
* buffered is determined by the value that has been set with {@link
1175+
* #setAutoBatchDmlUpdateCount(long)}. The default is 1. The connection verifies that the update
1176+
* counts that were returned while buffering DML statements match the actual update counts that
1177+
* are returned by Spanner when the batch is executed. This verification can be disabled by
1178+
* calling {@link #setAutoBatchDmlUpdateCountVerification(boolean)}.
1179+
*/
1180+
void setAutoBatchDml(boolean autoBatchDml);
1181+
1182+
/** Returns whether automatic DML batching is enabled on this connection. */
1183+
boolean isAutoBatchDml();
1184+
1185+
/**
1186+
* Sets the update count that is returned for DML statements that are buffered during an automatic
1187+
* DML batch. This value is only used if {@link #isAutoBatchDml()} is enabled.
1188+
*/
1189+
void setAutoBatchDmlUpdateCount(long updateCount);
1190+
1191+
/**
1192+
* Returns the update count that is returned for DML statements that are buffered during an
1193+
* automatic DML batch.
1194+
*/
1195+
long getAutoBatchDmlUpdateCount();
1196+
1197+
/**
1198+
* Sets whether the update count that is returned by Spanner after executing an automatic DML
1199+
* batch should be verified against the update counts that were returned during the buffering of
1200+
* those statements.
1201+
*/
1202+
void setAutoBatchDmlUpdateCountVerification(boolean verification);
1203+
1204+
/** Indicates whether the update counts of automatic DML batches should be verified. */
1205+
boolean isAutoBatchDmlUpdateCountVerification();
1206+
11681207
/**
11691208
* Enable data boost for partitioned queries. See also {@link #partitionQuery(Statement,
11701209
* PartitionOptions, QueryOption...)}

0 commit comments

Comments
 (0)