From c120e8f050d97aad30bcedf3c9b41bee9ccbe944 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 5 Jul 2022 13:36:02 +0200 Subject: [PATCH] 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 --- .../AbstractCassandraConfiguration.java | 3 +- .../BasicCassandraPersistentEntity.java | 45 +++----- .../BasicCassandraPersistentProperty.java | 37 +------ .../core/mapping/CassandraMappingContext.java | 32 +++--- .../CassandraUserTypePersistentEntity.java | 9 +- .../core/mapping/CqlIdentifierGenerator.java | 103 ++++++++++++++++++ .../core/mapping/IdentifierFactory.java | 40 ------- .../core/mapping/NamingStrategy.java | 19 +++- ...sicCassandraPersistentEntityUnitTests.java | 4 +- .../core/mapping/NamingStrategyUnitTests.java | 57 +++++++++- src/main/asciidoc/reference/mapping.adoc | 4 +- 11 files changed, 216 insertions(+), 137 deletions(-) create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CqlIdentifierGenerator.java delete mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/IdentifierFactory.java diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractCassandraConfiguration.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractCassandraConfiguration.java index c26f3a039..c43802432 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractCassandraConfiguration.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractCassandraConfiguration.java @@ -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 { diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java index e8b91d0fd..688865243 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java @@ -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 extends BasicPersistentEntity extends BasicPersistentEntity, 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 extends BasicPersistentEntity extends BasicPersistentEntity 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 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 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)); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraMappingContext.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraMappingContext.java index 5dfa33583..bbdb629a6 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraMappingContext.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraMappingContext.java @@ -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 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); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraUserTypePersistentEntity.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraUserTypePersistentEntity.java index c2d98ebaa..6c3a661f1 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraUserTypePersistentEntity.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraUserTypePersistentEntity.java @@ -43,14 +43,7 @@ public class CassandraUserTypePersistentEntity 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 diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CqlIdentifierGenerator.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CqlIdentifierGenerator.java new file mode 100644 index 000000000..afd2e2897 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CqlIdentifierGenerator.java @@ -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 CqlIdentifier generate(@Nullable String providedName, boolean forceQuote, + BiFunction 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; + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/IdentifierFactory.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/IdentifierFactory.java deleted file mode 100644 index 6ae734237..000000000 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/IdentifierFactory.java +++ /dev/null @@ -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()); - } -} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/NamingStrategy.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/NamingStrategy.java index d5e79aa09..c51ef8ebb 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/NamingStrategy.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/NamingStrategy.java @@ -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. *

* 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. *

* 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: *

* NamingStrategy lower = NamingStrategy.INSTANCE.transform(String::toLowerCase); *

diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntityUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntityUnitTests.java index ae524a1f3..034f86713 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntityUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntityUnitTests.java @@ -52,12 +52,12 @@ class BasicCassandraPersistentEntityUnitTests { @Mock ApplicationContext context; @Test - void subclassInheritsAtDocumentAnnotation() { + void subclassInheritsAtTableAnnotation() { BasicCassandraPersistentEntity entity = new BasicCassandraPersistentEntity<>( ClassTypeInformation.from(Notification.class)); - assertThat(entity.getTableName()).hasToString("messages"); + assertThat(entity.getTableName().asCql(true)).isEqualTo("messages"); } @Test diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/NamingStrategyUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/NamingStrategyUnitTests.java index a74fe62be..b47932ed5 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/NamingStrategyUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/NamingStrategyUnitTests.java @@ -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 entity = new BasicCassandraPersistentEntity<>( + ClassTypeInformation.from(TableNameHolderThingy.class)); + + assertThat(entity.getTableName().asCql(true)).isEqualTo("tablenameholderthingy"); + } + + @Test + void getTableNameGeneratesQuotedTableName() { + + BasicCassandraPersistentEntity 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 entity = new BasicCassandraPersistentEntity<>( + ClassTypeInformation.from(ProvidedTableName.class)); + + assertThat(entity.getTableName().asCql(true)).isEqualTo("\"iAmProvided\""); + } + + @Test + void atTableWithQuotedNameShouldRetainQuotes() { + + BasicCassandraPersistentEntity 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; } + } diff --git a/src/main/asciidoc/reference/mapping.adoc b/src/main/asciidoc/reference/mapping.adoc index 2f41f3dd7..7394ecae8 100644 --- a/src/main/asciidoc/reference/mapping.adoc +++ b/src/main/asciidoc/reference/mapping.adoc @@ -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.