diff --git a/changelog/README.md b/changelog/README.md index fb943a3e6d5..e48125960f8 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -7,6 +7,7 @@ - [new feature] JAVA-1362: Send query options flags as [int] for Protocol V5+. - [improvement] JAVA-1367: Make protocol negotiation more resilient. - [bug] JAVA-1397: Handle duration as native datatype in protocol v5+. +- [improvement] JAVA-1308: CodecRegistry performance improvements. Merged from 3.1.x branch: diff --git a/driver-core/src/main/java/com/datastax/driver/core/CodecRegistry.java b/driver-core/src/main/java/com/datastax/driver/core/CodecRegistry.java index 65928cf56ce..e62957f8f71 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/CodecRegistry.java +++ b/driver-core/src/main/java/com/datastax/driver/core/CodecRegistry.java @@ -18,7 +18,6 @@ import com.datastax.driver.core.exceptions.CodecNotFoundException; import com.google.common.base.Objects; import com.google.common.cache.*; -import com.google.common.collect.ImmutableSet; import com.google.common.reflect.TypeToken; import com.google.common.util.concurrent.UncheckedExecutionException; import org.slf4j.Logger; @@ -27,6 +26,7 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.nio.ByteBuffer; +import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -49,8 +49,7 @@ * The only reason to create your own instances is if you have multiple {@code Cluster} objects that use different * sets of codecs. In that case, use {@link com.datastax.driver.core.Cluster.Builder#withCodecRegistry(CodecRegistry)} * to associate the registry with the cluster: - *
- * {@code
+ * {@code
* CodecRegistry myCodecRegistry = new CodecRegistry();
* myCodecRegistry.register(myCodec1, myCodec2, myCodec3);
* Cluster cluster = Cluster.builder().withCodecRegistry(myCodecRegistry).build();
@@ -68,8 +67,7 @@
* To create a custom codec, write a class that extends {@link TypeCodec}, create an instance, and pass it to one of
* the {@link #register(TypeCodec) register} methods; for example, one could create a codec that maps CQL
* timestamps to JDK8's {@code java.time.LocalDate}:
- *
- * {@code
+ * {@code
* class LocalDateCodec extends TypeCodec {
* ...
* }
@@ -85,8 +83,7 @@
*
*
* Example:
- *
- * {@code
+ * {@code
* Row row = session.executeQuery("select date from some_table where pk = 1").one();
* java.time.LocalDate date = row.get(0, java.time.LocalDate.class); // uses LocalDateCodec registered above}
*
@@ -104,7 +101,7 @@
* as well as all {@code Map} types whose keys and/or values are {@code java.time.LocalDate}.
* This works recursively for nested collections;
* {@link UserType user types}, mapped to {@link UDTValue} objects. Custom codecs are available recursively
- * to the UDT's fields, so if one of your fields is a {@code timestamp} you can use your {@code LocaDateCodec} to retrieve
+ * to the UDT's fields, so if one of your fields is a {@code timestamp} you can use your {@code LocalDateCodec} to retrieve
* it as a {@code java.time.LocalDate};
* {@link TupleType tuple types}, mapped to {@link TupleValue} (with the same rules for nested fields);
* {@link com.datastax.driver.core.DataType.CustomType custom types}, mapped to {@code ByteBuffer}.
@@ -129,7 +126,7 @@
* When the registry looks up a codec, the rules of precedence are:
*
* - if a result was previously cached for that mapping, it is returned;
- * - otherwise, the registry checks the list of "basic" codecs: the default ones, and the ones that were explicitly
+ *
- otherwise, the registry checks the list of built-in codecs – the default ones – and the ones that were explicitly
* registered (in the order that they were registered). It calls each codec's {@code accepts} methods to determine if
* it can handle the mapping, and if so returns it;
* - otherwise, the registry tries to generate a codec, according to the rules outlined above.
@@ -141,29 +138,55 @@ public final class CodecRegistry {
private static final Logger logger = LoggerFactory.getLogger(CodecRegistry.class);
- @SuppressWarnings("unchecked")
- private static final ImmutableSet> PRIMITIVE_CODECS = ImmutableSet.of(
- TypeCodec.blob(),
- TypeCodec.cboolean(),
- TypeCodec.smallInt(),
- TypeCodec.tinyInt(),
+ private static final Map> BUILT_IN_CODECS_MAP = new EnumMap>(DataType.Name.class);
+
+ static {
+ BUILT_IN_CODECS_MAP.put(DataType.Name.ASCII, TypeCodec.ascii());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.BIGINT, TypeCodec.bigint());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.BLOB, TypeCodec.blob());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.BOOLEAN, TypeCodec.cboolean());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.COUNTER, TypeCodec.counter());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.DECIMAL, TypeCodec.decimal());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.DOUBLE, TypeCodec.cdouble());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.FLOAT, TypeCodec.cfloat());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.INET, TypeCodec.inet());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.INT, TypeCodec.cint());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.TEXT, TypeCodec.varchar());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.TIMESTAMP, TypeCodec.timestamp());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.UUID, TypeCodec.uuid());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.VARCHAR, TypeCodec.varchar());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.VARINT, TypeCodec.varint());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.TIMEUUID, TypeCodec.timeUUID());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.SMALLINT, TypeCodec.smallInt());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.TINYINT, TypeCodec.tinyInt());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.DATE, TypeCodec.date());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.TIME, TypeCodec.time());
+ BUILT_IN_CODECS_MAP.put(DataType.Name.DURATION, TypeCodec.duration());
+ }
+
+ // roughly sorted by popularity
+ private static final TypeCodec>[] BUILT_IN_CODECS = new TypeCodec>[]{
+ TypeCodec.varchar(), // must be declared before AsciiCodec so it gets chosen when CQL type not available
+ TypeCodec.uuid(), // must be declared before TimeUUIDCodec so it gets chosen when CQL type not available
+ TypeCodec.timeUUID(),
+ TypeCodec.timestamp(),
TypeCodec.cint(),
TypeCodec.bigint(),
- TypeCodec.counter(),
+ TypeCodec.blob(),
TypeCodec.cdouble(),
TypeCodec.cfloat(),
- TypeCodec.varint(),
TypeCodec.decimal(),
- TypeCodec.varchar(), // must be declared before AsciiCodec so it gets chosen when CQL type not available
- TypeCodec.ascii(),
- TypeCodec.timestamp(),
+ TypeCodec.varint(),
+ TypeCodec.inet(),
+ TypeCodec.cboolean(),
+ TypeCodec.smallInt(),
+ TypeCodec.tinyInt(),
TypeCodec.date(),
TypeCodec.time(),
- TypeCodec.uuid(), // must be declared before TimeUUIDCodec so it gets chosen when CQL type not available
- TypeCodec.timeUUID(),
- TypeCodec.inet(),
- TypeCodec.duration()
- );
+ TypeCodec.duration(),
+ TypeCodec.counter(),
+ TypeCodec.ascii()
+ };
/**
* The default {@code CodecRegistry} instance.
@@ -181,7 +204,7 @@ private static final class CacheKey {
private final TypeToken> javaType;
- public CacheKey(DataType cqlType, TypeToken> javaType) {
+ CacheKey(DataType cqlType, TypeToken> javaType) {
this.javaType = javaType;
this.cqlType = cqlType;
}
@@ -209,7 +232,18 @@ public int hashCode() {
private class TypeCodecCacheLoader extends CacheLoader> {
@Override
public TypeCodec> load(CacheKey cacheKey) {
- return findCodec(cacheKey.cqlType, cacheKey.javaType);
+ checkNotNull(cacheKey.cqlType, "Parameter cqlType cannot be null");
+ if (logger.isTraceEnabled())
+ logger.trace("Loading codec into cache: [{} <-> {}]",
+ CodecRegistry.toString(cacheKey.cqlType),
+ CodecRegistry.toString(cacheKey.javaType));
+ for (TypeCodec> codec : codecs) {
+ if (codec.accepts(cacheKey.cqlType) && (cacheKey.javaType == null || codec.accepts(cacheKey.javaType))) {
+ logger.trace("Already existing codec found: {}", codec);
+ return codec;
+ }
+ }
+ return createCodec(cacheKey.cqlType, cacheKey.javaType);
}
}
@@ -218,7 +252,7 @@ public TypeCodec> load(CacheKey cacheKey) {
* Weights are computed mainly according to the CQL type:
*
* - Manually-registered codecs always weigh 0;
- *
- Codecs for primtive types weigh 0;
+ *
- Codecs for primitive types weigh 0;
*
- Codecs for collections weigh the total weight of their inner types + the weight of their level of deepness;
*
- Codecs for UDTs and tuples weigh the total weight of their inner types + the weight of their level of deepness, but cannot weigh less than 1;
*
- Codecs for custom (non-CQL) types weigh 1.
@@ -278,9 +312,7 @@ public void onRemoval(RemovalNotification
> notification)
}
/**
- * The list of registered codecs.
- * This list is initialized with the built-in codecs;
- * User-defined codecs are appended to the list.
+ * The list of user-registered codecs.
*/
private final CopyOnWriteArrayList> codecs;
@@ -294,13 +326,13 @@ public void onRemoval(RemovalNotification> notification)
* Creates a new instance initialized with built-in codecs for all the base CQL types.
*/
public CodecRegistry() {
- this.codecs = new CopyOnWriteArrayList>(PRIMITIVE_CODECS);
+ this.codecs = new CopyOnWriteArrayList>();
this.cache = defaultCacheBuilder().build(new TypeCodecCacheLoader());
}
private CacheBuilder> defaultCacheBuilder() {
CacheBuilder> builder = CacheBuilder.newBuilder()
- // 19 primitive codecs + collections thereof = 19*3 + 19*19 = 418 codecs,
+ // lists, sets and maps of 20 primitive types = 20 + 20 + 20*20 = 440 codecs,
// so let's start with roughly 1/4 of that
.initialCapacity(100)
.maximumWeight(1000)
@@ -323,20 +355,24 @@ private CacheBuilder> defaultCacheBuilder() {
* @return this CodecRegistry (for method chaining).
*/
public CodecRegistry register(TypeCodec> newCodec) {
+ for (TypeCodec> oldCodec : BUILT_IN_CODECS) {
+ if (oldCodec.accepts(newCodec.getCqlType()) && oldCodec.accepts(newCodec.getJavaType())) {
+ logger.warn("Ignoring codec {} because it collides with previously registered codec {}", newCodec, oldCodec);
+ return this;
+ }
+ }
for (TypeCodec> oldCodec : codecs) {
if (oldCodec.accepts(newCodec.getCqlType()) && oldCodec.accepts(newCodec.getJavaType())) {
logger.warn("Ignoring codec {} because it collides with previously registered codec {}", newCodec, oldCodec);
return this;
}
}
-
CacheKey key = new CacheKey(newCodec.getCqlType(), newCodec.getJavaType());
TypeCodec> existing = cache.getIfPresent(key);
if (existing != null) {
logger.warn("Ignoring codec {} because it collides with previously generated codec {}", newCodec, existing);
return this;
}
-
this.codecs.add(newCodec);
return this;
}
@@ -474,13 +510,16 @@ public TypeCodec codecFor(DataType cqlType, T value) {
@SuppressWarnings("unchecked")
private TypeCodec lookupCodec(DataType cqlType, TypeToken javaType) {
checkNotNull(cqlType, "Parameter cqlType cannot be null");
+ TypeCodec> codec = BUILT_IN_CODECS_MAP.get(cqlType.getName());
+ if (codec != null && (javaType == null || codec.accepts(javaType))) {
+ logger.trace("Returning built-in codec {}", codec);
+ return (TypeCodec) codec;
+ }
if (logger.isTraceEnabled())
logger.trace("Querying cache for codec [{} <-> {}]", toString(cqlType), toString(javaType));
- CacheKey cacheKey = new CacheKey(cqlType, javaType);
try {
- TypeCodec> codec = cache.get(cacheKey);
- logger.trace("Returning cached codec {}", codec);
- return (TypeCodec) codec;
+ CacheKey cacheKey = new CacheKey(cqlType, javaType);
+ codec = cache.get(cacheKey);
} catch (UncheckedExecutionException e) {
if (e.getCause() instanceof CodecNotFoundException) {
throw (CodecNotFoundException) e.getCause();
@@ -491,6 +530,8 @@ private TypeCodec lookupCodec(DataType cqlType, TypeToken javaType) {
} catch (ExecutionException e) {
throw new CodecNotFoundException(e.getCause(), cqlType, javaType);
}
+ logger.trace("Returning cached codec {}", codec);
+ return (TypeCodec) codec;
}
@SuppressWarnings("unchecked")
@@ -498,9 +539,19 @@ private TypeCodec findCodec(DataType cqlType, TypeToken javaType) {
checkNotNull(cqlType, "Parameter cqlType cannot be null");
if (logger.isTraceEnabled())
logger.trace("Looking for codec [{} <-> {}]", toString(cqlType), toString(javaType));
+
+ // Look at the built-in codecs first
+ for (TypeCodec> codec : BUILT_IN_CODECS) {
+ if (codec.accepts(cqlType) && (javaType == null || codec.accepts(javaType))) {
+ logger.trace("Built-in codec found: {}", codec);
+ return (TypeCodec) codec;
+ }
+ }
+
+ // Look at the user-registered codecs next
for (TypeCodec> codec : codecs) {
if (codec.accepts(cqlType) && (javaType == null || codec.accepts(javaType))) {
- logger.trace("Codec found: {}", codec);
+ logger.trace("Already registered codec found: {}", codec);
return (TypeCodec) codec;
}
}
@@ -512,9 +563,19 @@ private TypeCodec findCodec(DataType cqlType, T value) {
checkNotNull(value, "Parameter value cannot be null");
if (logger.isTraceEnabled())
logger.trace("Looking for codec [{} <-> {}]", toString(cqlType), value.getClass());
+
+ // Look at the built-in codecs first
+ for (TypeCodec> codec : BUILT_IN_CODECS) {
+ if ((cqlType == null || codec.accepts(cqlType)) && codec.accepts(value)) {
+ logger.trace("Built-in codec found: {}", codec);
+ return (TypeCodec) codec;
+ }
+ }
+
+ // Look at the user-registered codecs next
for (TypeCodec> codec : codecs) {
if ((cqlType == null || codec.accepts(cqlType)) && codec.accepts(value)) {
- logger.trace("Codec found: {}", codec);
+ logger.trace("Already registered codec found: {}", codec);
return (TypeCodec) codec;
}
}
@@ -598,7 +659,7 @@ private TypeCodec maybeCreateCodec(DataType cqlType, TypeToken javaTyp
return null;
}
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({"unchecked", "rawtypes"})
private TypeCodec maybeCreateCodec(DataType cqlType, T value) {
checkNotNull(value);
diff --git a/driver-core/src/main/java/com/datastax/driver/core/TypeCodec.java b/driver-core/src/main/java/com/datastax/driver/core/TypeCodec.java
index 01a8f82f1fb..4af6073848b 100644
--- a/driver-core/src/main/java/com/datastax/driver/core/TypeCodec.java
+++ b/driver-core/src/main/java/com/datastax/driver/core/TypeCodec.java
@@ -579,7 +579,7 @@ public DataType getCqlType() {
* the given {@code javaType}, and {@code false} otherwise.
* @throws NullPointerException if {@code javaType} is {@code null}.
*/
- public boolean accepts(TypeToken javaType) {
+ public boolean accepts(TypeToken> javaType) {
checkNotNull(javaType, "Parameter javaType cannot be null");
return this.javaType.equals(javaType.wrap());
}
diff --git a/driver-core/src/test/java/com/datastax/driver/core/CodecRegistryTest.java b/driver-core/src/test/java/com/datastax/driver/core/CodecRegistryTest.java
index 08a7ed388c9..3cc6b63a854 100644
--- a/driver-core/src/test/java/com/datastax/driver/core/CodecRegistryTest.java
+++ b/driver-core/src/test/java/com/datastax/driver/core/CodecRegistryTest.java
@@ -62,7 +62,8 @@ public static Object[][] cql() {
{DataType.time(), TypeCodec.time()},
{DataType.uuid(), TypeCodec.uuid()},
{DataType.timeuuid(), TypeCodec.timeUUID()},
- {DataType.inet(), TypeCodec.inet()}
+ {DataType.inet(), TypeCodec.inet()},
+ {DataType.duration(), TypeCodec.duration()}
};
}
@@ -87,7 +88,8 @@ public static Object[][] cqlAndJava() {
{DataType.time(), Long.class, TypeCodec.time()},
{DataType.uuid(), UUID.class, TypeCodec.uuid()},
{DataType.timeuuid(), UUID.class, TypeCodec.timeUUID()},
- {DataType.inet(), InetAddress.class, TypeCodec.inet()}
+ {DataType.inet(), InetAddress.class, TypeCodec.inet()},
+ {DataType.duration(), Duration.class, TypeCodec.duration()}
};
}
@@ -108,7 +110,8 @@ public static Object[][] value() {
{new Date(42), TypeCodec.timestamp()},
{LocalDate.fromDaysSinceEpoch(42), TypeCodec.date()},
{UUID.randomUUID(), TypeCodec.uuid()},
- {mock(InetAddress.class), TypeCodec.inet()}
+ {mock(InetAddress.class), TypeCodec.inet()},
+ {Duration.from("1mo2d3h"), TypeCodec.duration()}
};
}
@@ -133,7 +136,8 @@ public static Object[][] cqlAndValue() {
{DataType.time(), 42L, TypeCodec.time()},
{DataType.uuid(), UUID.randomUUID(), TypeCodec.uuid()},
{DataType.timeuuid(), UUID.randomUUID(), TypeCodec.timeUUID()},
- {DataType.inet(), mock(InetAddress.class), TypeCodec.inet()}
+ {DataType.inet(), mock(InetAddress.class), TypeCodec.inet()},
+ {DataType.duration(), Duration.from("1mo2d3h"), TypeCodec.duration()}
};
}
@@ -195,7 +199,7 @@ public void should_find_codec_by_cql_type_and_value(DataType cqlType, Object val
public void should_find_newly_registered_codec_by_cql_type() {
// given
CodecRegistry registry = new CodecRegistry();
- TypeCodec expected = mockCodec(list(text()), listOf(String.class));
+ TypeCodec> expected = mockCodec(list(text()), listOf(String.class));
registry.register(expected);
// when
TypeCodec> actual = registry.codecFor(list(text()));
@@ -209,7 +213,7 @@ public void should_find_newly_registered_codec_by_cql_type() {
public void should_find_default_codec_if_cql_type_already_registered() {
// given
CodecRegistry registry = new CodecRegistry();
- TypeCodec newCodec = mockCodec(text(), of(StringBuilder.class));
+ TypeCodec> newCodec = mockCodec(text(), of(StringBuilder.class));
registry.register(newCodec);
// when
TypeCodec> actual = registry.codecFor(text());
@@ -226,7 +230,7 @@ public void should_find_default_codec_if_cql_type_already_registered() {
public void should_find_newly_registered_codec_by_cql_type_and_java_type() {
// given
CodecRegistry registry = new CodecRegistry();
- TypeCodec expected = mockCodec(list(text()), listOf(String.class));
+ TypeCodec> expected = mockCodec(list(text()), listOf(String.class));
registry.register(expected);
// when
TypeCodec> actual = registry.codecFor(list(text()), listOf(String.class));
@@ -443,7 +447,7 @@ public void should_ignore_codec_colliding_with_already_registered_codec() {
CodecRegistry registry = new CodecRegistry();
- TypeCodec newCodec = mockCodec(cint(), of(Integer.class));
+ TypeCodec> newCodec = mockCodec(cint(), of(Integer.class));
registry.register(newCodec);
@@ -464,7 +468,7 @@ public void should_ignore_codec_colliding_with_already_generated_codec() {
// Force generation of a list token from the default token
registry.codecFor(list(cint()), listOf(Integer.class));
- TypeCodec newCodec = mockCodec(list(cint()), listOf(Integer.class));
+ TypeCodec> newCodec = mockCodec(list(cint()), listOf(Integer.class));
registry.register(newCodec);
@@ -490,8 +494,9 @@ private void stopCapturingLogs(MemoryAppender logs) {
registryLogger.removeAppender(logs);
}
- private TypeCodec> mockCodec(DataType cqlType, TypeToken> javaType) {
- TypeCodec newCodec = mock(TypeCodec.class);
+ private TypeCodec mockCodec(DataType cqlType, TypeToken javaType) {
+ @SuppressWarnings("unchecked")
+ TypeCodec newCodec = mock(TypeCodec.class);
when(newCodec.getCqlType()).thenReturn(cqlType);
when(newCodec.getJavaType()).thenReturn(javaType);
when(newCodec.accepts(cqlType)).thenReturn(true);