diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml
index 1f33ef2d82..d1f2664141 100644
--- a/google-cloud-spanner/pom.xml
+++ b/google-cloud-spanner/pom.xml
@@ -364,11 +364,6 @@
${graal-sdk.version}
provided
-
- com.google.auto.value
- auto-value
- 1.10.4
-
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java
index ef4643bb63..bd78336699 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java
@@ -513,6 +513,7 @@ protected Date getDateInternal(int columnIndex) {
return (Date) rowData.get(columnIndex);
}
+ @Override
protected Interval getIntervalInternal(int columnIndex) {
ensureDecoded(columnIndex);
return (Interval) rowData.get(columnIndex);
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Interval.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Interval.java
index c53524b3eb..4f5b5c57e4 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Interval.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Interval.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 Google LLC
+ * Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
package com.google.cloud.spanner;
-import com.google.auto.value.AutoValue;
import com.google.errorprone.annotations.Immutable;
import java.io.Serializable;
import java.math.BigDecimal;
@@ -25,9 +24,21 @@
import java.util.regex.Pattern;
import org.jetbrains.annotations.NotNull;
-@AutoValue
+/**
+ * Represents the time duration as a combination of months, days and nanoseconds. Nanoseconds are
+ * broken into two components microseconds and nanoFractions, where nanoFractions can range from
+ * [-999, 999]. Internally, Spanner supports Interval value with the following range of individual
+ * fields: months: [-120000, 120000] days: [-3660000, 3660000] nanoseconds: [-316224000000000000000,
+ * 316224000000000000000] Interval value created outside the specified domain will return error when
+ * sent to Spanner backend.
+ */
@Immutable
-public abstract class Interval implements Serializable {
+public class Interval implements Serializable {
+ private final int months;
+ private final int days;
+ private final long microseconds;
+ private final short nanoFractions;
+
public static final long MONTHS_PER_YEAR = 12;
public static final long DAYS_PER_MONTH = 30;
public static final long HOURS_PER_DAY = 24;
@@ -48,103 +59,110 @@ public abstract class Interval implements Serializable {
BigInteger.valueOf(MICROS_PER_MINUTE * NANOS_PER_MICRO);
public static final BigInteger NANOS_PER_HOUR =
BigInteger.valueOf(MICROS_PER_HOUR * NANOS_PER_MICRO);
+ public static final Interval ZERO = Interval.builder().build();
+ /** Regex to ISO8601 formatted interval. `P[n]Y[n]M[n]DT[n]H[n]M[n(.[fraction])]S` */
private static final Pattern INTERVAL_PATTERN =
Pattern.compile(
"^P(?!$)(-?\\d+Y)?(-?\\d+M)?(-?\\d+D)?(T(?=-?.?\\d)(-?\\d+H)?(-?\\d+M)?(-?((\\d+(\\.\\d{1,9})?)|(\\.\\d{1,9}))S)?)?$");
+ private Interval(int months, int days, long microseconds, short nanoFractions) {
+ this.months = months;
+ this.days = days;
+ this.microseconds = microseconds;
+ this.nanoFractions = nanoFractions;
+ }
+
/** Returns the months component of the interval. */
- public abstract int months();
+ public int getMonths() {
+ return months;
+ }
/** Returns the days component of the interval. */
- public abstract int days();
+ public int getDays() {
+ return days;
+ }
/** Returns the microseconds component of the interval. */
- public abstract long micros();
+ public long getMicroseconds() {
+ return microseconds;
+ }
/** Returns the nanoFractions component of the interval. */
- public abstract short nanoFractions();
-
- public static Builder builder() {
- return new AutoValue_Interval.Builder();
+ public short getNanoFractions() {
+ return nanoFractions;
}
- /** Returns the nanoseconds component of the interval. */
- public BigInteger nanos() {
- return BigInteger.valueOf(micros())
+ /** Returns the microseconds and nanoFraction of the Interval combined as nanoseconds. */
+ public BigInteger getNanoseconds() {
+ return BigInteger.valueOf(getMicroseconds())
.multiply(BigInteger.valueOf(NANOS_PER_MICRO))
- .add(BigInteger.valueOf(nanoFractions()));
+ .add(BigInteger.valueOf(getNanoFractions()));
+ }
+
+ public static Builder builder() {
+ return new Builder();
}
- /** Returns the total microseconds represented by the interval. */
- public long getAsMicros() {
- return months() * MICROS_PER_MONTH + days() * MICROS_PER_DAY + micros();
+ /**
+ * Returns the total microseconds represented by the interval. It combines months, days and
+ * microseconds fields of the interval into microseconds.
+ */
+ public long getAsMicroseconds() {
+ return Math.addExact(
+ Math.addExact(
+ Math.multiplyExact(getMonths(), MICROS_PER_MONTH),
+ Math.multiplyExact(getDays(), MICROS_PER_DAY)),
+ getMicroseconds());
}
- /** Returns the total nanoseconds represented by the interval. */
- public BigInteger getAsNanos() {
- return BigInteger.valueOf(getAsMicros())
+ /**
+ * Returns the total nanoseconds represented by the interval. It combines months, days,
+ * microseconds and nanoFractions fields of the interval into nanoseconds.
+ */
+ public BigInteger getAsNanoseconds() {
+ return BigInteger.valueOf(getAsMicroseconds())
.multiply(BigInteger.valueOf(NANOS_PER_MICRO))
- .add(BigInteger.valueOf(nanoFractions()));
+ .add(BigInteger.valueOf(getNanoFractions()));
}
/** Creates an interval with specified number of months. */
public static Interval ofMonths(int months) {
- return builder().setMonths(months).setDays(0).setMicros(0).setNanoFractions((short) 0).build();
+ return builder().setMonths(months).build();
}
/** Creates an interval with specified number of days. */
public static Interval ofDays(int days) {
- return builder().setMonths(0).setDays(days).setMicros(0).setNanoFractions((short) 0).build();
+ return builder().setDays(days).build();
}
/** Creates an interval with specified number of seconds. */
public static Interval ofSeconds(long seconds) {
- return builder()
- .setMonths(0)
- .setDays(0)
- .setMicros(seconds * MICROS_PER_SECOND)
- .setNanoFractions((short) 0)
- .build();
+ return builder().setMicroseconds(seconds * MICROS_PER_SECOND).build();
}
/** Creates an interval with specified number of milliseconds. */
public static Interval ofMilliseconds(long milliseconds) {
- return builder()
- .setMonths(0)
- .setDays(0)
- .setMicros(milliseconds * MICROS_PER_MILLI)
- .setNanoFractions((short) 0)
- .build();
+ return builder().setMicroseconds(milliseconds * MICROS_PER_MILLI).build();
}
/** Creates an interval with specified number of microseconds. */
- public static Interval ofMicros(long micros) {
- return builder().months(0).days(0).micros(micros).nanoFractions((short) 0).build();
+ public static Interval ofMicroseconds(long micros) {
+ return builder().setMicroseconds(micros).build();
}
/** Creates an interval with specified number of nanoseconds. */
- public static Interval ofNanos(@NotNull BigInteger nanos) {
+ public static Interval ofNanoseconds(@NotNull BigInteger nanos) {
BigInteger micros = nanos.divide(BigInteger.valueOf(NANOS_PER_MICRO));
BigInteger nanoFractions = nanos.subtract(micros.multiply(BigInteger.valueOf(NANOS_PER_MICRO)));
long microsValue = micros.longValueExact();
short nanoFractionsValue = nanoFractions.shortValueExact();
- return builder()
- .setMonths(0)
- .setDays(0)
- .setMicros(microsValue)
- .setNanoFractions(nanoFractionsValue)
- .build();
+ return builder().setMicroseconds(microsValue).setNanoFractions(nanoFractionsValue).build();
}
/** Creates an interval with specified number of months, days and microseconds. */
public static Interval fromMonthsDaysMicros(int months, int days, long micros) {
- return builder()
- .setMonths(months)
- .setDays(days)
- .setMicros(micros)
- .setNanoFractions((short) 0)
- .build();
+ return builder().setMonths(months).setDays(days).setMicroseconds(micros).build();
}
/** Creates an interval with specified number of months, days and nanoseconds. */
@@ -157,7 +175,7 @@ public static Interval fromMonthsDaysNanos(int months, int days, BigInteger nano
return builder()
.setMonths(months)
.setDays(days)
- .setMicros(micros)
+ .setMicroseconds(micros)
.setNanoFractions(nanoFractions)
.build();
}
@@ -195,17 +213,21 @@ public static Interval parseFromString(String interval) {
return Interval.builder()
.setMonths(Math.toIntExact(totalMonths))
.setDays(Math.toIntExact(days))
- .setMicros(totalMicros.longValueExact())
+ .setMicroseconds(totalMicros.longValueExact())
.setNanoFractions(nanoFractions.shortValueExact())
.build();
}
/** Converts Interval to ISO8601 Duration Formatted String. */
- public String ToISO8601() {
+ public String toISO8601() {
+ if (this.equals(ZERO)) {
+ return "P0Y";
+ }
+
StringBuilder result = new StringBuilder();
result.append("P");
- long months = this.months();
+ long months = this.getMonths();
long years = months / MONTHS_PER_YEAR;
months = months - years * MONTHS_PER_YEAR;
@@ -217,11 +239,11 @@ public String ToISO8601() {
result.append(String.format("%dM", months));
}
- if (this.days() != 0) {
- result.append(String.format("%dD", this.days()));
+ if (this.getDays() != 0) {
+ result.append(String.format("%dD", this.getDays()));
}
- BigInteger nanos = this.nanos();
+ BigInteger nanos = this.getNanoseconds();
BigInteger zero = BigInteger.valueOf(0);
if (nanos.compareTo(zero) != 0) {
result.append("T");
@@ -245,16 +267,12 @@ public String ToISO8601() {
}
}
- if (result.length() == 1) {
- result.append("0Y");
- }
-
return result.toString();
}
- /** Creates an interval which representing 0-duration. */
- public static Interval zeroInterval() {
- return builder().setMonths(0).setDays(0).setMicros(0).setNanoFractions((short) 0).build();
+ @Override
+ public String toString() {
+ return toISO8601();
}
@Override
@@ -264,43 +282,42 @@ public boolean equals(Object rhs) {
}
Interval anotherInterval = (Interval) rhs;
- return months() == anotherInterval.months()
- && days() == anotherInterval.days()
- && nanos().equals(anotherInterval.nanos());
+ return getMonths() == anotherInterval.getMonths()
+ && getDays() == anotherInterval.getDays()
+ && getNanoseconds().equals(anotherInterval.getNanoseconds());
}
@Override
public int hashCode() {
int result = 17;
- result = 31 * result + Long.valueOf(months()).hashCode();
- result = 31 * result + Long.valueOf(days()).hashCode();
- result = 31 * result + nanos().hashCode();
+ result = 31 * result + Long.valueOf(getMonths()).hashCode();
+ result = 31 * result + Long.valueOf(getDays()).hashCode();
+ result = 31 * result + getNanoseconds().hashCode();
return result;
}
- @AutoValue.Builder
- public abstract static class Builder {
- abstract Builder months(int months);
-
- abstract Builder days(int days);
+ public static class Builder {
+ private int months = 0;
+ private int days = 0;
+ private long microseconds = 0;
+ private short nanoFractions = 0;
- abstract Builder micros(long micros);
-
- abstract Builder nanoFractions(short nanoFractions);
-
- public Builder setMonths(int months) {
- return months(months);
+ Builder setMonths(int months) {
+ this.months = months;
+ return this;
}
- public Builder setDays(int days) {
- return days(days);
+ Builder setDays(int days) {
+ this.days = days;
+ return this;
}
- public Builder setMicros(long micros) {
- return micros(micros);
+ Builder setMicroseconds(long microseconds) {
+ this.microseconds = microseconds;
+ return this;
}
- public Builder setNanoFractions(short nanoFractions) {
+ Builder setNanoFractions(short nanoFractions) {
if (nanoFractions <= -NANOS_PER_MICRO || nanoFractions >= NANOS_PER_MICRO) {
throw SpannerExceptionFactory.newSpannerException(
ErrorCode.INVALID_ARGUMENT,
@@ -308,9 +325,13 @@ public Builder setNanoFractions(short nanoFractions) {
"NanoFractions must be between:[-%d, %d]",
NANOS_PER_MICRO - 1, NANOS_PER_MICRO - 1));
}
- return nanoFractions(nanoFractions);
+
+ this.nanoFractions = nanoFractions;
+ return this;
}
- public abstract Interval build();
+ public Interval build() {
+ return new Interval(months, days, microseconds, nanoFractions);
+ }
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java
index 2d6371ea65..4b8d94f481 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java
@@ -52,6 +52,7 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
+import org.jetbrains.annotations.NotNull;
/**
* Represents a value to be consumed by the Cloud Spanner API. A value can be {@code NULL} or
@@ -1853,12 +1854,17 @@ public Interval getInterval() {
@Override
void valueToString(StringBuilder b) {
- b.append(value.ToISO8601());
+ b.append(value.toISO8601());
}
@Override
com.google.protobuf.Value valueToProto() {
- return com.google.protobuf.Value.newBuilder().setStringValue(value.ToISO8601()).build();
+ return com.google.protobuf.Value.newBuilder().setStringValue(value.toISO8601()).build();
+ }
+
+ @Override
+ public @NotNull String getAsString() {
+ return isNull() ? NULL_STRING : value.toISO8601();
}
}
@@ -2878,12 +2884,12 @@ public List getIntervalArray() {
@Override
void appendElement(StringBuilder b, Interval element) {
- b.append(element.ToISO8601());
+ b.append(element.toISO8601());
}
@Override
- String elementToString(Interval element) {
- return element.ToISO8601();
+ String elementToString(@NotNull Interval element) {
+ return element.toISO8601();
}
}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java
index 8a036065bd..7782893025 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java
@@ -445,7 +445,7 @@ public static Collection