Skip to content

Commit 2ea6432

Browse files
committed
Fix #77: add codecs for timestamp, time and date types
1 parent 36167e2 commit 2ea6432

13 files changed

+675
-50
lines changed

Diff for: CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1919
- Use an extended implementation of `UdtValue`, including a string representation of the value, for UDT values returned
2020
by `CassandraResultSet.getObject(String|int)` methods (see issue
2121
[#76](https://github.com/ing-bank/cassandra-jdbc-wrapper/issues/76)).
22+
- Add support for collections of `timestamp`, `date` and `time` values (see issue
23+
[#77](https://github.com/ing-bank/cassandra-jdbc-wrapper/issues/77)) by adding specific codecs for
24+
`java.sql.Timestamp`, `java.sql.Date` and `java.sql.Time` types.
2225

2326
## [4.14.0] - 2024-12-24
2427
### Added
@@ -336,6 +339,7 @@ For this version, the changelog lists the main changes comparatively to the late
336339
- Fix logs in `CassandraConnection` constructor.
337340

338341
[original project]: https://github.com/adejanovski/cassandra-jdbc-wrapper/
342+
[4.15.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.14.0...v4.15.0
339343
[4.14.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.13.1...v4.14.0
340344
[4.13.1]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.13.0...v4.13.1
341345
[4.13.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.12.0...v4.13.0

Diff for: src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java

+2-25
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,6 @@
2222
import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
2323
import com.datastax.oss.driver.api.core.metadata.Metadata;
2424
import com.datastax.oss.driver.api.core.session.Session;
25-
import com.datastax.oss.driver.api.core.type.codec.TypeCodec;
26-
import com.ing.data.cassandra.jdbc.codec.BigintToBigDecimalCodec;
27-
import com.ing.data.cassandra.jdbc.codec.DecimalToDoubleCodec;
28-
import com.ing.data.cassandra.jdbc.codec.FloatToDoubleCodec;
29-
import com.ing.data.cassandra.jdbc.codec.IntToLongCodec;
30-
import com.ing.data.cassandra.jdbc.codec.LongToIntCodec;
31-
import com.ing.data.cassandra.jdbc.codec.SmallintToIntCodec;
32-
import com.ing.data.cassandra.jdbc.codec.TimestampToLongCodec;
33-
import com.ing.data.cassandra.jdbc.codec.TinyintToIntCodec;
34-
import com.ing.data.cassandra.jdbc.codec.TinyintToShortCodec;
35-
import com.ing.data.cassandra.jdbc.codec.VarintToIntCodec;
3625
import com.ing.data.cassandra.jdbc.optionset.Default;
3726
import com.ing.data.cassandra.jdbc.optionset.OptionSet;
3827
import org.apache.commons.lang3.StringUtils;
@@ -48,9 +37,7 @@
4837
import java.sql.SQLTimeoutException;
4938
import java.sql.SQLWarning;
5039
import java.sql.Statement;
51-
import java.util.ArrayList;
5240
import java.util.HashMap;
53-
import java.util.List;
5441
import java.util.Map;
5542
import java.util.Objects;
5643
import java.util.Properties;
@@ -69,6 +56,7 @@
6956
import static com.ing.data.cassandra.jdbc.CassandraResultSet.DEFAULT_HOLDABILITY;
7057
import static com.ing.data.cassandra.jdbc.CassandraResultSet.DEFAULT_TYPE;
7158
import static com.ing.data.cassandra.jdbc.utils.AwsUtil.AWS_KEYSPACES_HOSTS_REGEX;
59+
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.PRECONFIGURED_CODECS;
7260
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.safelyRegisterCodecs;
7361
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.toStringWithoutSensitiveValues;
7462
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.ALWAYS_AUTOCOMMIT;
@@ -245,18 +233,7 @@ public CassandraConnection(final Session cSession, final String currentKeyspace,
245233
this.serialConsistencyLevel = ConsistencyLevel.SERIAL;
246234
this.debugMode = debugMode;
247235
setActiveExecutionProfile(Objects.toString(defaultExecutionProfile, DriverExecutionProfile.DEFAULT_NAME));
248-
final List<TypeCodec<?>> codecs = new ArrayList<>();
249-
codecs.add(new TimestampToLongCodec());
250-
codecs.add(new LongToIntCodec());
251-
codecs.add(new IntToLongCodec());
252-
codecs.add(new BigintToBigDecimalCodec());
253-
codecs.add(new DecimalToDoubleCodec());
254-
codecs.add(new FloatToDoubleCodec());
255-
codecs.add(new VarintToIntCodec());
256-
codecs.add(new SmallintToIntCodec());
257-
codecs.add(new TinyintToIntCodec());
258-
codecs.add(new TinyintToShortCodec());
259-
safelyRegisterCodecs(cSession, codecs);
236+
safelyRegisterCodecs(cSession, PRECONFIGURED_CODECS);
260237
}
261238

262239
/**

Diff for: src/main/java/com/ing/data/cassandra/jdbc/SessionHolder.java

+3-23
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,6 @@
3131
import com.datastax.oss.driver.internal.core.loadbalancing.DefaultLoadBalancingPolicy;
3232
import com.datastax.oss.driver.internal.core.ssl.DefaultSslEngineFactory;
3333
import com.github.benmanes.caffeine.cache.LoadingCache;
34-
import com.ing.data.cassandra.jdbc.codec.BigintToBigDecimalCodec;
35-
import com.ing.data.cassandra.jdbc.codec.DecimalToDoubleCodec;
36-
import com.ing.data.cassandra.jdbc.codec.FloatToDoubleCodec;
37-
import com.ing.data.cassandra.jdbc.codec.IntToLongCodec;
38-
import com.ing.data.cassandra.jdbc.codec.LongToIntCodec;
39-
import com.ing.data.cassandra.jdbc.codec.SmallintToIntCodec;
40-
import com.ing.data.cassandra.jdbc.codec.TimestampToLongCodec;
41-
import com.ing.data.cassandra.jdbc.codec.TinyintToIntCodec;
42-
import com.ing.data.cassandra.jdbc.codec.TinyintToShortCodec;
43-
import com.ing.data.cassandra.jdbc.codec.VarintToIntCodec;
4434
import com.ing.data.cassandra.jdbc.utils.AwsUtil;
4535
import com.ing.data.cassandra.jdbc.utils.ContactPoint;
4636
import com.instaclustr.cassandra.driver.auth.KerberosAuthProviderBase;
@@ -69,6 +59,7 @@
6959
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.JSSE_KEYSTORE_PROPERTY;
7060
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.JSSE_TRUSTSTORE_PASSWORD_PROPERTY;
7161
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.JSSE_TRUSTSTORE_PROPERTY;
62+
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.PRECONFIGURED_CODECS;
7263
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.SINGLE_QUOTE;
7364
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.redactSensitiveValuesInJdbcUrl;
7465
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.toStringWithoutSensitiveValues;
@@ -369,19 +360,8 @@ private Session createSession(final Properties properties) throws SQLException {
369360
builder.withAuthProvider(new SigV4AuthProvider(awsRegion));
370361
}
371362

372-
// Declare and register codecs.
373-
final List<TypeCodec<?>> codecs = new ArrayList<>();
374-
codecs.add(new TimestampToLongCodec());
375-
codecs.add(new LongToIntCodec());
376-
codecs.add(new IntToLongCodec());
377-
codecs.add(new BigintToBigDecimalCodec());
378-
codecs.add(new DecimalToDoubleCodec());
379-
codecs.add(new FloatToDoubleCodec());
380-
codecs.add(new VarintToIntCodec());
381-
codecs.add(new SmallintToIntCodec());
382-
codecs.add(new TinyintToIntCodec());
383-
codecs.add(new TinyintToShortCodec());
384-
builder.addTypeCodecs(codecs.toArray(new TypeCodec[]{}));
363+
// Register codecs.
364+
builder.addTypeCodecs(PRECONFIGURED_CODECS.toArray(new TypeCodec[]{}));
385365

386366
builder.withKeyspace(keyspace);
387367
builder.withConfigLoader(driverConfigLoaderBuilder.build());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package com.ing.data.cassandra.jdbc.codec;
17+
18+
import com.datastax.oss.driver.api.core.ProtocolVersion;
19+
import com.datastax.oss.driver.api.core.type.DataType;
20+
import com.datastax.oss.driver.api.core.type.DataTypes;
21+
import com.datastax.oss.driver.api.core.type.codec.TypeCodec;
22+
import com.datastax.oss.driver.api.core.type.reflect.GenericType;
23+
import com.datastax.oss.driver.internal.core.type.codec.DateCodec;
24+
import com.datastax.oss.driver.internal.core.util.Strings;
25+
import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil;
26+
27+
import javax.annotation.Nonnull;
28+
import java.nio.ByteBuffer;
29+
import java.sql.Date;
30+
import java.time.LocalDate;
31+
32+
import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
33+
34+
/**
35+
* Manages the two-way conversion between the CQL type {@link DataTypes#DATE} and the Java type {@link java.sql.Date}.
36+
*
37+
* @implNote The CQL type {@code DATE} is stored as an unsigned 32-bit integer.
38+
*/
39+
public class SqlDateCodec extends AbstractCodec<Date> implements TypeCodec<Date> {
40+
41+
/**
42+
* Constructor for {@code SqlDateCodec}.
43+
*/
44+
public SqlDateCodec() {
45+
}
46+
47+
@Nonnull
48+
@Override
49+
public GenericType<Date> getJavaType() {
50+
return GenericType.of(Date.class);
51+
}
52+
53+
@Nonnull
54+
@Override
55+
public DataType getCqlType() {
56+
return DataTypes.DATE;
57+
}
58+
59+
@Override
60+
public ByteBuffer encode(final Date value, @Nonnull final ProtocolVersion protocolVersion) {
61+
if (value == null) {
62+
return null;
63+
}
64+
return ByteBufferUtil.bytes(signedToUnsigned((int) value.toLocalDate().toEpochDay()));
65+
}
66+
67+
@Override
68+
public Date decode(final ByteBuffer bytes, @Nonnull final ProtocolVersion protocolVersion) {
69+
if (bytes == null) {
70+
return null;
71+
}
72+
// always duplicate the ByteBuffer instance before consuming it!
73+
final int readInt = ByteBufferUtil.toInt(bytes.duplicate());
74+
return Date.valueOf(LocalDate.ofEpochDay(unsignedToSigned(readInt)));
75+
}
76+
77+
@Override
78+
Date parseNonNull(@Nonnull final String value) {
79+
// Re-use the parser of the standard DateCodec to handle the formats supported by Cassandra.
80+
final LocalDate parsedLocalDate = new DateCodec().parse(value);
81+
if (parsedLocalDate == null) {
82+
return null;
83+
}
84+
return Date.valueOf(parsedLocalDate);
85+
}
86+
87+
@Override
88+
String formatNonNull(@Nonnull final Date value) {
89+
return Strings.quote(ISO_LOCAL_DATE.format(value.toLocalDate()));
90+
}
91+
92+
static int signedToUnsigned(final int signed) {
93+
return signed - Integer.MIN_VALUE;
94+
}
95+
96+
static int unsignedToSigned(final int unsigned) {
97+
return unsigned + Integer.MIN_VALUE;
98+
}
99+
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package com.ing.data.cassandra.jdbc.codec;
17+
18+
import com.datastax.oss.driver.api.core.ProtocolVersion;
19+
import com.datastax.oss.driver.api.core.type.DataType;
20+
import com.datastax.oss.driver.api.core.type.DataTypes;
21+
import com.datastax.oss.driver.api.core.type.codec.TypeCodec;
22+
import com.datastax.oss.driver.api.core.type.reflect.GenericType;
23+
import com.datastax.oss.driver.internal.core.type.codec.TimeCodec;
24+
import com.datastax.oss.driver.internal.core.util.Strings;
25+
import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil;
26+
27+
import javax.annotation.Nonnull;
28+
import java.nio.ByteBuffer;
29+
import java.sql.Time;
30+
import java.time.LocalTime;
31+
import java.time.format.DateTimeFormatter;
32+
33+
/**
34+
* Manages the two-way conversion between the CQL type {@link DataTypes#TIME} and the Java type {@link Time}.
35+
*/
36+
public class SqlTimeCodec extends AbstractCodec<Time> implements TypeCodec<Time> {
37+
38+
/**
39+
* The default time format used by the method {@link #formatNonNull(Time)}.
40+
*/
41+
public static final DateTimeFormatter DEFAULT_TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSSSSS");
42+
43+
/**
44+
* Constructor for {@code SqlTimeCodec}.
45+
*/
46+
public SqlTimeCodec() {
47+
}
48+
49+
@Nonnull
50+
@Override
51+
public GenericType<Time> getJavaType() {
52+
return GenericType.of(Time.class);
53+
}
54+
55+
@Nonnull
56+
@Override
57+
public DataType getCqlType() {
58+
return DataTypes.TIME;
59+
}
60+
61+
@Override
62+
public ByteBuffer encode(final Time value, @Nonnull final ProtocolVersion protocolVersion) {
63+
if (value == null) {
64+
return null;
65+
}
66+
return ByteBufferUtil.bytes(value.toLocalTime().toNanoOfDay());
67+
}
68+
69+
@Override
70+
public Time decode(final ByteBuffer bytes, @Nonnull final ProtocolVersion protocolVersion) {
71+
if (bytes == null) {
72+
return null;
73+
}
74+
// always duplicate the ByteBuffer instance before consuming it!
75+
return Time.valueOf(LocalTime.ofNanoOfDay(ByteBufferUtil.toLong(bytes.duplicate())));
76+
}
77+
78+
@Override
79+
Time parseNonNull(@Nonnull final String value) {
80+
// Re-use the parser of the standard TimeCodec to handle the formats supported by Cassandra.
81+
final LocalTime parsedLocalTime = new TimeCodec().parse(value);
82+
if (parsedLocalTime == null) {
83+
return null;
84+
}
85+
return Time.valueOf(parsedLocalTime);
86+
}
87+
88+
@Override
89+
String formatNonNull(@Nonnull final Time value) {
90+
return Strings.quote(DEFAULT_TIME_FORMAT.format(value.toLocalTime()));
91+
}
92+
93+
}

0 commit comments

Comments
 (0)