Refine table and column name generation.
We revised the table and column name generation by unifying the generation code into CqlIdentifierGenerator. We also properly distinguish between generated names that are generated and those provided by the application (i.e. through annotations). Closes #1263
This commit is contained in:
@@ -37,7 +37,6 @@ import org.springframework.data.cassandra.core.mapping.SimpleUserTypeResolver;
|
||||
import org.springframework.data.cassandra.core.mapping.Table;
|
||||
import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.domain.ManagedTypes;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -101,7 +100,7 @@ public abstract class AbstractCassandraConfiguration extends AbstractSessionConf
|
||||
* Return the {@link MappingContext} instance to map Entities to {@link Object Java Objects}.
|
||||
*
|
||||
* @see org.springframework.data.cassandra.core.mapping.CassandraMappingContext
|
||||
* @deprecated since 4.0, use {@link #cassandraMappingContext(ManagedTypes)} instead.
|
||||
* @deprecated since 4.0, use {@link #cassandraMappingContext(CassandraManagedTypes)} instead.
|
||||
*/
|
||||
@Deprecated(since = "4.0", forRemoval = true)
|
||||
public CassandraMappingContext cassandraMapping() throws ClassNotFoundException {
|
||||
|
||||
@@ -15,15 +15,17 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.core.mapping;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.expression.BeanFactoryAccessor;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.data.cassandra.util.SpelUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.AssociationHandler;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
@@ -32,7 +34,6 @@ import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
|
||||
@@ -49,14 +50,14 @@ public class BasicCassandraPersistentEntity<T> extends BasicPersistentEntity<T,
|
||||
|
||||
private static final CassandraPersistentEntityMetadataVerifier DEFAULT_VERIFIER = new CompositeCassandraPersistentEntityMetadataVerifier();
|
||||
|
||||
private final CqlIdentifierGenerator namingAccessor = new CqlIdentifierGenerator();
|
||||
|
||||
private Boolean forceQuote;
|
||||
|
||||
private CassandraPersistentEntityMetadataVerifier verifier = DEFAULT_VERIFIER;
|
||||
|
||||
private CqlIdentifier tableName;
|
||||
|
||||
private NamingStrategy namingStrategy = NamingStrategy.INSTANCE;
|
||||
|
||||
private @Nullable StandardEvaluationContext spelContext;
|
||||
|
||||
/**
|
||||
@@ -101,27 +102,20 @@ public class BasicCassandraPersistentEntity<T> extends BasicPersistentEntity<T,
|
||||
}
|
||||
|
||||
protected CqlIdentifier determineTableName() {
|
||||
|
||||
Table annotation = findAnnotation(Table.class);
|
||||
|
||||
if (annotation != null) {
|
||||
return determineName(annotation.value(), annotation.forceQuote());
|
||||
}
|
||||
|
||||
return IdentifierFactory.create(getNamingStrategy().getTableName(this), false);
|
||||
return determineTableName(NamingStrategy::getTableName, findAnnotation(Table.class));
|
||||
}
|
||||
|
||||
CqlIdentifier determineName(String value, boolean forceQuote) {
|
||||
CqlIdentifier determineTableName(
|
||||
BiFunction<NamingStrategy, CassandraPersistentEntity<?>, String> defaultNameGenerator,
|
||||
@Nullable Annotation annotation) {
|
||||
|
||||
if (!StringUtils.hasText(value)) {
|
||||
return IdentifierFactory.create(getNamingStrategy().getTableName(this), forceQuote);
|
||||
if (annotation != null) {
|
||||
return this.namingAccessor.generate((String) AnnotationUtils.getValue(annotation),
|
||||
(Boolean) AnnotationUtils.getValue(annotation, "forceQuote"), defaultNameGenerator, this, this.spelContext);
|
||||
}
|
||||
|
||||
String name = Optional.ofNullable(this.spelContext).map(it -> SpelUtils.evaluate(value, it)).orElse(value);
|
||||
|
||||
Assert.state(name != null, () -> String.format("Cannot determine default name for %s", this));
|
||||
|
||||
return IdentifierFactory.create(name, forceQuote);
|
||||
return this.namingAccessor.generate(null, forceQuote != null ? forceQuote : false, defaultNameGenerator, this,
|
||||
this.spelContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -168,7 +162,7 @@ public class BasicCassandraPersistentEntity<T> extends BasicPersistentEntity<T,
|
||||
this.forceQuote = forceQuote;
|
||||
|
||||
if (changed) {
|
||||
setTableName(IdentifierFactory.create(getTableName().asInternal(), forceQuote));
|
||||
setTableName(CqlIdentifierGenerator.createIdentifier(getTableName().asInternal(), forceQuote));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,14 +181,7 @@ public class BasicCassandraPersistentEntity<T> extends BasicPersistentEntity<T,
|
||||
* @since 3.0
|
||||
*/
|
||||
public void setNamingStrategy(NamingStrategy namingStrategy) {
|
||||
|
||||
Assert.notNull(namingStrategy, "NamingStrategy must not be null");
|
||||
|
||||
this.namingStrategy = namingStrategy;
|
||||
}
|
||||
|
||||
NamingStrategy getNamingStrategy() {
|
||||
return namingStrategy;
|
||||
this.namingAccessor.setNamingStrategy(namingStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
@@ -31,7 +30,6 @@ import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.data.cassandra.core.cql.Ordering;
|
||||
import org.springframework.data.cassandra.core.cql.PrimaryKeyType;
|
||||
import org.springframework.data.cassandra.util.SpelUtils;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
|
||||
import org.springframework.data.mapping.model.Property;
|
||||
@@ -43,7 +41,6 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
|
||||
@@ -60,13 +57,13 @@ import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentProperty<CassandraPersistentProperty>
|
||||
implements CassandraPersistentProperty, ApplicationContextAware {
|
||||
|
||||
private final CqlIdentifierGenerator namingAccessor = new CqlIdentifierGenerator();
|
||||
|
||||
// Indicates whether this property has been explicitly instructed to force quoted column names.
|
||||
private Boolean forceQuote;
|
||||
|
||||
private @Nullable CqlIdentifier columnName;
|
||||
|
||||
private NamingStrategy namingStrategy = NamingStrategy.INSTANCE;
|
||||
|
||||
private @Nullable StandardEvaluationContext spelContext;
|
||||
|
||||
/**
|
||||
@@ -168,8 +165,6 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
|
||||
return null;
|
||||
}
|
||||
|
||||
Supplier<String> defaultName = () -> getNamingStrategy().getColumnName(this);
|
||||
|
||||
String overriddenName = null;
|
||||
|
||||
boolean forceQuote = false;
|
||||
@@ -200,7 +195,7 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
|
||||
}
|
||||
}
|
||||
|
||||
return createColumnName(defaultName, overriddenName, forceQuote);
|
||||
return namingAccessor.generate(overriddenName, forceQuote, NamingStrategy::getColumnName, this, this.spelContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -229,21 +224,6 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private CqlIdentifier createColumnName(Supplier<String> defaultName, @Nullable String overriddenName,
|
||||
boolean forceQuote) {
|
||||
|
||||
String name;
|
||||
|
||||
if (StringUtils.hasText(overriddenName)) {
|
||||
name = this.spelContext != null ? SpelUtils.evaluate(overriddenName, this.spelContext) : overriddenName;
|
||||
} else {
|
||||
name = defaultName.get();
|
||||
}
|
||||
|
||||
return name != null ? IdentifierFactory.create(name, forceQuote) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColumnName(CqlIdentifier columnName) {
|
||||
|
||||
@@ -259,14 +239,7 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
|
||||
* @since 3.0
|
||||
*/
|
||||
public void setNamingStrategy(NamingStrategy namingStrategy) {
|
||||
|
||||
Assert.notNull(namingStrategy, "NamingStrategy must not be null");
|
||||
|
||||
this.namingStrategy = namingStrategy;
|
||||
}
|
||||
|
||||
NamingStrategy getNamingStrategy() {
|
||||
return this.namingStrategy;
|
||||
this.namingAccessor.setNamingStrategy(namingStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -277,7 +250,7 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
|
||||
this.forceQuote = forceQuote;
|
||||
|
||||
if (changed) {
|
||||
setColumnName(IdentifierFactory.create(getRequiredColumnName().asInternal(), forceQuote));
|
||||
setColumnName(CqlIdentifierGenerator.createIdentifier(getRequiredColumnName().asInternal(), forceQuote));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ public class CassandraMappingContext
|
||||
|
||||
private Mapping mapping = new Mapping();
|
||||
|
||||
private NamingStrategy namingStrategy = NamingStrategy.INSTANCE;
|
||||
private @Nullable NamingStrategy namingStrategy;
|
||||
|
||||
private @Deprecated @Nullable UserTypeResolver userTypeResolver;
|
||||
|
||||
@@ -128,7 +128,8 @@ public class CassandraMappingContext
|
||||
String entityTableName = entityMapping.getTableName();
|
||||
|
||||
if (StringUtils.hasText(entityTableName)) {
|
||||
entity.setTableName(IdentifierFactory.create(entityTableName, Boolean.valueOf(entityMapping.getForceQuote())));
|
||||
entity.setTableName(
|
||||
CqlIdentifierGenerator.createIdentifier(entityTableName, Boolean.valueOf(entityMapping.getForceQuote())));
|
||||
}
|
||||
|
||||
processMappingOverrides(entity, entityMapping);
|
||||
@@ -160,7 +161,7 @@ public class CassandraMappingContext
|
||||
property.setForceQuote(forceQuote);
|
||||
|
||||
if (StringUtils.hasText(mapping.getColumnName())) {
|
||||
property.setColumnName(IdentifierFactory.create(mapping.getColumnName(), forceQuote));
|
||||
property.setColumnName(CqlIdentifierGenerator.createIdentifier(mapping.getColumnName(), forceQuote));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +192,7 @@ public class CassandraMappingContext
|
||||
|
||||
/**
|
||||
* @deprecated since 3.0. Use custom conversion through
|
||||
* {@link org.springframework.data.cassandra.core.convert.MappingCassandraConverter}.
|
||||
* {@link org.springframework.data.cassandra.core.convert.MappingCassandraConverter}.
|
||||
*/
|
||||
@Deprecated
|
||||
public CustomConversions getCustomConversions() {
|
||||
@@ -246,7 +247,7 @@ public class CassandraMappingContext
|
||||
|
||||
/**
|
||||
* @deprecated since 3.0. Retrieve {@link CodecRegistry} directly from
|
||||
* {@link org.springframework.data.cassandra.core.convert.CassandraConverter}.
|
||||
* {@link org.springframework.data.cassandra.core.convert.CassandraConverter}.
|
||||
*/
|
||||
@Deprecated
|
||||
public CodecRegistry getCodecRegistry() {
|
||||
@@ -295,7 +296,7 @@ public class CassandraMappingContext
|
||||
|
||||
/**
|
||||
* @deprecated since 3.0. Retrieve {@link UserTypeResolver} directly from
|
||||
* {@link org.springframework.data.cassandra.core.convert.CassandraConverter}.
|
||||
* {@link org.springframework.data.cassandra.core.convert.CassandraConverter}.
|
||||
*/
|
||||
@Nullable
|
||||
@Deprecated
|
||||
@@ -340,7 +341,6 @@ public class CassandraMappingContext
|
||||
if (!entity.isUserDefinedType() && !entity.isTupleType() && entity.isAnnotationPresent(Table.class)) {
|
||||
this.tableEntities.add(entity);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return optional;
|
||||
@@ -357,11 +357,12 @@ public class CassandraMappingContext
|
||||
|
||||
BasicCassandraPersistentEntity<T> entity = isUserDefinedType(typeInformation)
|
||||
? new CassandraUserTypePersistentEntity<>(typeInformation, getVerifier())
|
||||
: isTuple(typeInformation)
|
||||
? new BasicCassandraPersistentTupleEntity<>(typeInformation)
|
||||
: new BasicCassandraPersistentEntity<>(typeInformation, getVerifier());
|
||||
: isTuple(typeInformation) ? new BasicCassandraPersistentTupleEntity<>(typeInformation)
|
||||
: new BasicCassandraPersistentEntity<>(typeInformation, getVerifier());
|
||||
|
||||
entity.setNamingStrategy(this.namingStrategy);
|
||||
if (this.namingStrategy != null) {
|
||||
entity.setNamingStrategy(this.namingStrategy);
|
||||
}
|
||||
Optional.ofNullable(this.applicationContext).ifPresent(entity::setApplicationContext);
|
||||
|
||||
return entity;
|
||||
@@ -383,7 +384,10 @@ public class CassandraMappingContext
|
||||
? new BasicCassandraPersistentTupleProperty(property, owner, simpleTypeHolder)
|
||||
: new CachingCassandraPersistentProperty(property, owner, simpleTypeHolder);
|
||||
|
||||
persistentProperty.setNamingStrategy(this.namingStrategy);
|
||||
if (this.namingStrategy != null) {
|
||||
persistentProperty.setNamingStrategy(this.namingStrategy);
|
||||
}
|
||||
|
||||
Optional.ofNullable(this.applicationContext).ifPresent(persistentProperty::setApplicationContext);
|
||||
|
||||
return persistentProperty;
|
||||
@@ -424,9 +428,7 @@ public class CassandraMappingContext
|
||||
|
||||
return getPersistentEntities().stream().flatMap(entity -> StreamSupport.stream(entity.spliterator(), false))
|
||||
.flatMap(it -> Optionals.toStream(Optional.ofNullable(it.findAnnotation(CassandraType.class))))
|
||||
.map(CassandraType::userTypeName)
|
||||
.filter(StringUtils::hasText)
|
||||
.map(CqlIdentifier::fromCql)
|
||||
.map(CassandraType::userTypeName).filter(StringUtils::hasText).map(CqlIdentifier::fromCql)
|
||||
.anyMatch(identifier::equals);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,14 +43,7 @@ public class CassandraUserTypePersistentEntity<T> extends BasicCassandraPersiste
|
||||
|
||||
@Override
|
||||
protected CqlIdentifier determineTableName() {
|
||||
|
||||
UserDefinedType annotation = findAnnotation(UserDefinedType.class);
|
||||
|
||||
if (annotation != null) {
|
||||
return determineName(annotation.value(), annotation.forceQuote());
|
||||
}
|
||||
|
||||
return IdentifierFactory.create(getNamingStrategy().getUserDefinedTypeName(this), false);
|
||||
return determineTableName(NamingStrategy::getUserDefinedTypeName, findAnnotation(UserDefinedType.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.cassandra.core.mapping;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.data.cassandra.util.SpelUtils;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
import com.datastax.oss.driver.internal.core.util.Strings;
|
||||
|
||||
/**
|
||||
* Strategy class to generate {@link CqlIdentifier identifier names} using {@link NamingStrategy} and contextual details
|
||||
* from entities and properties.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.4.2
|
||||
*/
|
||||
class CqlIdentifierGenerator {
|
||||
|
||||
private @Nullable NamingStrategy namingStrategy;
|
||||
|
||||
static CqlIdentifier createIdentifier(String simpleName, boolean forceQuote) {
|
||||
|
||||
if (Strings.isDoubleQuoted(simpleName)) {
|
||||
return CqlIdentifier.fromCql(simpleName);
|
||||
}
|
||||
|
||||
if (forceQuote || Strings.needsDoubleQuotes(simpleName)) {
|
||||
return CqlIdentifier.fromInternal(simpleName);
|
||||
}
|
||||
|
||||
return CqlIdentifier.fromCql(simpleName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a {@link CqlIdentifier name} using the provided name or fall back to the default {@link Function name
|
||||
* generator} using a {@link NamingStrategy}.
|
||||
*
|
||||
* @param providedName the name to use if provided.
|
||||
* @param forceQuote whether to enforce quoting.
|
||||
* @param defaultNameGenerator the default name generator.
|
||||
* @param source source to be used for name generation.
|
||||
* @param spelContext the SpEL evaluation context for evaluating SpEL expressions provided through
|
||||
* {@code providedName}.
|
||||
* @return the generated name.
|
||||
*/
|
||||
public <T> CqlIdentifier generate(@Nullable String providedName, boolean forceQuote,
|
||||
BiFunction<NamingStrategy, T, String> defaultNameGenerator, T source, @Nullable EvaluationContext spelContext) {
|
||||
|
||||
String name;
|
||||
boolean useForceQuote = forceQuote;
|
||||
|
||||
if (StringUtils.hasText(providedName)) {
|
||||
name = spelContext != null ? SpelUtils.evaluate(providedName, spelContext) : providedName;
|
||||
useForceQuote = true;
|
||||
} else {
|
||||
name = defaultNameGenerator.apply(getNamingStrategy(forceQuote), source);
|
||||
}
|
||||
|
||||
Assert.state(name != null, () -> String.format("Cannot determine default name for %s", source));
|
||||
|
||||
return createIdentifier(name, useForceQuote);
|
||||
}
|
||||
|
||||
public void setNamingStrategy(@Nullable NamingStrategy namingStrategy) {
|
||||
|
||||
Assert.notNull(namingStrategy, "NamingStrategy must not be null");
|
||||
|
||||
this.namingStrategy = namingStrategy;
|
||||
}
|
||||
|
||||
private NamingStrategy getNamingStrategy(boolean forceQuote) {
|
||||
|
||||
if (namingStrategy == null) {
|
||||
if (forceQuote) {
|
||||
return new NamingStrategy() {};
|
||||
} else {
|
||||
return NamingStrategy.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
return namingStrategy;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.cassandra.core.mapping;
|
||||
|
||||
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
import com.datastax.oss.driver.internal.core.util.Strings;
|
||||
|
||||
/**
|
||||
* Factory for {@link CqlIdentifier}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class IdentifierFactory {
|
||||
|
||||
static CqlIdentifier create(String simpleName, boolean forceQuote) {
|
||||
|
||||
if (forceQuote) {
|
||||
return CqlIdentifier.fromCql("\"" + simpleName + "\"");
|
||||
}
|
||||
|
||||
if (Strings.needsDoubleQuotes(simpleName.toLowerCase())) {
|
||||
return CqlIdentifier.fromCql("\"" + simpleName.toLowerCase() + "\"");
|
||||
}
|
||||
|
||||
return CqlIdentifier.fromInternal(simpleName.toLowerCase());
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.core.mapping;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Interface and default implementation of a naming strategy. Defaults to table name based on {@link Class} and column
|
||||
* name based on property names.
|
||||
* name based on property names. Names are used as-is without quoting. Lower-case, non-keyword names are used without
|
||||
* quoting. Upper-case, keyword or other names requiring quoting are used with quotes.
|
||||
* <p>
|
||||
* NOTE: Can also be used as an adapter. Create a lambda or an anonymous subclass and override any settings to implement
|
||||
* a different strategy on the fly.
|
||||
@@ -35,8 +37,15 @@ public interface NamingStrategy {
|
||||
* Empty implementation of the interface utilizing only the default implementation.
|
||||
* <p>
|
||||
* Using this avoids creating essentially the same class over and over again.
|
||||
*
|
||||
* @since 3.3.6
|
||||
*/
|
||||
NamingStrategy INSTANCE = new NamingStrategy() {};
|
||||
NamingStrategy CASE_SENSITIVE = new NamingStrategy() {};
|
||||
|
||||
/**
|
||||
* Default implementation converting all names to {@link String#toLowerCase()}.
|
||||
*/
|
||||
NamingStrategy INSTANCE = CASE_SENSITIVE.transform(s -> s.toLowerCase(Locale.ROOT));
|
||||
|
||||
/**
|
||||
* Naming strategy that renders CamelCase name parts to {@code snake_case}.
|
||||
@@ -74,10 +83,8 @@ public interface NamingStrategy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a {@link UnaryOperator transformation function} to create a new {@link NamingStrategy}
|
||||
* that applies the given transformation to each name component.
|
||||
*
|
||||
* Example:
|
||||
* Apply a {@link UnaryOperator transformation function} to create a new {@link NamingStrategy} that applies the given
|
||||
* transformation to each name component. Example:
|
||||
* <p class="code">
|
||||
* NamingStrategy lower = NamingStrategy.INSTANCE.transform(String::toLowerCase);
|
||||
* </p>
|
||||
|
||||
@@ -52,12 +52,12 @@ class BasicCassandraPersistentEntityUnitTests {
|
||||
@Mock ApplicationContext context;
|
||||
|
||||
@Test
|
||||
void subclassInheritsAtDocumentAnnotation() {
|
||||
void subclassInheritsAtTableAnnotation() {
|
||||
|
||||
BasicCassandraPersistentEntity<Notification> entity = new BasicCassandraPersistentEntity<>(
|
||||
ClassTypeInformation.from(Notification.class));
|
||||
|
||||
assertThat(entity.getTableName()).hasToString("messages");
|
||||
assertThat(entity.getTableName().asCql(true)).isEqualTo("messages");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -21,6 +21,8 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
|
||||
@@ -38,7 +40,48 @@ class NamingStrategyUnitTests {
|
||||
context.setUserTypeResolver(typeName -> {
|
||||
throw new IllegalStateException("");
|
||||
});
|
||||
context.setNamingStrategy(NamingStrategy.INSTANCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTableNameGeneratesTableName() {
|
||||
|
||||
BasicCassandraPersistentEntity<TableNameHolderThingy> entity = new BasicCassandraPersistentEntity<>(
|
||||
ClassTypeInformation.from(TableNameHolderThingy.class));
|
||||
|
||||
assertThat(entity.getTableName().asCql(true)).isEqualTo("tablenameholderthingy");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTableNameGeneratesQuotedTableName() {
|
||||
|
||||
BasicCassandraPersistentEntity<TableNameHolderThingy> entity = new BasicCassandraPersistentEntity<>(
|
||||
ClassTypeInformation.from(TableNameHolderThingy.class));
|
||||
entity.setNamingStrategy(NamingStrategy.SNAKE_CASE.transform(it -> "\"" + StringUtils.capitalize(it) + "\""));
|
||||
|
||||
assertThat(entity.getTableName().asCql(true)).isEqualTo("\"Table_name_holder_thingy\"");
|
||||
|
||||
entity = new BasicCassandraPersistentEntity<>(ClassTypeInformation.from(TableNameHolderThingy.class));
|
||||
entity.setNamingStrategy(NamingStrategy.SNAKE_CASE.transform(String::toUpperCase));
|
||||
|
||||
assertThat(entity.getTableName().asCql(true)).isEqualTo("\"TABLE_NAME_HOLDER_THINGY\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void atTableIsCaseSensitive() {
|
||||
|
||||
BasicCassandraPersistentEntity<ProvidedTableName> entity = new BasicCassandraPersistentEntity<>(
|
||||
ClassTypeInformation.from(ProvidedTableName.class));
|
||||
|
||||
assertThat(entity.getTableName().asCql(true)).isEqualTo("\"iAmProvided\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void atTableWithQuotedNameShouldRetainQuotes() {
|
||||
|
||||
BasicCassandraPersistentEntity<QuotedTableName> entity = new BasicCassandraPersistentEntity<>(
|
||||
ClassTypeInformation.from(QuotedTableName.class));
|
||||
|
||||
assertThat(entity.getTableName().asCql(true)).isEqualTo("\"IAmQuoted\"");
|
||||
}
|
||||
|
||||
@Test // DATACASS-84
|
||||
@@ -105,6 +148,17 @@ class NamingStrategyUnitTests {
|
||||
@Id String firstName;
|
||||
}
|
||||
|
||||
@Table("messages")
|
||||
static class Message {}
|
||||
|
||||
static class TableNameHolderThingy {}
|
||||
|
||||
@Table("iAmProvided")
|
||||
private static class ProvidedTableName {}
|
||||
|
||||
@Table("\"IAmQuoted\"")
|
||||
private static class QuotedTableName {}
|
||||
|
||||
@UserDefinedType
|
||||
private static class MyUserType {
|
||||
|
||||
@@ -122,4 +176,5 @@ class NamingStrategyUnitTests {
|
||||
|
||||
String firstName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -138,8 +138,8 @@ The conventions are:
|
||||
|
||||
* The simple (short) Java class name is mapped to the table name by being changed to lower case.
|
||||
For example, `com.bigbank.SavingsAccount` maps to a table named `savingsaccount`.
|
||||
* The converter uses any registered Spring `Converter` instances to override the default mapping of object properties to tables fields.
|
||||
* The properties of an object are used to convert to and from properties in the table.
|
||||
* The converter uses any registered Spring `Converter` instances to override the default mapping of object properties to tables columns.
|
||||
* The properties of an object are used to convert to and from columns in the table.
|
||||
|
||||
You can adjust conventions by configuring a `NamingStrategy` on `CassandraMappingContext`.
|
||||
Naming strategy objects implement the convention by which a table, column or user-defined type is derived from an entity class and from an actual property.
|
||||
|
||||
Reference in New Issue
Block a user