Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static com.google.cloud.spanner.jdbc.JdbcTypeConverter.getMainTypeCode;

import com.google.cloud.spanner.Dialect;
import com.google.cloud.spanner.Interval;
import com.google.cloud.spanner.Type;
import com.google.cloud.spanner.Type.Code;
import com.google.common.base.Preconditions;
Expand Down Expand Up @@ -174,6 +175,8 @@ static String getClassName(Type type) {
return String.class.getName();
case TIMESTAMP:
return Timestamp.class.getName();
case INTERVAL:
return Interval.class.getName();
case ARRAY:
switch (getMainTypeCode(type.getArrayElementType())) {
case BOOL:
Expand All @@ -199,6 +202,8 @@ static String getClassName(Type type) {
return String[].class.getName();
case TIMESTAMP:
return Timestamp[].class.getName();
case INTERVAL:
return Interval[].class.getName();
}
case STRUCT:
default:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2026 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.jdbc;

import com.google.spanner.v1.TypeCode;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLType;

/**
* Custom SQL type for Spanner INTERVAL data type. This type (or the vendor type number) must be
* used when setting an INTERVAL parameter using {@link PreparedStatement#setObject(int, Object,
* SQLType)}.
*/
public class IntervalType implements SQLType {
public static final IntervalType INSTANCE = new IntervalType();

/**
* Spanner does not have any type numbers, but the code values are unique. Add 100,000 to avoid
* conflicts with the type numbers in java.sql.Types.
*/
public static final int VENDOR_TYPE_NUMBER = 100_000 + TypeCode.INTERVAL_VALUE;

/**
* Define a short type number as well, as this is what is expected to be returned in {@link
* DatabaseMetaData#getTypeInfo()}.
*/
public static final short SHORT_VENDOR_TYPE_NUMBER = (short) VENDOR_TYPE_NUMBER;

private IntervalType() {}

@Override
public String getName() {
return "INTERVAL";
}

@Override
public String getVendor() {
return IntervalType.class.getPackage().getName();
}

@Override
public Integer getVendorTypeNumber() {
return VENDOR_TYPE_NUMBER;
}

@Override
public String toString() {
return getName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.google.cloud.spanner.jdbc;

import com.google.cloud.ByteArray;
import com.google.cloud.spanner.Interval;
import com.google.cloud.spanner.ResultSets;
import com.google.cloud.spanner.Struct;
import com.google.cloud.spanner.Type;
Expand All @@ -37,6 +38,8 @@
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Timestamp;
import java.time.Duration;
import java.time.Period;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -97,6 +100,8 @@ private JdbcArray(JdbcDataType type, Object[] elements) throws SQLException {
// Convert Byte[], Short[], and Integer[] to Long[] for INT64 type
// since Spanner only supports ARRAY<INT64>
this.data = convertToLongArray(elements);
} else if (type == JdbcDataType.INTERVAL) {
this.data = convertToIntervalArray(elements);
} else {
this.data = java.lang.reflect.Array.newInstance(type.getJavaClass(), elements.length);
try {
Expand Down Expand Up @@ -129,11 +134,44 @@ private static Long[] convertToLongArray(Object[] elements) {
return longElements;
}

private static Interval[] convertToIntervalArray(Object[] elements) throws SQLException {
Interval[] intervalElements = new Interval[elements.length];
for (int i = 0; i < elements.length; i++) {
if (elements[i] == null) {
intervalElements[i] = null;
} else if (elements[i] instanceof Duration) {
intervalElements[i] = JdbcTypeConverter.toInterval((Duration) elements[i]);
} else if (elements[i] instanceof Period) {
intervalElements[i] = JdbcTypeConverter.toInterval((Period) elements[i]);
} else if (elements[i] instanceof Interval) {
intervalElements[i] = (Interval) elements[i];
} else {
throw JdbcSqlExceptionFactory.of(
"Could not copy array elements. Make sure the supplied array only contains elements of class "
+ Interval.class.getName()
+ ", "
+ Duration.class.getName()
+ ", or "
+ Period.class.getName(),
Code.UNKNOWN);
}
}
return intervalElements;
}

private JdbcArray(JdbcDataType type, List<?> elements) {
this.type = type;
if (elements != null) {
this.data = java.lang.reflect.Array.newInstance(type.getJavaClass(), elements.size());
elements.toArray((Object[]) data);
if (type == JdbcDataType.INTERVAL) {
try {
this.data = convertToIntervalArray(elements.toArray());
} catch (SQLException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
} else {
this.data = java.lang.reflect.Array.newInstance(type.getJavaClass(), elements.size());
elements.toArray((Object[]) data);
}
}
}

Expand Down Expand Up @@ -281,6 +319,9 @@ public ResultSet getResultSet(long startIndex, int count) throws SQLException {
case TIMESTAMP:
builder = binder.to(JdbcTypeConverter.toGoogleTimestamp((Timestamp) value));
break;
case INTERVAL:
builder = binder.to((Interval) value);
break;
case ARRAY:
case STRUCT:
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.google.cloud.spanner.jdbc;

import com.google.cloud.spanner.Dialect;
import com.google.cloud.spanner.Interval;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.Struct;
import com.google.cloud.spanner.Type;
Expand All @@ -25,6 +26,8 @@
import java.sql.Date;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Duration;
import java.time.Period;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
Expand Down Expand Up @@ -435,6 +438,46 @@ public Type getSpannerType() {
return Type.uuid();
}
},
INTERVAL {
private final Set<Class<?>> classes =
new HashSet<>(Arrays.asList(Interval.class, Duration.class, Period.class));
private final Set<String> aliases = new HashSet<>(Collections.singletonList("interval"));

@Override
public int getSqlType() {
return IntervalType.VENDOR_TYPE_NUMBER;
}

@Override
public Class<Interval> getJavaClass() {
return Interval.class;
}

@Override
public Set<Class<?>> getSupportedJavaClasses() {
return classes;
}

@Override
public Code getCode() {
return Code.INTERVAL;
}

@Override
public List<Interval> getArrayElements(ResultSet resultSet, int columnIndex) {
return resultSet.getIntervalList(columnIndex);
}

@Override
public Type getSpannerType() {
return Type.interval();
}

@Override
public Set<String> getPostgreSQLAliases() {
return aliases;
}
},
STRUCT {
@Override
public int getSqlType() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,44 @@ public ResultSet getTypeInfo() {
.set("NUM_PREC_RADIX")
.to((Long) null)
.build(),
Struct.newBuilder()
.set("TYPE_NAME")
.to("INTERVAL")
.set("DATA_TYPE")
.to((long) IntervalType.VENDOR_TYPE_NUMBER)
.set("PRECISION")
.to((Long) null)
.set("LITERAL_PREFIX")
.to("INTERVAL ")
.set("LITERAL_SUFFIX")
.to((String) null)
.set("CREATE_PARAMS")
.to((String) null)
.set("NULLABLE")
.to(DatabaseMetaData.typeNullable)
.set("CASE_SENSITIVE")
.to(false)
.set("SEARCHABLE")
.to(DatabaseMetaData.typePredBasic)
.set("UNSIGNED_ATTRIBUTE")
.to(false)
.set("FIXED_PREC_SCALE")
.to(false)
.set("AUTO_INCREMENT")
.to(false)
.set("LOCAL_TYPE_NAME")
.to("INTERVAL")
.set("MINIMUM_SCALE")
.to(0)
.set("MAXIMUM_SCALE")
.to(0)
.set("SQL_DATA_TYPE")
.to((Long) null)
.set("SQL_DATETIME_SUB")
.to((Long) null)
.set("NUM_PREC_RADIX")
.to((Long) null)
.build(),
getJsonType(connection.getDialect()))),
// Allow column 2 to be cast to short without any range checks.
ImmutableSet.of(2));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.cloud.spanner.jdbc;

import com.google.cloud.spanner.Interval;
import com.google.cloud.spanner.JdbcDataTypeConverter;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.Statement;
Expand All @@ -31,6 +32,9 @@
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Duration;
import java.time.Period;
import java.util.UUID;

/** {@link ParameterMetaData} implementation for Cloud Spanner */
class JdbcParameterMetaData extends AbstractJdbcWrapper implements ParameterMetaData {
Expand Down Expand Up @@ -156,6 +160,12 @@ private int getParameterTypeFromValue(int param) {
return Types.NVARCHAR;
} else if (byte[].class.isAssignableFrom(value.getClass())) {
return Types.BINARY;
} else if (Interval.class.isAssignableFrom(value.getClass())
|| Duration.class.isAssignableFrom(value.getClass())
|| Period.class.isAssignableFrom(value.getClass())) {
return IntervalType.VENDOR_TYPE_NUMBER;
} else if (UUID.class.isAssignableFrom(value.getClass())) {
return UuidType.VENDOR_TYPE_NUMBER;
} else {
return Types.OTHER;
}
Expand Down
Loading
Loading