spring-projects / spring-data-cassandra

Provides support to increase developer productivity in Java when using Apache Cassandra. Uses familiar Spring concepts such as a template classes for core API usage and lightweight repository style data access.
https://spring.io/projects/spring-data-cassandra/
Apache License 2.0
373 stars 307 forks source link

Codec not found for requested operation using a `Set` of mapped User Defined Type #1473

Closed soheilrahsaz closed 5 months ago

soheilrahsaz commented 5 months ago

Hi I have test application working with Cassandra repositories. When I try to write a custom query and a partial update on a column which it's type is a collection of UDT, I get this error:

org.springframework.data.cassandra.CassandraUncategorizedException: Query; CQL [UPDATE vet SET specialities = ? WHERE id = ?]; Codec not found for requested operation: [UDT(spring_cassandra.speciality) <-> com.example.cassandratest.entity.Speciality] at org.springframework.data.cassandra.core.cql.CassandraExceptionTranslator.translate(CassandraExceptionTranslator.java:162) ~[spring-data-cassandra-4.2.0.jar:4.2.0] at org.springframework.data.cassandra.core.cql.CassandraAccessor.translate(CassandraAccessor.java:421) ~[spring-data-cassandra-4.2.0.jar:4.2.0] at org.springframework.data.cassandra.core.cql.CqlTemplate.translateException(CqlTemplate.java:551) ~[spring-data-cassandra-4.2.0.jar:4.2.0] at org.springframework.data.cassandra.core.cql.CqlTemplate.query(CqlTemplate.java:409) ~[spring-data-cassandra-4.2.0.jar:4.2.0] at org.springframework.data.cassandra.core.cql.CqlTemplate.query(CqlTemplate.java:424) ~[spring-data-cassandra-4.2.0.jar:4.2.0] at org.springframework.data.cassandra.core.CassandraTemplate.doQuery(CassandraTemplate.java:852) ~[spring-data-cassandra-4.2.0.jar:4.2.0] at org.springframework.data.cassandra.core.CassandraTemplate.select(CassandraTemplate.java:371) ~[spring-data-cassandra-4.2.0.jar:4.2.0] at org.springframework.data.cassandra.repository.query.CassandraQueryExecution$SingleEntityExecution.execute(CassandraQueryExecution.java:182) ~[spring-data-cassandra-4.2.0.jar:4.2.0] at org.springframework.data.cassandra.repository.query.CassandraQueryExecution$ResultProcessingExecution.execute(CassandraQueryExecution.java:270) ~[spring-data-cassandra-4.2.0.jar:4.2.0] at org.springframework.data.cassandra.repository.query.AbstractCassandraQuery.execute(AbstractCassandraQuery.java:86) ~[spring-data-cassandra-4.2.0.jar:4.2.0] at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) ~[spring-data-commons-3.2.0.jar:3.2.0] at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) ~[spring-data-commons-3.2.0.jar:3.2.0] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:164) ~[spring-data-commons-3.2.0.jar:3.2.0] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143) ~[spring-data-commons-3.2.0.jar:3.2.0] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.1.jar:6.1.1] at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:70) ~[spring-data-commons-3.2.0.jar:3.2.0] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.1.jar:6.1.1] at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-6.1.1.jar:6.1.1] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.1.jar:6.1.1] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:249) ~[spring-aop-6.1.1.jar:6.1.1] at jdk.proxy2/jdk.proxy2.$Proxy79.updateSpecialities(Unknown Source) ~[na:na] at com.example.cassandratest.CassandraTestApplication.run(CassandraTestApplication.java:25) ~[classes/:na] at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:786) ~[spring-boot-3.2.0.jar:3.2.0] ... 14 common frames omitted Caused by: com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException: Codec not found for requested operation: [UDT(spring_cassandra.speciality) <-> com.example.cassandratest.entity.Speciality] at com.datastax.oss.driver.internal.core.type.codec.registry.CachingCodecRegistry.createCodec(CachingCodecRegistry.java:656) ~[java-driver-core-4.17.0.jar:na] at com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry$1.load(DefaultCodecRegistry.java:95) ~[java-driver-core-4.17.0.jar:na] at com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry$1.load(DefaultCodecRegistry.java:92) ~[java-driver-core-4.17.0.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3527) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2276) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2154) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache$Segment.get(LocalCache.java:2044) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache.get(LocalCache.java:3951) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache.getOrLoad(LocalCache.java:3973) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:4957) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache$LocalLoadingCache.getUnchecked(LocalCache.java:4963) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry.getCachedCodec(DefaultCodecRegistry.java:117) ~[java-driver-core-4.17.0.jar:na] at com.datastax.oss.driver.internal.core.type.codec.registry.CachingCodecRegistry.codecFor(CachingCodecRegistry.java:190) ~[java-driver-core-4.17.0.jar:na] at com.datastax.oss.driver.internal.core.type.codec.registry.CachingCodecRegistry.getElementCodecForCqlAndJavaType(CachingCodecRegistry.java:587) ~[java-driver-core-4.17.0.jar:na] at com.datastax.oss.driver.internal.core.type.codec.registry.CachingCodecRegistry.createCodec(CachingCodecRegistry.java:619) ~[java-driver-core-4.17.0.jar:na] at com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry$1.load(DefaultCodecRegistry.java:95) ~[java-driver-core-4.17.0.jar:na] at com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry$1.load(DefaultCodecRegistry.java:92) ~[java-driver-core-4.17.0.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3527) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2276) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2154) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache$Segment.get(LocalCache.java:2044) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache.get(LocalCache.java:3951) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache.getOrLoad(LocalCache.java:3973) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:4957) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.shaded.guava.common.cache.LocalCache$LocalLoadingCache.getUnchecked(LocalCache.java:4963) ~[java-driver-shaded-guava-25.1-jre-graal-sub-1.jar:na] at com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry.getCachedCodec(DefaultCodecRegistry.java:117) ~[java-driver-core-4.17.0.jar:na] at com.datastax.oss.driver.internal.core.type.codec.registry.CachingCodecRegistry.codecFor(CachingCodecRegistry.java:252) ~[java-driver-core-4.17.0.jar:na] at com.datastax.oss.driver.internal.core.data.ValuesHelper.encodePreparedValues(ValuesHelper.java:112) ~[java-driver-core-4.17.0.jar:na] at com.datastax.oss.driver.internal.core.cql.DefaultPreparedStatement.boundStatementBuilder(DefaultPreparedStatement.java:187) ~[java-driver-core-4.17.0.jar:na] at org.springframework.data.cassandra.core.PreparedStatementDelegate.bind(PreparedStatementDelegate.java:59) ~[spring-data-cassandra-4.2.0.jar:4.2.0] at org.springframework.data.cassandra.core.CassandraTemplate$PreparedStatementHandler.bindValues(CassandraTemplate.java:1004) ~[spring-data-cassandra-4.2.0.jar:4.2.0] at org.springframework.data.cassandra.core.cql.CqlTemplate.query(CqlTemplate.java:402) ~[spring-data-cassandra-4.2.0.jar:4.2.0] ... 33 common frames omitted

