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:
@@ -15,15 +15,17 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.cassandra.core.mapping;
|
package org.springframework.data.cassandra.core.mapping;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
import org.springframework.context.expression.BeanFactoryAccessor;
|
import org.springframework.context.expression.BeanFactoryAccessor;
|
||||||
import org.springframework.context.expression.BeanFactoryResolver;
|
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.Association;
|
||||||
import org.springframework.data.mapping.AssociationHandler;
|
import org.springframework.data.mapping.AssociationHandler;
|
||||||
import org.springframework.data.mapping.MappingException;
|
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.expression.spel.support.StandardEvaluationContext;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
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 static final CassandraPersistentEntityMetadataVerifier DEFAULT_VERIFIER = new CompositeCassandraPersistentEntityMetadataVerifier();
|
||||||
|
|
||||||
|
private final CqlIdentifierGenerator namingAccessor = new CqlIdentifierGenerator();
|
||||||
|
|
||||||
private Boolean forceQuote;
|
private Boolean forceQuote;
|
||||||
|
|
||||||
private CassandraPersistentEntityMetadataVerifier verifier = DEFAULT_VERIFIER;
|
private CassandraPersistentEntityMetadataVerifier verifier = DEFAULT_VERIFIER;
|
||||||
|
|
||||||
private CqlIdentifier tableName;
|
private CqlIdentifier tableName;
|
||||||
|
|
||||||
private NamingStrategy namingStrategy = NamingStrategy.INSTANCE;
|
|
||||||
|
|
||||||
private @Nullable StandardEvaluationContext spelContext;
|
private @Nullable StandardEvaluationContext spelContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,27 +102,20 @@ public class BasicCassandraPersistentEntity<T> extends BasicPersistentEntity<T,
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected CqlIdentifier determineTableName() {
|
protected CqlIdentifier determineTableName() {
|
||||||
|
return determineTableName(NamingStrategy::getTableName, findAnnotation(Table.class));
|
||||||
Table annotation = findAnnotation(Table.class);
|
|
||||||
|
|
||||||
if (annotation != null) {
|
|
||||||
return determineName(annotation.value(), annotation.forceQuote());
|
|
||||||
}
|
|
||||||
|
|
||||||
return IdentifierFactory.create(getNamingStrategy().getTableName(this), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CqlIdentifier determineName(String value, boolean forceQuote) {
|
CqlIdentifier determineTableName(
|
||||||
|
BiFunction<NamingStrategy, CassandraPersistentEntity<?>, String> defaultNameGenerator,
|
||||||
|
@Nullable Annotation annotation) {
|
||||||
|
|
||||||
if (!StringUtils.hasText(value)) {
|
if (annotation != null) {
|
||||||
return IdentifierFactory.create(getNamingStrategy().getTableName(this), forceQuote);
|
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);
|
return this.namingAccessor.generate(null, forceQuote != null ? forceQuote : false, defaultNameGenerator, this,
|
||||||
|
this.spelContext);
|
||||||
Assert.state(name != null, () -> String.format("Cannot determine default name for %s", this));
|
|
||||||
|
|
||||||
return IdentifierFactory.create(name, forceQuote);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -186,7 +180,7 @@ public class BasicCassandraPersistentEntity<T> extends BasicPersistentEntity<T,
|
|||||||
this.forceQuote = forceQuote;
|
this.forceQuote = forceQuote;
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
setTableName(IdentifierFactory.create(getTableName().asInternal(), forceQuote));
|
setTableName(CqlIdentifierGenerator.createIdentifier(getTableName().asInternal(), forceQuote));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,14 +202,7 @@ public class BasicCassandraPersistentEntity<T> extends BasicPersistentEntity<T,
|
|||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
public void setNamingStrategy(NamingStrategy namingStrategy) {
|
public void setNamingStrategy(NamingStrategy namingStrategy) {
|
||||||
|
this.namingAccessor.setNamingStrategy(namingStrategy);
|
||||||
Assert.notNull(namingStrategy, "NamingStrategy must not be null");
|
|
||||||
|
|
||||||
this.namingStrategy = namingStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
NamingStrategy getNamingStrategy() {
|
|
||||||
return namingStrategy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import java.lang.reflect.Field;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
@@ -31,7 +30,6 @@ import org.springframework.context.expression.BeanFactoryResolver;
|
|||||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
import org.springframework.data.cassandra.core.cql.Ordering;
|
import org.springframework.data.cassandra.core.cql.Ordering;
|
||||||
import org.springframework.data.cassandra.core.cql.PrimaryKeyType;
|
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.Association;
|
||||||
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
|
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
|
||||||
import org.springframework.data.mapping.model.Property;
|
import org.springframework.data.mapping.model.Property;
|
||||||
@@ -42,7 +40,6 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
|
|||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||||
|
|
||||||
@@ -59,13 +56,13 @@ import com.datastax.oss.driver.api.core.CqlIdentifier;
|
|||||||
public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentProperty<CassandraPersistentProperty>
|
public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentProperty<CassandraPersistentProperty>
|
||||||
implements CassandraPersistentProperty, ApplicationContextAware {
|
implements CassandraPersistentProperty, ApplicationContextAware {
|
||||||
|
|
||||||
|
private final CqlIdentifierGenerator namingAccessor = new CqlIdentifierGenerator();
|
||||||
|
|
||||||
// Indicates whether this property has been explicitly instructed to force quoted column names.
|
// Indicates whether this property has been explicitly instructed to force quoted column names.
|
||||||
private Boolean forceQuote;
|
private Boolean forceQuote;
|
||||||
|
|
||||||
private @Nullable CqlIdentifier columnName;
|
private @Nullable CqlIdentifier columnName;
|
||||||
|
|
||||||
private NamingStrategy namingStrategy = NamingStrategy.INSTANCE;
|
|
||||||
|
|
||||||
private @Nullable StandardEvaluationContext spelContext;
|
private @Nullable StandardEvaluationContext spelContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -197,8 +194,6 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Supplier<String> defaultName = () -> getNamingStrategy().getColumnName(this);
|
|
||||||
|
|
||||||
String overriddenName = null;
|
String overriddenName = null;
|
||||||
|
|
||||||
boolean forceQuote = false;
|
boolean forceQuote = false;
|
||||||
@@ -231,22 +226,7 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return createColumnName(defaultName, overriddenName, forceQuote);
|
return namingAccessor.generate(overriddenName, forceQuote, NamingStrategy::getColumnName, this, this.spelContext);
|
||||||
}
|
|
||||||
|
|
||||||
@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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -267,14 +247,7 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
|
|||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
public void setNamingStrategy(NamingStrategy namingStrategy) {
|
public void setNamingStrategy(NamingStrategy namingStrategy) {
|
||||||
|
this.namingAccessor.setNamingStrategy(namingStrategy);
|
||||||
Assert.notNull(namingStrategy, "NamingStrategy must not be null");
|
|
||||||
|
|
||||||
this.namingStrategy = namingStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
NamingStrategy getNamingStrategy() {
|
|
||||||
return this.namingStrategy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -288,7 +261,7 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
|
|||||||
this.forceQuote = forceQuote;
|
this.forceQuote = forceQuote;
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
setColumnName(IdentifierFactory.create(getRequiredColumnName().asInternal(), forceQuote));
|
setColumnName(CqlIdentifierGenerator.createIdentifier(getRequiredColumnName().asInternal(), forceQuote));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public class CassandraMappingContext
|
|||||||
|
|
||||||
private Mapping mapping = new Mapping();
|
private Mapping mapping = new Mapping();
|
||||||
|
|
||||||
private NamingStrategy namingStrategy = NamingStrategy.INSTANCE;
|
private @Nullable NamingStrategy namingStrategy;
|
||||||
|
|
||||||
private @Deprecated @Nullable UserTypeResolver userTypeResolver;
|
private @Deprecated @Nullable UserTypeResolver userTypeResolver;
|
||||||
|
|
||||||
@@ -131,7 +131,8 @@ public class CassandraMappingContext
|
|||||||
String entityTableName = entityMapping.getTableName();
|
String entityTableName = entityMapping.getTableName();
|
||||||
|
|
||||||
if (StringUtils.hasText(entityTableName)) {
|
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);
|
processMappingOverrides(entity, entityMapping);
|
||||||
@@ -163,7 +164,7 @@ public class CassandraMappingContext
|
|||||||
property.setForceQuote(forceQuote);
|
property.setForceQuote(forceQuote);
|
||||||
|
|
||||||
if (StringUtils.hasText(mapping.getColumnName())) {
|
if (StringUtils.hasText(mapping.getColumnName())) {
|
||||||
property.setColumnName(IdentifierFactory.create(mapping.getColumnName(), forceQuote));
|
property.setColumnName(CqlIdentifierGenerator.createIdentifier(mapping.getColumnName(), forceQuote));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +201,7 @@ public class CassandraMappingContext
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since 3.0. Use custom conversion through
|
* @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
|
@Deprecated
|
||||||
public CustomConversions getCustomConversions() {
|
public CustomConversions getCustomConversions() {
|
||||||
@@ -255,7 +256,7 @@ public class CassandraMappingContext
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since 3.0. Retrieve {@link CodecRegistry} directly from
|
* @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
|
@Deprecated
|
||||||
public CodecRegistry getCodecRegistry() {
|
public CodecRegistry getCodecRegistry() {
|
||||||
@@ -304,7 +305,7 @@ public class CassandraMappingContext
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since 3.0. Retrieve {@link UserTypeResolver} directly from
|
* @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
|
@Nullable
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@@ -352,7 +353,6 @@ public class CassandraMappingContext
|
|||||||
if (!entity.isUserDefinedType() && !entity.isTupleType() && entity.isAnnotationPresent(Table.class)) {
|
if (!entity.isUserDefinedType() && !entity.isTupleType() && entity.isAnnotationPresent(Table.class)) {
|
||||||
this.tableEntities.add(entity);
|
this.tableEntities.add(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return optional;
|
return optional;
|
||||||
@@ -375,11 +375,12 @@ public class CassandraMappingContext
|
|||||||
|
|
||||||
BasicCassandraPersistentEntity<T> entity = isUserDefinedType(typeInformation)
|
BasicCassandraPersistentEntity<T> entity = isUserDefinedType(typeInformation)
|
||||||
? new CassandraUserTypePersistentEntity<>(typeInformation, getVerifier())
|
? new CassandraUserTypePersistentEntity<>(typeInformation, getVerifier())
|
||||||
: isTuple(typeInformation)
|
: isTuple(typeInformation) ? new BasicCassandraPersistentTupleEntity<>(typeInformation)
|
||||||
? new BasicCassandraPersistentTupleEntity<>(typeInformation)
|
: new BasicCassandraPersistentEntity<>(typeInformation, getVerifier());
|
||||||
: new BasicCassandraPersistentEntity<>(typeInformation, getVerifier());
|
|
||||||
|
|
||||||
entity.setNamingStrategy(this.namingStrategy);
|
if (this.namingStrategy != null) {
|
||||||
|
entity.setNamingStrategy(this.namingStrategy);
|
||||||
|
}
|
||||||
Optional.ofNullable(this.applicationContext).ifPresent(entity::setApplicationContext);
|
Optional.ofNullable(this.applicationContext).ifPresent(entity::setApplicationContext);
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
@@ -404,7 +405,10 @@ public class CassandraMappingContext
|
|||||||
? new BasicCassandraPersistentTupleProperty(property, owner, simpleTypeHolder)
|
? new BasicCassandraPersistentTupleProperty(property, owner, simpleTypeHolder)
|
||||||
: new CachingCassandraPersistentProperty(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);
|
Optional.ofNullable(this.applicationContext).ifPresent(persistentProperty::setApplicationContext);
|
||||||
|
|
||||||
return persistentProperty;
|
return persistentProperty;
|
||||||
@@ -445,9 +449,7 @@ public class CassandraMappingContext
|
|||||||
|
|
||||||
return getPersistentEntities().stream().flatMap(entity -> StreamSupport.stream(entity.spliterator(), false))
|
return getPersistentEntities().stream().flatMap(entity -> StreamSupport.stream(entity.spliterator(), false))
|
||||||
.flatMap(it -> Optionals.toStream(Optional.ofNullable(it.findAnnotation(CassandraType.class))))
|
.flatMap(it -> Optionals.toStream(Optional.ofNullable(it.findAnnotation(CassandraType.class))))
|
||||||
.map(CassandraType::userTypeName)
|
.map(CassandraType::userTypeName).filter(StringUtils::hasText).map(CqlIdentifier::fromCql)
|
||||||
.filter(StringUtils::hasText)
|
|
||||||
.map(CqlIdentifier::fromCql)
|
|
||||||
.anyMatch(identifier::equals);
|
.anyMatch(identifier::equals);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,14 +46,7 @@ public class CassandraUserTypePersistentEntity<T> extends BasicCassandraPersiste
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected CqlIdentifier determineTableName() {
|
protected CqlIdentifier determineTableName() {
|
||||||
|
return determineTableName(NamingStrategy::getUserDefinedTypeName, findAnnotation(UserDefinedType.class));
|
||||||
UserDefinedType annotation = findAnnotation(UserDefinedType.class);
|
|
||||||
|
|
||||||
if (annotation != null) {
|
|
||||||
return determineName(annotation.value(), annotation.forceQuote());
|
|
||||||
}
|
|
||||||
|
|
||||||
return IdentifierFactory.create(getNamingStrategy().getUserDefinedTypeName(this), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
|
|||||||
@@ -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.3.6
|
||||||
|
*/
|
||||||
|
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;
|
package org.springframework.data.cassandra.core.mapping;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.function.UnaryOperator;
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface and default implementation of a naming strategy. Defaults to table name based on {@link Class} and column
|
* 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>
|
* <p>
|
||||||
* NOTE: Can also be used as an adapter. Create a lambda or an anonymous subclass and override any settings to implement
|
* 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.
|
* a different strategy on the fly.
|
||||||
@@ -35,8 +37,15 @@ public interface NamingStrategy {
|
|||||||
* Empty implementation of the interface utilizing only the default implementation.
|
* Empty implementation of the interface utilizing only the default implementation.
|
||||||
* <p>
|
* <p>
|
||||||
* Using this avoids creating essentially the same class over and over again.
|
* 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}.
|
* 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}
|
* Apply a {@link UnaryOperator transformation function} to create a new {@link NamingStrategy} that applies the given
|
||||||
* that applies the given transformation to each name component.
|
* transformation to each name component. Example:
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* <p class="code">
|
* <p class="code">
|
||||||
* NamingStrategy lower = NamingStrategy.INSTANCE.transform(String::toLowerCase);
|
* NamingStrategy lower = NamingStrategy.INSTANCE.transform(String::toLowerCase);
|
||||||
* </p>
|
* </p>
|
||||||
|
|||||||
@@ -52,12 +52,12 @@ class BasicCassandraPersistentEntityUnitTests {
|
|||||||
@Mock ApplicationContext context;
|
@Mock ApplicationContext context;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void subclassInheritsAtDocumentAnnotation() {
|
void subclassInheritsAtTableAnnotation() {
|
||||||
|
|
||||||
BasicCassandraPersistentEntity<Notification> entity = new BasicCassandraPersistentEntity<>(
|
BasicCassandraPersistentEntity<Notification> entity = new BasicCassandraPersistentEntity<>(
|
||||||
ClassTypeInformation.from(Notification.class));
|
ClassTypeInformation.from(Notification.class));
|
||||||
|
|
||||||
assertThat(entity.getTableName()).hasToString("messages");
|
assertThat(entity.getTableName().asCql(true)).isEqualTo("messages");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.data.annotation.Id;
|
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;
|
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||||
|
|
||||||
@@ -38,7 +40,48 @@ class NamingStrategyUnitTests {
|
|||||||
context.setUserTypeResolver(typeName -> {
|
context.setUserTypeResolver(typeName -> {
|
||||||
throw new IllegalStateException("");
|
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
|
@Test // DATACASS-84
|
||||||
@@ -105,6 +148,17 @@ class NamingStrategyUnitTests {
|
|||||||
@Id String firstName;
|
@Id String firstName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Table("messages")
|
||||||
|
static class Message {}
|
||||||
|
|
||||||
|
static class TableNameHolderThingy {}
|
||||||
|
|
||||||
|
@Table("iAmProvided")
|
||||||
|
private static class ProvidedTableName {}
|
||||||
|
|
||||||
|
@Table("\"IAmQuoted\"")
|
||||||
|
private static class QuotedTableName {}
|
||||||
|
|
||||||
@UserDefinedType
|
@UserDefinedType
|
||||||
private static class MyUserType {
|
private static class MyUserType {
|
||||||
|
|
||||||
@@ -122,4 +176,5 @@ class NamingStrategyUnitTests {
|
|||||||
|
|
||||||
String firstName;
|
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.
|
* 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`.
|
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 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 properties in the table.
|
* 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`.
|
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.
|
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