Apply configured CodecRegistry to StatementBuilder.

Closes #1114
This commit is contained in:
Mark Paluch
2024-09-09 09:43:43 +02:00
parent bb2b7a4ddd
commit 66a0bf9dee
4 changed files with 162 additions and 18 deletions

View File

@@ -238,7 +238,9 @@ public class StatementFactory {
cassandraConverter.write(id, where, entity);
return StatementBuilder.of(QueryBuilder.selectFrom(getKeyspace(entity, tableName), tableName).all().limit(1))
return StatementBuilder
.of(QueryBuilder.selectFrom(getKeyspace(entity, tableName), tableName).all().limit(1),
cassandraConverter.getCodecRegistry())
.bind((statement, factory) -> statement.where(toRelations(where, factory)));
}
@@ -325,7 +327,8 @@ public class StatementFactory {
cassandraConverter.write(objectToInsert, object, entity);
StatementBuilder<RegularInsert> builder = StatementBuilder
.of(QueryBuilder.insertInto(getKeyspace(entity, tableName), tableName).valuesByIds(Collections.emptyMap()))
.of(QueryBuilder.insertInto(getKeyspace(entity, tableName), tableName).valuesByIds(Collections.emptyMap()),
cassandraConverter.getCodecRegistry())
.bind((statement, factory) -> {
Map<CqlIdentifier, Term> values = createTerms(insertNulls, object, factory);
@@ -457,7 +460,9 @@ public class StatementFactory {
where.forEach((cqlIdentifier, o) -> object.remove(cqlIdentifier));
StatementBuilder<com.datastax.oss.driver.api.querybuilder.update.Update> builder = StatementBuilder
.of(QueryBuilder.update(getKeyspace(entity, tableName), tableName).set().where()).bind((statement, factory) -> {
.of(QueryBuilder.update(getKeyspace(entity, tableName), tableName).set().where(),
cassandraConverter.getCodecRegistry())
.bind((statement, factory) -> {
CqlStatementOptionsAccessor<UpdateStart> accessor = factory.ifBoundOrInline(
bindings -> CqlStatementOptionsAccessor.ofUpdate(bindings, (UpdateStart) statement),
@@ -492,7 +497,9 @@ public class StatementFactory {
cassandraConverter.write(id, where, entity);
return StatementBuilder.of(QueryBuilder.deleteFrom(getKeyspace(entity, tableName), tableName).where())
return StatementBuilder
.of(QueryBuilder.deleteFrom(getKeyspace(entity, tableName), tableName).where(),
cassandraConverter.getCodecRegistry())
.bind((statement, factory) -> statement.where(toRelations(where, factory)));
}
@@ -565,7 +572,8 @@ public class StatementFactory {
.getRequiredPersistentEntity(ProxyUtils.getUserClass(entity.getClass()));
StatementBuilder<Delete> builder = StatementBuilder
.of(QueryBuilder.deleteFrom(getKeyspace(persistentEntity, tableName), tableName).where())
.of(QueryBuilder.deleteFrom(getKeyspace(persistentEntity, tableName), tableName).where(),
cassandraConverter.getCodecRegistry())
.bind((statement, factory) -> {
Delete statementToUse;
@@ -697,7 +705,7 @@ public class StatementFactory {
select = QueryBuilder.selectFrom(getKeyspace(entity, from), from).selectors(mappedSelectors);
}
StatementBuilder<Select> builder = StatementBuilder.of(select);
StatementBuilder<Select> builder = StatementBuilder.of(select, cassandraConverter.getCodecRegistry());
builder.bind((statement, factory) -> {
return statement.where(getRelations(filter, factory));
@@ -758,7 +766,8 @@ public class StatementFactory {
UpdateStart updateStart = QueryBuilder.update(getKeyspace(entity, table), table);
return StatementBuilder.of((com.datastax.oss.driver.api.querybuilder.update.Update) updateStart)
return StatementBuilder
.of((com.datastax.oss.driver.api.querybuilder.update.Update) updateStart, cassandraConverter.getCodecRegistry())
.bind((statement, factory) -> {
com.datastax.oss.driver.api.querybuilder.update.Update statementToUse;
@@ -918,7 +927,7 @@ public class StatementFactory {
select = select.column(columnName);
}
return StatementBuilder.of(select.where()).bind((statement, factory) -> {
return StatementBuilder.of(select.where(), cassandraConverter.getCodecRegistry()).bind((statement, factory) -> {
WriteOptions options = optionsOptional.orElse(null);
Delete statementToUse;

View File

@@ -43,8 +43,8 @@ import com.datastax.oss.driver.internal.querybuilder.CqlHelper;
* Functional builder for Cassandra {@link BuildableQuery statements}. Statements are built by applying
* {@link UnaryOperator builder functions} that get applied when {@link #build() building} the actual
* {@link SimpleStatement statement}. The {@code StatementBuilder} provides a mutable container for statement creation
* allowing a functional declaration of actions that are necessary to build a statement. This class helps building CQL
* statements as a {@link BuildableQuery} classes are typically immutable and require return value tracking across
* allowing a functional declaration of actions that are necessary to build a statement. This class helps with building
* CQL statements as a {@link BuildableQuery} classes are typically immutable and require return value tracking across
* methods that want to apply modifications to a statement.
* <p>
* Building a statement consists of three phases:
@@ -61,7 +61,11 @@ import com.datastax.oss.driver.internal.querybuilder.CqlHelper;
* The builder can be used for structural evolution and value evolution of statements. Values are bound through
* {@link BindFunction binding functions} that accept the statement and a {@link TermFactory}. Values can be bound
* inline or through bind markers when {@link #build(ParameterHandling, CodecRegistry) building} the statement. All
* functions are applied in the order of their declaration.
* functions remain in the order of their declaration.
* <p>
* {@link ParameterHandling#INLINE Inline} rendering of parameters requires a {@link CodecRegistry}. A StatementBuilder
* can be {@link StatementBuilder#of(BuildableQuery, CodecRegistry) created} by providing a custom CodecRegistry.
* Otherwise, the registry falls back to {@link CodecRegistry#DEFAULT}.
* <p>
* All methods returning {@link StatementBuilder} point to the same instance. This class is intended for internal use.
*
@@ -72,6 +76,7 @@ import com.datastax.oss.driver.internal.querybuilder.CqlHelper;
public class StatementBuilder<S extends BuildableQuery> {
private final S statement;
private final CodecRegistry registry;
private final List<BuilderRunnable<S>> queryActions = new ArrayList<>();
private final List<Consumer<SimpleStatementBuilder>> onBuild = new ArrayList<>();
@@ -79,7 +84,8 @@ public class StatementBuilder<S extends BuildableQuery> {
/**
* Factory method used to create a new {@link StatementBuilder} with the given {@link BuildableQuery query stub}. The
* stub is used as base for the built query so each query inherits properties of this stub.
* stub is used as base for the built query so each query inherits properties of this stub. This factory method
* initializes StatementBuilder with the default {@link CodecRegistry#DEFAULT CodecRegistry}.
*
* @param <S> query type.
* @param stub the {@link BuildableQuery query stub} to use.
@@ -88,10 +94,27 @@ public class StatementBuilder<S extends BuildableQuery> {
* @see com.datastax.oss.driver.api.querybuilder.BuildableQuery
*/
public static <S extends BuildableQuery> StatementBuilder<S> of(S stub) {
return of(stub, CodecRegistry.DEFAULT);
}
/**
* Factory method used to create a new {@link StatementBuilder} with the given {@link BuildableQuery query stub}. The
* stub is used as base for the built query so each query inherits properties of this stub.
*
* @param <S> query type.
* @param stub the {@link BuildableQuery query stub} to use.
* @param registry the default {@link CodecRegistry} to use for inline parameter rendering.
* @return a {@link StatementBuilder} for the given {@link BuildableQuery query stub}.
* @throws IllegalArgumentException if the {@link BuildableQuery query stub} is {@literal null}.
* @see com.datastax.oss.driver.api.querybuilder.BuildableQuery
* @since 4.4
*/
public static <S extends BuildableQuery> StatementBuilder<S> of(S stub, CodecRegistry registry) {
Assert.notNull(stub, "Query stub must not be null");
Assert.notNull(registry, "CodecRegistry stub must not be null");
return new StatementBuilder<>(stub);
return new StatementBuilder<>(stub, registry);
}
/**
@@ -101,8 +124,9 @@ public class StatementBuilder<S extends BuildableQuery> {
* {@link com.datastax.oss.driver.api.core.cql.Statement}.
* @see com.datastax.oss.driver.api.querybuilder.BuildableQuery
*/
private StatementBuilder(S statement) {
private StatementBuilder(S statement, CodecRegistry registry) {
this.statement = statement;
this.registry = registry;
}
/**
@@ -177,7 +201,7 @@ public class StatementBuilder<S extends BuildableQuery> {
* @return the built {@link SimpleStatement}.
*/
public SimpleStatement build() {
return build(ParameterHandling.BY_INDEX, CodecRegistry.DEFAULT);
return build(ParameterHandling.BY_INDEX, this.registry);
}
/**
@@ -188,7 +212,7 @@ public class StatementBuilder<S extends BuildableQuery> {
* @return the built {@link SimpleStatement}.
*/
public SimpleStatement build(ParameterHandling parameterHandling) {
return build(parameterHandling, CodecRegistry.DEFAULT);
return build(parameterHandling, this.registry);
}
/**

View File

@@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.cassandra.core.query.Criteria.*;
import static org.springframework.data.domain.Sort.Direction.*;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
@@ -29,7 +30,6 @@ import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.core.convert.CassandraConverter;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.convert.UpdateMapper;
import org.springframework.data.cassandra.core.cql.QueryOptions;
@@ -47,10 +47,16 @@ import org.springframework.data.domain.Sort;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.DefaultConsistencyLevel;
import com.datastax.oss.driver.api.core.ProtocolVersion;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.driver.api.core.type.DataType;
import com.datastax.oss.driver.api.core.type.DataTypes;
import com.datastax.oss.driver.api.core.type.codec.TypeCodec;
import com.datastax.oss.driver.api.core.type.reflect.GenericType;
import com.datastax.oss.driver.api.querybuilder.delete.Delete;
import com.datastax.oss.driver.api.querybuilder.insert.RegularInsert;
import com.datastax.oss.driver.api.querybuilder.select.Select;
import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry;
/**
* Unit tests for {@link StatementFactory}.
@@ -60,7 +66,7 @@ import com.datastax.oss.driver.api.querybuilder.select.Select;
*/
class StatementFactoryUnitTests {
private CassandraConverter converter = new MappingCassandraConverter();
private MappingCassandraConverter converter = new MappingCassandraConverter();
private UpdateMapper updateMapper = new UpdateMapper(converter);
@@ -850,6 +856,53 @@ class StatementFactoryUnitTests {
.isEqualTo("SELECT count(1) FROM group WHERE foo='bar'");
}
@Test // GH-1114
void shouldConsiderCodecRegistry() {
DefaultCodecRegistry cr = new DefaultCodecRegistry("foo");
cr.register(new TypeCodec<MyString>() {
@Override
public GenericType<MyString> getJavaType() {
return GenericType.of(MyString.class);
}
@Override
public DataType getCqlType() {
return DataTypes.TEXT;
}
@Override
public ByteBuffer encode(MyString value, ProtocolVersion protocolVersion) {
return null;
}
@Override
public MyString decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
return null;
}
@Override
public String format(MyString value) {
return "'" + value.value() + "'";
}
@Override
public MyString parse(String value) {
return new MyString(value);
}
});
converter.setCodecRegistry(cr);
Query query = Query.query(where("foo").is(new MyString("bar")));
StatementBuilder<Select> count = statementFactory.count(query,
converter.getMappingContext().getRequiredPersistentEntity(Group.class));
assertThat(count.build(ParameterHandling.INLINE).getQuery())
.isEqualTo("SELECT count(1) FROM group WHERE foo='bar'");
}
@SuppressWarnings("unused")
static class Person {
@@ -865,4 +918,8 @@ class StatementFactoryUnitTests {
@Column("first_name") private String firstName;
}
record MyString(String value) {
}
}

View File

@@ -17,15 +17,22 @@ package org.springframework.data.cassandra.core.cql.util;
import static org.assertj.core.api.Assertions.*;
import java.nio.ByteBuffer;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.ProtocolVersion;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder;
import com.datastax.oss.driver.api.core.type.DataType;
import com.datastax.oss.driver.api.core.type.DataTypes;
import com.datastax.oss.driver.api.core.type.codec.TypeCodec;
import com.datastax.oss.driver.api.core.type.reflect.GenericType;
import com.datastax.oss.driver.api.querybuilder.QueryBuilder;
import com.datastax.oss.driver.api.querybuilder.relation.Relation;
import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry;
/**
* Unit tests for {@link StatementBuilder}.
@@ -151,4 +158,51 @@ class StatementBuilderUnitTests {
assertThat(statement.getQuery()).isEqualTo("SELECT * FROM person");
assertThat(statement.getExecutionProfileName()).isEqualTo("foo");
}
@Test // GH-1114
void shouldConsiderCodecRegistry() {
DefaultCodecRegistry cr = new DefaultCodecRegistry("foo");
cr.register(new TypeCodec<MyString>() {
@Override
public GenericType<MyString> getJavaType() {
return GenericType.of(MyString.class);
}
@Override
public DataType getCqlType() {
return DataTypes.TEXT;
}
@Override
public ByteBuffer encode(MyString value, ProtocolVersion protocolVersion) {
return null;
}
@Override
public MyString decode(ByteBuffer bytes, ProtocolVersion protocolVersion) {
return null;
}
@Override
public String format(MyString value) {
return "'" + value.value() + "'";
}
@Override
public MyString parse(String value) {
return new MyString(value);
}
});
SimpleStatement statement = StatementBuilder.of(QueryBuilder.selectFrom("person").all(), cr)
.bind((select, factory) -> select.where(Relation.column("foo").isEqualTo(factory.create(new MyString("bar")))))
.build(StatementBuilder.ParameterHandling.INLINE);
assertThat(statement.getQuery()).isEqualTo("SELECT * FROM person WHERE foo='bar'");
}
record MyString(String value) {
}
}