Here is my entity:

@Table
public class Vet {

    @PrimaryKey
    private UUID id;

    private String firstName;

    private String lastName;

    private Set<Speciality> specialities;

}

@UserDefinedType
public class Speciality {
    private String name;
}

public interface VetRepository extends CassandraRepository<Vet, UUID> {

    @Query("UPDATE vet SET specialities = :specialities WHERE id = :id")
    void updateSpecialities(UUID id, Set<Speciality> specialities);
}

 @Override
    public void run(String... args) throws Exception {
        vetRepository.updateSpecialities(UUID.randomUUID(), Set.of(new Speciality("Ophthalmology")));
    }

But if i dont put Speciality in a collection and update it using the same way, there is no problem.

Tested with spring boot 3.2.0 and spring-data-cassandra 4.2.0

mp911de commented 5 months ago

Care to provide the Table and UDT CQL definitions so we have a complete reproducer?

soheilrahsaz commented 5 months ago

Sure, I used spring.cassandra.schema-action=create_if_not_exists to create the schemas:

CREATE KEYSPACE spring_cassandra WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}
  AND durable_writes = true;

CREATE TYPE spring_cassandra.speciality (
    name text
);

CREATE TABLE spring_cassandra.vet (
    id uuid PRIMARY KEY,
    firstname text,
    lastname text,
    specialities set<frozen<speciality>>
)
mp911de commented 5 months ago

Thanks a lot. We were missing collection introspection of elements if the element type wasn't sufficient. That's fixed now.