diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml
index ec13415790..e121fce7ab 100644
--- a/google-cloud-spanner/clirr-ignored-differences.xml
+++ b/google-cloud-spanner/clirr-ignored-differences.xml
@@ -566,6 +566,58 @@
java.util.List getFloat32Array()
+
+
+ 7012
+ com/google/cloud/spanner/StructReader
+ com.google.cloud.spanner.Interval getInterval(int)
+
+
+ 7012
+ com/google/cloud/spanner/StructReader
+ com.google.cloud.spanner.Interval getInterval(java.lang.String)
+
+
+ 7012
+ com/google/cloud/spanner/StructReader
+ com.google.cloud.spanner.Interval[] getIntervalArray(int)
+
+
+ 7012
+ com/google/cloud/spanner/StructReader
+ com.google.cloud.spanner.Interval[] getIntervalArray(java.lang.String)
+
+
+ 7012
+ com/google/cloud/spanner/StructReader
+ java.util.List getIntervalList(int)
+
+
+ 7012
+ com/google/cloud/spanner/StructReader
+ java.util.List getIntervalList(java.lang.String)
+
+
+ 7013
+ com/google/cloud/spanner/AbstractStructReader
+ com.google.cloud.spanner.Interval getIntervalInternal(int)
+
+
+ 7013
+ com/google/cloud/spanner/AbstractStructReader
+ java.util.List getIntervalListInternal(int)
+
+
+ 7013
+ com/google/cloud/spanner/Value
+ com.google.cloud.spanner.Interval getInterval()
+
+
+ 7013
+ com/google/cloud/spanner/Value
+ java.util.List getIntervalArray()
+
+
7012
diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml
index 9719e08176..1f33ef2d82 100644
--- a/google-cloud-spanner/pom.xml
+++ b/google-cloud-spanner/pom.xml
@@ -364,6 +364,11 @@
${graal-sdk.version}
provided
+
+ com.google.auto.value
+ auto-value
+ 1.10.4
+
@@ -455,6 +460,12 @@
opentelemetry-sdk-testing
test
+
+ org.jetbrains
+ annotations
+ 24.1.0
+ compile
+
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java
index 2cf93fb92e..2d4088e486 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java
@@ -415,6 +415,11 @@ protected Date getDateInternal(int columnIndex) {
return currRow().getDateInternal(columnIndex);
}
+ @Override
+ protected Interval getIntervalInternal(int columnIndex) {
+ return currRow().getIntervalInternal(columnIndex);
+ }
+
@Override
protected Value getValueInternal(int columnIndex) {
return currRow().getValueInternal(columnIndex);
@@ -507,6 +512,11 @@ protected List getDateListInternal(int columnIndex) {
return currRow().getDateListInternal(columnIndex);
}
+ @Override
+ protected List getIntervalListInternal(int columnIndex) {
+ return currRow().getIntervalListInternal(columnIndex);
+ }
+
@Override
protected List getStructListInternal(int columnIndex) {
return currRow().getStructListInternal(columnIndex);
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java
index d13c61aaf0..10287544e9 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java
@@ -67,6 +67,8 @@ protected String getPgJsonbInternal(int columnIndex) {
protected abstract Date getDateInternal(int columnIndex);
+ protected abstract Interval getIntervalInternal(int columnIndex);
+
protected T getProtoMessageInternal(int columnIndex, T message) {
throw new UnsupportedOperationException("Not implemented");
}
@@ -128,6 +130,8 @@ protected List getPgJsonbListInternal(int columnIndex) {
protected abstract List getDateListInternal(int columnIndex);
+ protected abstract List getIntervalListInternal(int columnIndex);
+
protected abstract List getStructListInternal(int columnIndex);
@Override
@@ -299,6 +303,19 @@ public Date getDate(String columnName) {
return getDateInternal(columnIndex);
}
+ @Override
+ public Interval getInterval(int columnIndex) {
+ checkNonNullOfType(columnIndex, Type.interval(), columnIndex);
+ return getIntervalInternal(columnIndex);
+ }
+
+ @Override
+ public Interval getInterval(String columnName) {
+ int columnIndex = getColumnIndex(columnName);
+ checkNonNullOfType(columnIndex, Type.interval(), columnName);
+ return getIntervalInternal(columnIndex);
+ }
+
@Override
public T getProtoEnum(
int columnIndex, Function method) {
@@ -583,6 +600,19 @@ public List getDateList(String columnName) {
return getDateListInternal(columnIndex);
}
+ @Override
+ public List getIntervalList(int columnIndex) {
+ checkNonNullOfType(columnIndex, Type.array(Type.interval()), columnIndex);
+ return getIntervalListInternal(columnIndex);
+ }
+
+ @Override
+ public List getIntervalList(String columnName) {
+ int columnIndex = getColumnIndex(columnName);
+ checkNonNullOfType(columnIndex, Type.array(Type.interval()), columnName);
+ return getIntervalListInternal(columnIndex);
+ }
+
@Override
public List getStructList(int columnIndex) {
checkNonNullArrayOfStruct(columnIndex, columnIndex);
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java
index b3e37ffcdd..c747c2a590 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java
@@ -231,6 +231,18 @@ public Date getDate(String columnName) {
return delegate.get().getDate(columnName);
}
+ @Override
+ public Interval getInterval(int columnIndex) {
+ checkValidState();
+ return delegate.get().getInterval(columnIndex);
+ }
+
+ @Override
+ public Interval getInterval(String columnName) {
+ checkValidState();
+ return delegate.get().getInterval(columnName);
+ }
+
@Override
public boolean[] getBooleanArray(int columnIndex) {
checkValidState();
@@ -409,6 +421,18 @@ public List getDateList(String columnName) {
return delegate.get().getDateList(columnName);
}
+ @Override
+ public List getIntervalList(int columnIndex) {
+ checkValidState();
+ return delegate.get().getIntervalList(columnIndex);
+ }
+
+ @Override
+ public List getIntervalList(String columnName) {
+ checkValidState();
+ return delegate.get().getIntervalList(columnName);
+ }
+
@Override
public List getProtoMessageList(int columnIndex, T message) {
checkValidState();
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 4d07a12880..ef4643bb63 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
@@ -131,6 +131,9 @@ private Object writeReplace() {
case DATE:
builder.set(fieldName).to((Date) value);
break;
+ case INTERVAL:
+ builder.set(fieldName).to((Interval) value);
+ break;
case ARRAY:
final Type elementType = fieldType.getArrayElementType();
switch (elementType.getCode()) {
@@ -184,6 +187,9 @@ private Object writeReplace() {
case DATE:
builder.set(fieldName).toDateArray((Iterable) value);
break;
+ case INTERVAL:
+ builder.set(fieldName).toIntervalArray((Iterable) value);
+ break;
case STRUCT:
builder.set(fieldName).toStructArray(elementType, (Iterable) value);
break;
@@ -298,6 +304,9 @@ private static Object decodeValue(Type fieldType, com.google.protobuf.Value prot
case DATE:
checkType(fieldType, proto, KindCase.STRING_VALUE);
return Date.parseDate(proto.getStringValue());
+ case INTERVAL:
+ checkType(fieldType, proto, KindCase.STRING_VALUE);
+ return Interval.parseFromString(proto.getStringValue());
case ARRAY:
checkType(fieldType, proto, KindCase.LIST_VALUE);
ListValue listValue = proto.getListValue();
@@ -347,6 +356,7 @@ static Object decodeArrayValue(Type elementType, ListValue listValue) {
case BYTES:
case TIMESTAMP:
case DATE:
+ case INTERVAL:
case STRUCT:
case PROTO:
return Lists.transform(listValue.getValuesList(), input -> decodeValue(elementType, input));
@@ -503,6 +513,11 @@ protected Date getDateInternal(int columnIndex) {
return (Date) rowData.get(columnIndex);
}
+ protected Interval getIntervalInternal(int columnIndex) {
+ ensureDecoded(columnIndex);
+ return (Interval) rowData.get(columnIndex);
+ }
+
private boolean isUnrecognizedType(int columnIndex) {
return type.getStructFields().get(columnIndex).getType().getCode() == Code.UNRECOGNIZED;
}
@@ -624,6 +639,8 @@ protected Value getValueInternal(int columnIndex) {
return Value.timestamp(isNull ? null : getTimestampInternal(columnIndex));
case DATE:
return Value.date(isNull ? null : getDateInternal(columnIndex));
+ case INTERVAL:
+ return Value.interval(isNull ? null : getIntervalInternal(columnIndex));
case STRUCT:
return Value.struct(isNull ? null : getStructInternal(columnIndex));
case UNRECOGNIZED:
@@ -664,6 +681,8 @@ protected Value getValueInternal(int columnIndex) {
return Value.timestampArray(isNull ? null : getTimestampListInternal(columnIndex));
case DATE:
return Value.dateArray(isNull ? null : getDateListInternal(columnIndex));
+ case INTERVAL:
+ return Value.intervalArray(isNull ? null : getIntervalListInternal(columnIndex));
case STRUCT:
return Value.structArray(
elementType, isNull ? null : getStructListInternal(columnIndex));
@@ -847,6 +866,13 @@ protected List getDateListInternal(int columnIndex) {
return Collections.unmodifiableList((List) rowData.get(columnIndex));
}
+ @Override
+ @SuppressWarnings("unchecked") // We know ARRAY produces a List.
+ protected List getIntervalListInternal(int columnIndex) {
+ ensureDecoded(columnIndex);
+ return Collections.unmodifiableList((List) rowData.get(columnIndex));
+ }
+
@Override
@SuppressWarnings("unchecked") // We know ARRAY> produces a List.
protected List getStructListInternal(int 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
new file mode 100644
index 0000000000..1f7ede79c3
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Interval.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.spanner;
+
+import com.google.auto.value.AutoValue;
+import com.google.errorprone.annotations.Immutable;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.jetbrains.annotations.NotNull;
+
+@AutoValue
+@Immutable
+public abstract class Interval implements Comparable, Serializable {
+ public static final long MONTHS_PER_YEAR = 12;
+ public static final long DAYS_PER_MONTH = 30;
+ public static final long HOURS_PER_DAY = 24;
+ public static final long MINUTES_PER_HOUR = 60;
+ public static final long SECONDS_PER_MINUTE = 60;
+ public static final long SECONDS_PER_HOUR = MINUTES_PER_HOUR * SECONDS_PER_MINUTE;
+ public static final long MILLIS_PER_SECOND = 1000;
+ public static final long MICROS_PER_MILLI = 1000;
+ public static final long NANOS_PER_MICRO = 1000;
+ public static final long MICROS_PER_SECOND = MICROS_PER_MILLI * MILLIS_PER_SECOND;
+ public static final long MICROS_PER_MINUTE = SECONDS_PER_MINUTE * MICROS_PER_SECOND;
+ public static final long MICROS_PER_HOUR = SECONDS_PER_HOUR * MICROS_PER_SECOND;
+ public static final long MICROS_PER_DAY = HOURS_PER_DAY * MICROS_PER_HOUR;
+ public static final long MICROS_PER_MONTH = DAYS_PER_MONTH * MICROS_PER_DAY;
+ public static final BigInteger NANOS_PER_SECOND =
+ BigInteger.valueOf(MICROS_PER_SECOND * NANOS_PER_MICRO);
+ public static final BigInteger NANOS_PER_MINUTE =
+ BigInteger.valueOf(MICROS_PER_MINUTE * NANOS_PER_MICRO);
+ public static final BigInteger NANOS_PER_HOUR =
+ BigInteger.valueOf(MICROS_PER_HOUR * NANOS_PER_MICRO);
+
+ 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})?S)?)?$");
+
+ public abstract long months();
+
+ public abstract long days();
+
+ public abstract long micros();
+
+ public abstract short nanoFractions();
+
+ public static Builder builder() {
+ return new AutoValue_Interval.Builder();
+ }
+
+ public BigInteger nanos() {
+ return BigInteger.valueOf(micros())
+ .multiply(BigInteger.valueOf(NANOS_PER_MICRO))
+ .add(BigInteger.valueOf(nanoFractions()));
+ }
+
+ /** Returns the total micros represented by the Interval. */
+ public long getAsMicros() {
+ return months() * MICROS_PER_MONTH + days() * MICROS_PER_DAY + micros();
+ }
+
+ /** Returns the total nanos represented by the Interval. */
+ public BigInteger getAsNanos() {
+ return BigInteger.valueOf(getAsMicros())
+ .multiply(BigInteger.valueOf(NANOS_PER_MICRO))
+ .add(BigInteger.valueOf(nanoFractions()));
+ }
+
+ /** Creates an Interval consisting of the given number of months. */
+ public static Interval ofMonths(long months) {
+ return builder().setMonths(months).setDays(0).setMicros(0).setNanoFractions((short) 0).build();
+ }
+
+ /** Creates an Interval consisting of the given number of days. */
+ public static Interval ofDays(long days) {
+ return builder().setMonths(0).setDays(days).setMicros(0).setNanoFractions((short) 0).build();
+ }
+
+ /** Creates an Interval with specified months, days and micros. */
+ public static Interval fromMonthsDaysMicros(long months, long days, long micros) {
+ return builder()
+ .setMonths(months)
+ .setDays(days)
+ .setMicros(micros)
+ .setNanoFractions((short) 0)
+ .build();
+ }
+
+ /** Creates an Interval with specified months, days and nanos. */
+ public static Interval fromMonthsDaysNanos(long months, long days, BigInteger nanos) {
+ long micros = nanos.divide(BigInteger.valueOf(NANOS_PER_MICRO)).longValue();
+ short nanoFractions =
+ nanos
+ .subtract(BigInteger.valueOf(micros).multiply(BigInteger.valueOf(NANOS_PER_MICRO)))
+ .shortValue();
+ return builder()
+ .setMonths(months)
+ .setDays(days)
+ .setMicros(micros)
+ .setNanoFractions(nanoFractions)
+ .build();
+ }
+
+ private static String getNullOrDefault(Matcher matcher, int groupIdx) {
+ String value = matcher.group(groupIdx);
+ return value == null ? "0" : value;
+ }
+
+ public static Interval parseFromString(String interval) {
+ Matcher matcher = INTERVAL_PATTERN.matcher(interval);
+ if (!matcher.matches()) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.INVALID_ARGUMENT, "Invalid Interval String: " + interval);
+ }
+
+ long years = Long.parseLong(getNullOrDefault(matcher, 1).replace("Y", ""));
+ long months = Long.parseLong(getNullOrDefault(matcher, 2).replace("M", ""));
+ long days = Long.parseLong(getNullOrDefault(matcher, 3).replace("D", ""));
+ long hours = Long.parseLong(getNullOrDefault(matcher, 5).replace("H", ""));
+ long minutes = Long.parseLong(getNullOrDefault(matcher, 6).replace("M", ""));
+ BigDecimal seconds = new BigDecimal(getNullOrDefault(matcher, 7).replace("S", ""));
+
+ long totalMonths = years * MONTHS_PER_YEAR + months;
+ BigInteger totalNanos = seconds.movePointRight(9).toBigInteger();
+ totalNanos =
+ totalNanos.add(BigInteger.valueOf(minutes * SECONDS_PER_MINUTE).multiply(NANOS_PER_SECOND));
+ totalNanos =
+ totalNanos.add(BigInteger.valueOf(hours * SECONDS_PER_HOUR).multiply(NANOS_PER_SECOND));
+
+ BigInteger totalMicros = totalNanos.divide(BigInteger.valueOf(NANOS_PER_MICRO));
+ BigInteger nanoFractions =
+ totalNanos.subtract(totalMicros.multiply(BigInteger.valueOf(NANOS_PER_MICRO)));
+
+ return Interval.builder()
+ .setMonths(totalMonths)
+ .setDays(days)
+ .setMicros(totalMicros.longValue())
+ .setNanoFractions(nanoFractions.shortValue())
+ .build();
+ }
+
+
+ /** @return the Interval in ISO801 duration format. */
+ public String ToISO8601() {
+ StringBuilder result = new StringBuilder();
+ result.append("P");
+
+ long months = this.months();
+ long years = months / MONTHS_PER_YEAR;
+ months = months - years * MONTHS_PER_YEAR;
+
+ if (years != 0) {
+ result.append(String.format("%dY", years));
+ }
+
+ if (months != 0) {
+ result.append(String.format("%dM", months));
+ }
+
+ if (this.days() != 0) {
+ result.append(String.format("%dD", this.days()));
+ }
+
+ BigInteger nanos = this.nanos();
+ BigInteger zero = BigInteger.valueOf(0);
+ if (nanos.compareTo(zero) != 0) {
+ result.append("T");
+ BigInteger hours = nanos.divide(NANOS_PER_HOUR);
+
+ if (hours.compareTo(zero) != 0) {
+ result.append(String.format("%sH", hours));
+ }
+
+ nanos = nanos.subtract(hours.multiply(NANOS_PER_HOUR));
+ BigInteger minutes = nanos.divide(NANOS_PER_MINUTE);
+ if (minutes.compareTo(zero) != 0) {
+ result.append(String.format("%sM", minutes));
+ }
+
+ nanos = nanos.subtract(minutes.multiply(NANOS_PER_MINUTE));
+ BigDecimal seconds = new BigDecimal(nanos).movePointLeft(9);
+
+ if (seconds.compareTo(new BigDecimal(zero)) != 0) {
+ result.append(String.format("%sS", seconds));
+ }
+ }
+
+ if (result.length() == 1) {
+ result.append("0Y");
+ }
+
+ return result.toString();
+ }
+
+ /** Creates an Interval consisting of the given number of seconds. */
+ public static Interval ofSeconds(long seconds) {
+ return builder()
+ .setMonths(0)
+ .setDays(0)
+ .setMicros(seconds * MICROS_PER_SECOND)
+ .setNanoFractions((short) 0)
+ .build();
+ }
+
+ /** Creates an Interval consisting of the given number of milliseconds. */
+ public static Interval ofMilliseconds(long milliseconds) {
+ return builder()
+ .setMonths(0)
+ .setDays(0)
+ .setMicros(milliseconds * MICROS_PER_MILLI)
+ .setNanoFractions((short) 0)
+ .build();
+ }
+
+ /** Creates an Interval consisting of the given number of microseconds. */
+ public static Interval ofMicros(long micros) {
+ return builder().months(0).days(0).micros(micros).nanoFractions((short) 0).build();
+ }
+
+ /** Creates an Interval consisting of the given number of nanoseconds. */
+ public static Interval ofNanos(@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.longValue();
+ long nanoFractionsValue = nanoFractions.longValue();
+
+ return builder()
+ .setMonths(0)
+ .setDays(0)
+ .setMicros(microsValue)
+ .setNanoFractions((short) nanoFractionsValue)
+ .build();
+ }
+
+ public static Interval zeroInterval() {
+ return builder().setMonths(0).setDays(0).setMicros(0).setNanoFractions((short) 0).build();
+ }
+
+ @Override
+ public boolean equals(Object rhs){
+ if(!(rhs instanceof Interval)){
+ return false;
+ }
+
+ Interval anotherInterval = (Interval) rhs;
+ return months() == anotherInterval.months() && days() == anotherInterval.days() && nanos().equals(anotherInterval.nanos());
+ }
+
+ @Override
+ public int compareTo(@NotNull Interval anotherInterval) {
+ if(equals(anotherInterval)){
+ return 0;
+ }
+ return getAsNanos().compareTo(anotherInterval.getAsNanos());
+ }
+
+ @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();
+ return result;
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ abstract Builder months(long months);
+
+ abstract Builder days(long days);
+
+ abstract Builder micros(long micros);
+
+ abstract Builder nanoFractions(short nanoFractions);
+
+ public Builder setMonths(long months) {
+ return months(months);
+ }
+
+ public Builder setDays(long days) {
+ return days(days);
+ }
+
+ public Builder setMicros(long micros) {
+ return micros(micros);
+ }
+
+ public Builder setNanoFractions(short nanoFractions) {
+ if (nanoFractions <= -NANOS_PER_MICRO || nanoFractions >= NANOS_PER_MICRO) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.INVALID_ARGUMENT,
+ String.format(
+ "NanoFractions must be between:[-%d, %d]",
+ NANOS_PER_MICRO - 1, NANOS_PER_MICRO - 1));
+ }
+ return nanoFractions(nanoFractions);
+ }
+
+ public abstract Interval build();
+ }
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java
index 3d12cf5ad2..d91bc2104a 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java
@@ -326,6 +326,16 @@ public Date getDate(String columnName) {
return getCurrentRowAsStruct().getDate(columnName);
}
+ @Override
+ public Interval getInterval(int columnIndex) {
+ return getCurrentRowAsStruct().getInterval(columnIndex);
+ }
+
+ @Override
+ public Interval getInterval(String columnName) {
+ return getCurrentRowAsStruct().getInterval(columnName);
+ }
+
@Override
public T getProtoMessage(int columnIndex, T message) {
return getCurrentRowAsStruct().getProtoMessage(columnIndex, message);
@@ -508,6 +518,16 @@ public List getDateList(String columnName) {
return getCurrentRowAsStruct().getDateList(columnName);
}
+ @Override
+ public List getIntervalList(int columnIndex) {
+ return getCurrentRowAsStruct().getIntervalList(columnIndex);
+ }
+
+ @Override
+ public List getIntervalList(String columnName) {
+ return getCurrentRowAsStruct().getIntervalList(columnName);
+ }
+
@Override
public List getProtoMessageList(int columnIndex, T message) {
return getCurrentRowAsStruct().getProtoMessageList(columnIndex, message);
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java
index 112ecc8120..b0f2ca105c 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java
@@ -226,6 +226,11 @@ protected Date getDateInternal(int columnIndex) {
return values.get(columnIndex).getDate();
}
+ @Override
+ protected Interval getIntervalInternal(int columnIndex) {
+ return values.get(columnIndex).getInterval();
+ }
+
@Override
protected T getProtoMessageInternal(int columnIndex, T message) {
return values.get(columnIndex).getProtoMessage(message);
@@ -334,6 +339,11 @@ protected List getDateListInternal(int columnIndex) {
return values.get(columnIndex).getDateArray();
}
+ @Override
+ protected List getIntervalListInternal(int columnIndex) {
+ return values.get(columnIndex).getIntervalArray();
+ }
+
@Override
protected List getStructListInternal(int columnIndex) {
return values.get(columnIndex).getStructArray();
@@ -420,6 +430,8 @@ private Object getAsObject(int columnIndex) {
return getTimestampInternal(columnIndex);
case DATE:
return getDateInternal(columnIndex);
+ case INTERVAL:
+ return getIntervalInternal(columnIndex);
case STRUCT:
return getStructInternal(columnIndex);
case ARRAY:
@@ -451,6 +463,8 @@ private Object getAsObject(int columnIndex) {
return getTimestampListInternal(columnIndex);
case DATE:
return getDateListInternal(columnIndex);
+ case INTERVAL:
+ return getIntervalInternal(columnIndex);
case STRUCT:
return getStructListInternal(columnIndex);
default:
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java
index f9967db045..33e88c39d3 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java
@@ -297,6 +297,18 @@ default T getProtoEnum(
*/
Date getDate(String columnName);
+ /**
+ * @param columnIndex index of the column
+ * @return the value of a non-{@code NULL} column with type {@link Type#interval()}.
+ */
+ Interval getInterval(int columnIndex);
+
+ /**
+ * @param columnName name of the column
+ * @return the value of a non-{@code NULL} column with type {@link Type#interval()}.
+ */
+ Interval getInterval(String columnName);
+
/**
* @param columnIndex index of the column
* @return the value of a nullable column as a {@link Value}.
@@ -625,6 +637,22 @@ default List getProtoEnumList(
*/
List getDateList(String columnName);
+ /**
+ * @param columnIndex index of the column
+ * @return the value of a non-{@code NULL} column with type {@code Type.array(Type.interval())}.
+ * The list returned by this method is lazily constructed. Create a copy of it if you intend
+ * to access each element in the list multiple times.
+ */
+ List getIntervalList(int columnIndex);
+
+ /**
+ * @param columnName name of the column
+ * @return the value of a non-{@code NULL} column with type {@code Type.array(Type.interval())}.
+ * The list returned by this method is lazily constructed. Create a copy of it if you intend
+ * to access each element in the list multiple times.
+ */
+ List getIntervalList(String columnName);
+
/**
* @param columnIndex index of the column
* @return the value of a non-{@code NULL} column with type {@code Type.array(Type.struct(...))}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java
index 748cb7f87e..7cb3f131f7 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java
@@ -59,6 +59,7 @@ public final class Type implements Serializable {
private static final Type TYPE_BYTES = new Type(Code.BYTES, null, null);
private static final Type TYPE_TIMESTAMP = new Type(Code.TIMESTAMP, null, null);
private static final Type TYPE_DATE = new Type(Code.DATE, null, null);
+ private static final Type TYPE_INTERVAL = new Type(Code.INTERVAL, null, null);
private static final Type TYPE_ARRAY_BOOL = new Type(Code.ARRAY, TYPE_BOOL, null);
private static final Type TYPE_ARRAY_INT64 = new Type(Code.ARRAY, TYPE_INT64, null);
private static final Type TYPE_ARRAY_FLOAT32 = new Type(Code.ARRAY, TYPE_FLOAT32, null);
@@ -72,6 +73,7 @@ public final class Type implements Serializable {
private static final Type TYPE_ARRAY_BYTES = new Type(Code.ARRAY, TYPE_BYTES, null);
private static final Type TYPE_ARRAY_TIMESTAMP = new Type(Code.ARRAY, TYPE_TIMESTAMP, null);
private static final Type TYPE_ARRAY_DATE = new Type(Code.ARRAY, TYPE_DATE, null);
+ private static final Type TYPE_ARRAY_INTERVAL = new Type(Code.ARRAY, TYPE_INTERVAL, null);
private static final int AMBIGUOUS_FIELD = -1;
private static final long serialVersionUID = -3076152125004114582L;
@@ -183,6 +185,16 @@ public static Type date() {
return TYPE_DATE;
}
+ /**
+ * Returns the descriptor for the {@code INTERVAL} type: an interval which represents a time
+ * duration as a tuple of 3 values (months, days, nanoseconds). [Interval(months:-120000, days:
+ * -3660000, nanoseconds: -316224000000000000000), Interval(months:120000, days: 3660000,
+ * nanoseconds: 316224000000000000000)].
+ */
+ public static Type interval() {
+ return TYPE_INTERVAL;
+ }
+
/** Returns a descriptor for an array of {@code elementType}. */
public static Type array(Type elementType) {
Preconditions.checkNotNull(elementType);
@@ -213,6 +225,8 @@ public static Type array(Type elementType) {
return TYPE_ARRAY_TIMESTAMP;
case DATE:
return TYPE_ARRAY_DATE;
+ case INTERVAL:
+ return TYPE_ARRAY_INTERVAL;
default:
return new Type(Code.ARRAY, elementType, null);
}
@@ -295,6 +309,7 @@ public enum Code {
BYTES(TypeCode.BYTES, "bytea"),
TIMESTAMP(TypeCode.TIMESTAMP, "timestamp with time zone"),
DATE(TypeCode.DATE, "date"),
+ INTERVAL(TypeCode.INTERVAL, "interval"),
ARRAY(TypeCode.ARRAY, "array"),
STRUCT(TypeCode.STRUCT, "struct");
@@ -610,6 +625,8 @@ static Type fromProto(com.google.spanner.v1.Type proto) {
return timestamp();
case DATE:
return date();
+ case INTERVAL:
+ return interval();
case PROTO:
return proto(proto.getProtoTypeFqn());
case ENUM:
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 c2c851d6dd..13ffc3c622 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
@@ -245,6 +245,15 @@ public static Value json(@Nullable String v) {
return new JsonImpl(v == null, v);
}
+ /**
+ * Returns a {@code INTERVAL} value.
+ *
+ * @param interval the value, which may be null
+ */
+ public static Value interval(@Nullable Interval interval) {
+ return new IntervalImpl(interval == null, interval);
+ }
+
/**
* Returns a {@code PG JSONB} value.
*
@@ -776,6 +785,16 @@ public static Value dateArray(@Nullable Iterable v) {
return new DateArrayImpl(v == null, v == null ? null : immutableCopyOf(v));
}
+ /**
+ * Returns an {@code ARRAY} value.
+ *
+ * @param v the source of element values. This may be {@code null} to produce a value for which
+ * {@code isNull()} is {@code true}. Individual elements may also be {@code null}.
+ */
+ public static Value intervalArray(@Nullable Iterable v) {
+ return new IntervalArrayImpl(v == null, v == null ? null : immutableCopyOf(v));
+ }
+
/**
* Returns an {@code ARRAY>} value.
*
@@ -915,6 +934,13 @@ public T getProtoEnum(
*/
public abstract Date getDate();
+ /**
+ * Returns the value of a {@code INTERVAL}-typed instance.
+ *
+ * @throws IllegalStateException if {@code isNull()} or the value is not of the expected type
+ */
+ public abstract Interval getInterval();
+
/**
* Returns the value of a {@code STRUCT}-typed instance.
*
@@ -1035,6 +1061,14 @@ public List getProtoEnumArray(
*/
public abstract List getDateArray();
+ /**
+ * Returns the value of an {@code ARRAY}-typed instance. While the returned list itself
+ * will never be {@code null}, elements of that list may be null.
+ *
+ * @throws IllegalStateException if {@code isNull()} or the value is not of the expected type
+ */
+ public abstract List getIntervalArray();
+
/**
* Returns the value of an {@code ARRAY>}-typed instance. While the returned list
* itself will never be {@code null}, elements of that list may be null.
@@ -1314,6 +1348,11 @@ public Date getDate() {
throw defaultGetter(Type.date());
}
+ @Override
+ public Interval getInterval() {
+ throw defaultGetter(Type.interval());
+ }
+
@Override
public Struct getStruct() {
if (getType().getCode() != Type.Code.STRUCT) {
@@ -1378,6 +1417,11 @@ public List getDateArray() {
throw defaultGetter(Type.array(Type.date()));
}
+ @Override
+ public List getIntervalArray() {
+ throw defaultGetter(Type.array(Type.interval()));
+ }
+
@Override
public List getStructArray() {
if (getType().getCode() != Type.Code.ARRAY
@@ -1795,6 +1839,29 @@ void valueToString(StringBuilder b) {
}
}
+ private static class IntervalImpl extends AbstractObjectValue {
+
+ private IntervalImpl(boolean isNull, Interval value) {
+ super(isNull, Type.interval(), value);
+ }
+
+ @Override
+ public Interval getInterval() {
+ checkNotNull();
+ return value;
+ }
+
+ @Override
+ void valueToString(StringBuilder b) {
+ b.append(value.ToISO8601());
+ }
+
+ @Override
+ com.google.protobuf.Value valueToProto() {
+ return com.google.protobuf.Value.newBuilder().setStringValue(value.ToISO8601()).build();
+ }
+ }
+
private static class StringImpl extends AbstractObjectValue {
private StringImpl(boolean isNull, @Nullable String value) {
@@ -2797,6 +2864,29 @@ void appendElement(StringBuilder b, Date element) {
}
}
+ private static class IntervalArrayImpl extends AbstractArrayValue {
+
+ private IntervalArrayImpl(boolean isNull, @Nullable List values) {
+ super(isNull, Type.interval(), values);
+ }
+
+ @Override
+ public List getIntervalArray() {
+ checkNotNull();
+ return value;
+ }
+
+ @Override
+ void appendElement(StringBuilder b, Interval element) {
+ b.append(element.ToISO8601());
+ }
+
+ @Override
+ String elementToString(Interval element){
+ return element.ToISO8601();
+ }
+ }
+
private static class NumericArrayImpl extends AbstractArrayValue {
private NumericArrayImpl(boolean isNull, @Nullable List values) {
@@ -2940,6 +3030,8 @@ private Value getValue(int fieldIndex) {
return Value.date(value.getDate(fieldIndex));
case TIMESTAMP:
return Value.timestamp(value.getTimestamp(fieldIndex));
+ case INTERVAL:
+ return Value.interval(value.getInterval(fieldIndex));
case PROTO:
return Value.protoMessage(value.getBytes(fieldIndex), fieldType.getProtoTypeFqn());
case ENUM:
@@ -2978,6 +3070,8 @@ private Value getValue(int fieldIndex) {
return Value.dateArray(value.getDateList(fieldIndex));
case TIMESTAMP:
return Value.timestampArray(value.getTimestampList(fieldIndex));
+ case INTERVAL:
+ return Value.intervalArray(value.getIntervalList(fieldIndex));
case STRUCT:
return Value.structArray(elementType, value.getStructList(fieldIndex));
case ARRAY:
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java
index 8386bd5c21..45b08ca582 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java
@@ -165,6 +165,11 @@ public R to(@Nullable Date value) {
return handle(Value.date(value));
}
+ /** Binds to {@code Value.interval(value)} */
+ public R to(@Nullable Interval value) {
+ return handle(Value.interval(value));
+ }
+
/** Binds a non-{@code NULL} struct value to {@code Value.struct(value)} */
public R to(Struct value) {
return handle(Value.struct(value));
@@ -323,6 +328,11 @@ public R toDateArray(@Nullable Iterable values) {
return handle(Value.dateArray(values));
}
+ /** Binds to {@code Value.intervalArray(values)} */
+ public R toIntervalArray(@Nullable Iterable values) {
+ return handle(Value.intervalArray(values));
+ }
+
/** Binds to {@code Value.structArray(fieldTypes, values)} */
public R toStructArray(Type elementType, @Nullable Iterable values) {
return handle(Value.structArray(elementType, values));
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java
index b5e4060ddd..bd8271bb99 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java
@@ -19,12 +19,7 @@
import com.google.cloud.ByteArray;
import com.google.cloud.Date;
import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.ProtobufResultSet;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.Struct;
-import com.google.cloud.spanner.Type;
-import com.google.cloud.spanner.Value;
+import com.google.cloud.spanner.*;
import com.google.common.base.Preconditions;
import com.google.protobuf.AbstractMessage;
import com.google.protobuf.ProtocolMessageEnum;
@@ -288,6 +283,18 @@ public Date getDate(String columnName) {
return delegate.getDate(columnName);
}
+ @Override
+ public Interval getInterval(int columnIndex) {
+ Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
+ return delegate.getInterval(columnIndex);
+ }
+
+ @Override
+ public Interval getInterval(String columnName) {
+ Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
+ return delegate.getInterval(columnName);
+ }
+
@Override
public Value getValue(int columnIndex) {
Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
@@ -480,6 +487,18 @@ public List getDateList(String columnName) {
return delegate.getDateList(columnName);
}
+ @Override
+ public List getIntervalList(int columnIndex) {
+ Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
+ return delegate.getIntervalList(columnIndex);
+ }
+
+ @Override
+ public List getIntervalList(String columnName) {
+ Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
+ return delegate.getIntervalList(columnName);
+ }
+
@Override
public List getProtoMessageList(int columnIndex, T message) {
Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java
index bd7c794a0f..59229037e1 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java
@@ -19,14 +19,7 @@
import com.google.cloud.ByteArray;
import com.google.cloud.Date;
import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.ProtobufResultSet;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.Struct;
-import com.google.cloud.spanner.Type;
-import com.google.cloud.spanner.Value;
+import com.google.cloud.spanner.*;
import com.google.common.base.Preconditions;
import com.google.protobuf.AbstractMessage;
import com.google.protobuf.ProtocolMessageEnum;
@@ -297,6 +290,18 @@ public Date getDate(String columnName) {
return delegate.getDate(columnName);
}
+ @Override
+ public Interval getInterval(int columnIndex) {
+ checkClosed();
+ return delegate.getInterval(columnIndex);
+ }
+
+ @Override
+ public Interval getInterval(String columnName) {
+ checkClosed();
+ return delegate.getInterval(columnName);
+ }
+
@Override
public Value getValue(int columnIndex) {
checkClosed();
@@ -489,6 +494,18 @@ public List getDateList(String columnName) {
return delegate.getDateList(columnName);
}
+ @Override
+ public List getIntervalList(int columnIndex) {
+ checkClosed();
+ return delegate.getIntervalList(columnIndex);
+ }
+
+ @Override
+ public List getIntervalList(String columnName) {
+ checkClosed();
+ return delegate.getIntervalList(columnName);
+ }
+
@Override
public List getProtoMessageList(int columnIndex, T message) {
checkClosed();
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 595bbcaf26..8a036065bd 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
@@ -103,6 +103,11 @@ protected Date getDateInternal(int columnIndex) {
return null;
}
+ @Override
+ protected Interval getIntervalInternal(int columnIndex) {
+ return null;
+ }
+
@Override
protected T getProtoMessageInternal(int columnIndex, T message) {
return null;
@@ -206,6 +211,11 @@ protected List getDateListInternal(int columnIndex) {
return null;
}
+ @Override
+ protected List getIntervalListInternal(int columnIndex) {
+ return null;
+ }
+
@Override
protected List getStructListInternal(int columnIndex) {
return null;
@@ -301,6 +311,13 @@ public static Collection