Skip to content

Commit 7adbfa6

Browse files
authored
refactor: use java.time.Duration in Connection API (#3324)
Use java.time.Duration internally in the Connection API, as that is the value that is used in the public API of Connection. For example, the `Connection#setMaxCommitDelay(java.time.Duration)` method uses this type in the public API. Refactoring the internal classes to also use this type makes it easier to plug these properties into a generic framework for connection state.
1 parent 654835f commit 7adbfa6

15 files changed

+13314
-8991
lines changed

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

Lines changed: 32 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package com.google.cloud.spanner.connection;
1818

19+
import static com.google.cloud.spanner.connection.ReadOnlyStalenessUtil.parseTimeUnit;
20+
import static com.google.cloud.spanner.connection.ReadOnlyStalenessUtil.toChronoUnit;
21+
1922
import com.google.cloud.spanner.ErrorCode;
2023
import com.google.cloud.spanner.Options.RpcPriority;
2124
import com.google.cloud.spanner.SpannerException;
@@ -27,15 +30,14 @@
2730
import com.google.common.base.Function;
2831
import com.google.common.base.Preconditions;
2932
import com.google.common.base.Strings;
30-
import com.google.protobuf.Duration;
31-
import com.google.protobuf.util.Durations;
3233
import com.google.spanner.v1.DirectedReadOptions;
34+
import java.time.Duration;
35+
import java.time.temporal.ChronoUnit;
3336
import java.util.Base64;
3437
import java.util.EnumSet;
3538
import java.util.HashMap;
3639
import java.util.Locale;
3740
import java.util.Map;
38-
import java.util.concurrent.TimeUnit;
3941
import java.util.regex.Matcher;
4042
import java.util.regex.Pattern;
4143

@@ -165,9 +167,16 @@ public Integer convert(String value) {
165167

166168
/** Converter from string to {@link Duration}. */
167169
static class DurationConverter implements ClientSideStatementValueConverter<Duration> {
170+
private final String resetValue;
171+
168172
private final Pattern allowedValues;
169173

170174
public DurationConverter(String allowedValues) {
175+
this("NULL", allowedValues);
176+
}
177+
178+
DurationConverter(String resetValue, String allowedValues) {
179+
this.resetValue = Preconditions.checkNotNull(resetValue);
171180
// Remove the parentheses from the beginning and end.
172181
this.allowedValues =
173182
Pattern.compile(
@@ -183,62 +192,35 @@ public Class<Duration> getParameterClass() {
183192
public Duration convert(String value) {
184193
Matcher matcher = allowedValues.matcher(value);
185194
if (matcher.find()) {
186-
if (matcher.group(0).equalsIgnoreCase("null")) {
187-
return Durations.fromNanos(0L);
195+
if (value.trim().equalsIgnoreCase(resetValue)) {
196+
return Duration.ZERO;
188197
} else {
189-
Duration duration =
190-
ReadOnlyStalenessUtil.createDuration(
191-
Long.parseLong(matcher.group(1)),
192-
ReadOnlyStalenessUtil.parseTimeUnit(matcher.group(2)));
193-
if (duration.getSeconds() == 0L && duration.getNanos() == 0) {
198+
try {
199+
Duration duration;
200+
if (matcher.group(1) != null && matcher.group(2) != null) {
201+
ChronoUnit unit = toChronoUnit(parseTimeUnit(matcher.group(2)));
202+
duration = Duration.of(Long.parseLong(matcher.group(1)), unit);
203+
} else {
204+
duration = Duration.ofMillis(Long.parseLong(value.trim()));
205+
}
206+
if (duration.isZero()) {
207+
return null;
208+
}
209+
return duration;
210+
} catch (NumberFormatException exception) {
211+
// Converters should return null for invalid values.
194212
return null;
195213
}
196-
return duration;
197214
}
198215
}
199216
return null;
200217
}
201218
}
202219

203220
/** Converter from string to {@link Duration}. */
204-
static class PgDurationConverter implements ClientSideStatementValueConverter<Duration> {
205-
private final Pattern allowedValues;
206-
221+
static class PgDurationConverter extends DurationConverter {
207222
public PgDurationConverter(String allowedValues) {
208-
// Remove the parentheses from the beginning and end.
209-
this.allowedValues =
210-
Pattern.compile(
211-
"(?is)\\A" + allowedValues.substring(1, allowedValues.length() - 1) + "\\z");
212-
}
213-
214-
@Override
215-
public Class<Duration> getParameterClass() {
216-
return Duration.class;
217-
}
218-
219-
@Override
220-
public Duration convert(String value) {
221-
Matcher matcher = allowedValues.matcher(value);
222-
if (matcher.find()) {
223-
Duration duration;
224-
if (matcher.group(0).equalsIgnoreCase("default")) {
225-
return Durations.fromNanos(0L);
226-
} else if (matcher.group(2) == null) {
227-
duration =
228-
ReadOnlyStalenessUtil.createDuration(
229-
Long.parseLong(matcher.group(0)), TimeUnit.MILLISECONDS);
230-
} else {
231-
duration =
232-
ReadOnlyStalenessUtil.createDuration(
233-
Long.parseLong(matcher.group(1)),
234-
ReadOnlyStalenessUtil.parseTimeUnit(matcher.group(2)));
235-
}
236-
if (duration.getSeconds() == 0L && duration.getNanos() == 0) {
237-
return null;
238-
}
239-
return duration;
240-
}
241-
return null;
223+
super("DEFAULT", allowedValues);
242224
}
243225
}
244226

@@ -288,7 +270,7 @@ public TimestampBound convert(String value) {
288270
try {
289271
return TimestampBound.ofExactStaleness(
290272
Long.parseLong(matcher.group(groupIndex + 2)),
291-
ReadOnlyStalenessUtil.parseTimeUnit(matcher.group(groupIndex + 3)));
273+
parseTimeUnit(matcher.group(groupIndex + 3)));
292274
} catch (IllegalArgumentException e) {
293275
throw SpannerExceptionFactory.newSpannerException(
294276
ErrorCode.INVALID_ARGUMENT, e.getMessage());
@@ -297,7 +279,7 @@ public TimestampBound convert(String value) {
297279
try {
298280
return TimestampBound.ofMaxStaleness(
299281
Long.parseLong(matcher.group(groupIndex + 2)),
300-
ReadOnlyStalenessUtil.parseTimeUnit(matcher.group(groupIndex + 3)));
282+
parseTimeUnit(matcher.group(groupIndex + 3)));
301283
} catch (IllegalArgumentException e) {
302284
throw SpannerExceptionFactory.newSpannerException(
303285
ErrorCode.INVALID_ARGUMENT, e.getMessage());

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
import com.google.cloud.spanner.Statement;
2121
import com.google.cloud.spanner.TimestampBound;
2222
import com.google.cloud.spanner.connection.PgTransactionMode.IsolationLevel;
23-
import com.google.protobuf.Duration;
2423
import com.google.spanner.v1.DirectedReadOptions;
24+
import java.time.Duration;
2525

2626
/**
2727
* The Cloud Spanner JDBC driver supports a number of client side statements that are interpreted by

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,11 @@
102102
import com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType;
103103
import com.google.common.base.MoreObjects;
104104
import com.google.common.base.Preconditions;
105-
import com.google.protobuf.Duration;
106105
import com.google.spanner.v1.DirectedReadOptions;
107106
import com.google.spanner.v1.PlanNode;
108107
import com.google.spanner.v1.QueryPlan;
109108
import com.google.spanner.v1.RequestOptions;
109+
import java.time.Duration;
110110
import java.util.ArrayList;
111111
import java.util.Collections;
112112
import java.util.concurrent.TimeUnit;
@@ -205,14 +205,19 @@ public StatementResult statementShowAutocommitDmlMode() {
205205

206206
@Override
207207
public StatementResult statementSetStatementTimeout(Duration duration) {
208-
if (duration.getSeconds() == 0L && duration.getNanos() == 0) {
208+
if (duration == null || duration.isZero()) {
209209
getConnection().clearStatementTimeout();
210210
} else {
211+
com.google.protobuf.Duration protoDuration =
212+
com.google.protobuf.Duration.newBuilder()
213+
.setSeconds(duration.getSeconds())
214+
.setNanos(duration.getNano())
215+
.build();
211216
TimeUnit unit =
212217
ReadOnlyStalenessUtil.getAppropriateTimeUnit(
213-
new ReadOnlyStalenessUtil.DurationGetter(duration));
218+
new ReadOnlyStalenessUtil.DurationGetter(protoDuration));
214219
getConnection()
215-
.setStatementTimeout(ReadOnlyStalenessUtil.durationToUnits(duration, unit), unit);
220+
.setStatementTimeout(ReadOnlyStalenessUtil.durationToUnits(protoDuration, unit), unit);
216221
}
217222
return noResult(SET_STATEMENT_TIMEOUT);
218223
}
@@ -343,11 +348,7 @@ public StatementResult statementShowReturnCommitStats() {
343348

344349
@Override
345350
public StatementResult statementSetMaxCommitDelay(Duration duration) {
346-
getConnection()
347-
.setMaxCommitDelay(
348-
duration == null || duration.equals(Duration.getDefaultInstance())
349-
? null
350-
: java.time.Duration.ofSeconds(duration.getSeconds(), duration.getNanos()));
351+
getConnection().setMaxCommitDelay(duration == null || duration.isZero() ? null : duration);
351352
return noResult(SET_MAX_COMMIT_DELAY);
352353
}
353354

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.google.cloud.spanner.TimestampBound.Mode;
2828
import com.google.protobuf.Duration;
2929
import com.google.protobuf.util.Durations;
30+
import java.time.temporal.ChronoUnit;
3031
import java.util.concurrent.TimeUnit;
3132

3233
/**
@@ -93,6 +94,31 @@ static TimeUnit parseTimeUnit(String unit) {
9394
ErrorCode.INVALID_ARGUMENT, "Invalid option for time unit: " + unit);
9495
}
9596

97+
/**
98+
* Convert from {@link TimeUnit} to {@link ChronoUnit}. This code is copied from {@link
99+
* TimeUnit#toChronoUnit()}, which is available in Java 9 and higher.
100+
*/
101+
static ChronoUnit toChronoUnit(TimeUnit timeUnit) {
102+
switch (timeUnit) {
103+
case NANOSECONDS:
104+
return ChronoUnit.NANOS;
105+
case MICROSECONDS:
106+
return ChronoUnit.MICROS;
107+
case MILLISECONDS:
108+
return ChronoUnit.MILLIS;
109+
case SECONDS:
110+
return ChronoUnit.SECONDS;
111+
case MINUTES:
112+
return ChronoUnit.MINUTES;
113+
case HOURS:
114+
return ChronoUnit.HOURS;
115+
case DAYS:
116+
return ChronoUnit.DAYS;
117+
default:
118+
throw new IllegalArgumentException();
119+
}
120+
}
121+
96122
/**
97123
* Internal interface that is used to generalize getting a time duration from Cloud Spanner
98124
* read-only staleness settings.

google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -365,11 +365,21 @@
365365
"statementType": "SET_STATEMENT_TIMEOUT",
366366
"regex": "(?is)\\A\\s*set\\s+statement_timeout\\s*(?:=)\\s*(.*)\\z",
367367
"method": "statementSetStatementTimeout",
368-
"exampleStatements": ["set statement_timeout=null", "set statement_timeout='1s'", "set statement_timeout='100ms'", "set statement_timeout='10000us'", "set statement_timeout='9223372036854775807ns'"],
368+
"exampleStatements": [
369+
"set statement_timeout=null",
370+
"set statement_timeout = null ",
371+
"set statement_timeout='1s'",
372+
"set statement_timeout = '1s' ",
373+
"set statement_timeout=100",
374+
"set statement_timeout = 100 ",
375+
"set statement_timeout='100ms'",
376+
"set statement_timeout='10000us'",
377+
"set statement_timeout='9223372036854775807ns'"
378+
],
369379
"setStatement": {
370380
"propertyName": "STATEMENT_TIMEOUT",
371381
"separator": "=",
372-
"allowedValues": "('(\\d{1,19})(s|ms|us|ns)'|NULL)",
382+
"allowedValues": "('(\\d{1,19})(s|ms|us|ns)'|\\d{1,19}|NULL)",
373383
"converterName": "ClientSideStatementValueConverters$DurationConverter"
374384
}
375385
},
@@ -485,11 +495,23 @@
485495
"statementType": "SET_MAX_COMMIT_DELAY",
486496
"regex": "(?is)\\A\\s*set\\s+max_commit_delay\\s*(?:=)\\s*(.*)\\z",
487497
"method": "statementSetMaxCommitDelay",
488-
"exampleStatements": ["set max_commit_delay=null", "set max_commit_delay='1s'", "set max_commit_delay='100ms'", "set max_commit_delay='10000us'", "set max_commit_delay='9223372036854775807ns'"],
498+
"exampleStatements": [
499+
"set max_commit_delay=null",
500+
"set max_commit_delay = null",
501+
"set max_commit_delay = null ",
502+
"set max_commit_delay=1000",
503+
"set max_commit_delay = 1000",
504+
"set max_commit_delay = 1000 ",
505+
"set max_commit_delay='1s'",
506+
"set max_commit_delay = '1s'",
507+
"set max_commit_delay = '1s' ",
508+
"set max_commit_delay='100ms'",
509+
"set max_commit_delay='10000us'",
510+
"set max_commit_delay='9223372036854775807ns'"],
489511
"setStatement": {
490512
"propertyName": "MAX_COMMIT_DELAY",
491513
"separator": "=",
492-
"allowedValues": "('(\\d{1,19})(s|ms|us|ns)'|NULL)",
514+
"allowedValues": "('(\\d{1,19})(s|ms|us|ns)'|\\d{1,19}|NULL)",
493515
"converterName": "ClientSideStatementValueConverters$DurationConverter"
494516
}
495517
},

google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/PG_ClientSideStatements.json

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -421,9 +421,13 @@
421421
"method": "statementSetStatementTimeout",
422422
"exampleStatements": [
423423
"set statement_timeout=default",
424+
"set statement_timeout = default ",
425+
"set statement_timeout = DEFAULT ",
424426
"set statement_timeout='1s'",
427+
"set statement_timeout = '1s' ",
425428
"set statement_timeout='100ms'",
426429
"set statement_timeout=100",
430+
"set statement_timeout = 100 ",
427431
"set statement_timeout='10000us'",
428432
"set statement_timeout='9223372036854775807ns'",
429433
"set statement_timeout to default",
@@ -436,7 +440,7 @@
436440
"setStatement": {
437441
"propertyName": "STATEMENT_TIMEOUT",
438442
"separator": "(?:=|\\s+TO\\s+)",
439-
"allowedValues": "(\\d{1,19}|'(\\d{1,19})(s|ms|us|ns)'|DEFAULT)",
443+
"allowedValues": "('(\\d{1,19})(s|ms|us|ns)'|\\d{1,19}|DEFAULT)",
440444
"converterName": "ClientSideStatementValueConverters$PgDurationConverter"
441445
}
442446
},
@@ -651,11 +655,23 @@
651655
"statementType": "SET_MAX_COMMIT_DELAY",
652656
"regex": "(?is)\\A\\s*set\\s+spanner\\.max_commit_delay(?:\\s*=\\s*|\\s+to\\s+)(.*)\\z",
653657
"method": "statementSetMaxCommitDelay",
654-
"exampleStatements": ["set spanner.max_commit_delay=null", "set spanner.max_commit_delay='1s'", "set spanner.max_commit_delay='100ms'", "set spanner.max_commit_delay to '10000us'", "set spanner.max_commit_delay TO '9223372036854775807ns'"],
658+
"exampleStatements": [
659+
"set spanner.max_commit_delay=null",
660+
"set spanner.max_commit_delay = NULL",
661+
"set spanner.max_commit_delay = null ",
662+
"set spanner.max_commit_delay='1s'",
663+
"set spanner.max_commit_delay = '1s'",
664+
"set spanner.max_commit_delay = '1s' ",
665+
"set spanner.max_commit_delay=1000",
666+
"set spanner.max_commit_delay = 1000",
667+
"set spanner.max_commit_delay = 1000 ",
668+
"set spanner.max_commit_delay='100ms'",
669+
"set spanner.max_commit_delay to '10000us'",
670+
"set spanner.max_commit_delay TO '9223372036854775807ns'"],
655671
"setStatement": {
656672
"propertyName": "SPANNER.MAX_COMMIT_DELAY",
657673
"separator": "(?:=|\\s+TO\\s+)",
658-
"allowedValues": "('(\\d{1,19})(s|ms|us|ns)'|NULL)",
674+
"allowedValues": "('(\\d{1,19})(s|ms|us|ns)'|\\d{1,19}|NULL)",
659675
"converterName": "ClientSideStatementValueConverters$DurationConverter"
660676
}
661677
},

0 commit comments

Comments
 (0)