Skip to content

Commit e154ef6

Browse files
committed
Allow keyspace customization for Cassandra tables and UDTs.
We now allow setting the keyspace on `@Table` and `@UserDefinedType` to interact with tables and types residing in a specific keyspace. @table(keyspace = "some_ks") class Person { … } Keyspace values can be either string literals or value expressions (#{…}, ${…}) to use the context or config properties to compute the actual keyspace. Special honors go to @tomekl007 and @mipo256 for their contributions. See #1400 See #279 Closes #921
1 parent 0816576 commit e154ef6

File tree

60 files changed

+1007
-131
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1007
-131
lines changed

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraTemplate.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -722,8 +722,10 @@ public CompletableFuture<Void> truncate(Class<?> entityClass) {
722722

723723
Assert.notNull(entityClass, "Entity type must not be null");
724724

725-
CqlIdentifier tableName = getTableName(entityClass);
726-
Truncate truncate = QueryBuilder.truncate(tableName);
725+
CassandraPersistentEntity<?> entity = getRequiredPersistentEntity(entityClass);
726+
CqlIdentifier tableName = entity.getTableName();
727+
728+
Truncate truncate = QueryBuilder.truncate(entity.getKeyspace(), tableName);
727729
SimpleStatement statement = truncate.build();
728730

729731
maybeEmitEvent(() -> new BeforeDeleteEvent<>(statement, entityClass, tableName));

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaDropper.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.util.MultiValueMap;
3333

3434
import com.datastax.oss.driver.api.core.CqlIdentifier;
35+
import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata;
3536
import com.datastax.oss.driver.api.core.metadata.schema.RelationMetadata;
3637
import com.datastax.oss.driver.api.core.type.DataType;
3738
import com.datastax.oss.driver.api.core.type.ListType;
@@ -80,12 +81,17 @@ public CassandraPersistentEntitySchemaDropper(CassandraMappingContext mappingCon
8081
*/
8182
public void dropTables(boolean dropUnused) {
8283

84+
KeyspaceMetadata keyspaceMetadata = this.cassandraAdminOperations.getKeyspaceMetadata();
85+
Set<CqlIdentifier> canRecreate = this.mappingContext.getTableEntities().stream()
86+
.filter(it -> isInKeyspace(it, keyspaceMetadata)).map(CassandraPersistentEntity::getTableName)
87+
.collect(Collectors.toSet());
88+
8389
this.cassandraAdminOperations.getKeyspaceMetadata() //
8490
.getTables() //
8591
.values() //
8692
.stream() //
8793
.map(RelationMetadata::getName) //
88-
.filter(table -> dropUnused || this.mappingContext.usesTable(table)) //
94+
.filter(table -> canRecreate.contains(table) || (dropUnused && !mappingContext.usesTable(table))) //
8995
.forEach(this.cassandraAdminOperations::dropTable);
9096
}
9197

@@ -98,18 +104,28 @@ public void dropTables(boolean dropUnused) {
98104
*/
99105
public void dropUserTypes(boolean dropUnused) {
100106

107+
KeyspaceMetadata keyspaceMetadata = this.cassandraAdminOperations.getKeyspaceMetadata();
101108
Set<CqlIdentifier> canRecreate = this.mappingContext.getUserDefinedTypeEntities().stream()
102-
.map(CassandraPersistentEntity::getTableName).collect(Collectors.toSet());
109+
.filter(it -> isInKeyspace(it, keyspaceMetadata)).map(CassandraPersistentEntity::getTableName)
110+
.collect(Collectors.toSet());
103111

104-
Collection<UserDefinedType> userTypes = this.cassandraAdminOperations.getKeyspaceMetadata().getUserDefinedTypes()
105-
.values();
112+
Collection<UserDefinedType> userTypes = keyspaceMetadata.getUserDefinedTypes().values();
106113

107114
getUserTypesToDrop(userTypes) //
108115
.stream() //
109116
.filter(it -> canRecreate.contains(it) || (dropUnused && !mappingContext.usesUserType(it))) //
110117
.forEach(this.cassandraAdminOperations::dropUserType);
111118
}
112119

120+
private static boolean isInKeyspace(CassandraPersistentEntity<?> entity, KeyspaceMetadata keyspaceMetadata) {
121+
122+
if (entity.hasKeyspace() && keyspaceMetadata.getName().equals(entity.getRequiredKeyspace())) {
123+
return true;
124+
}
125+
126+
return !entity.hasKeyspace();
127+
}
128+
113129
/**
114130
* Create {@link List} of {@link CqlIdentifier} with User-Defined type names to drop considering dependencies between
115131
* UDTs.

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -748,8 +748,10 @@ public void truncate(Class<?> entityClass) {
748748

749749
Assert.notNull(entityClass, "Entity type must not be null");
750750

751-
CqlIdentifier tableName = getTableName(entityClass);
752-
Truncate truncate = QueryBuilder.truncate(tableName);
751+
CassandraPersistentEntity<?> entity = getRequiredPersistentEntity(entityClass);
752+
CqlIdentifier tableName = entity.getTableName();
753+
754+
Truncate truncate = QueryBuilder.truncate(entity.getKeyspace(), tableName);
753755
SimpleStatement statement = truncate.build();
754756

755757
maybeEmitEvent(() -> new BeforeDeleteEvent<>(statement, entityClass, tableName));

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveCassandraTemplate.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -728,8 +728,10 @@ public Mono<Void> truncate(Class<?> entityClass) {
728728

729729
Assert.notNull(entityClass, "Entity type must not be null");
730730

731-
CqlIdentifier tableName = getTableName(entityClass);
732-
Truncate truncate = QueryBuilder.truncate(tableName);
731+
CassandraPersistentEntity<?> entity = getRequiredPersistentEntity(entityClass);
732+
CqlIdentifier tableName = entity.getTableName();
733+
734+
Truncate truncate = QueryBuilder.truncate(entity.getKeyspace(), tableName);
733735
SimpleStatement statement = truncate.build();
734736

735737
Mono<Boolean> result = doExecute(statement, ReactiveResultSet::wasApplied)

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/StatementFactory.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public class StatementFactory {
110110

111111
private final UpdateMapper updateMapper;
112112

113-
private KeyspaceProvider keyspaceProvider = KeyspaceProviders.EMPTY_KEYSPACE;
113+
private KeyspaceProvider keyspaceProvider = KeyspaceProviders.ENTITY_KEYSPACE;
114114

115115
/**
116116
* Create {@link StatementFactory} given {@link CassandraConverter}.
@@ -1252,7 +1252,25 @@ public interface KeyspaceProvider {
12521252

12531253
}
12541254

1255+
/**
1256+
* Implementations of {@link KeyspaceProvider}.
1257+
*/
12551258
enum KeyspaceProviders implements KeyspaceProvider {
1259+
1260+
/**
1261+
* Derive the keyspace from the given {@link CassandraPersistentEntity}.
1262+
*/
1263+
ENTITY_KEYSPACE {
1264+
@Nullable
1265+
@Override
1266+
public CqlIdentifier getKeyspace(CassandraPersistentEntity<?> entity, CqlIdentifier tableName) {
1267+
return entity.getKeyspace();
1268+
}
1269+
},
1270+
1271+
/**
1272+
* Use the session's keyspace.
1273+
*/
12561274
EMPTY_KEYSPACE {
12571275
@Nullable
12581276
@Override

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/DefaultColumnTypeResolver.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,10 +486,19 @@ private CodecRegistry getCodecRegistry() {
486486
}
487487

488488
private UserDefinedType getUserType(CassandraPersistentEntity<?> persistentEntity, boolean frozen) {
489-
return getUserType(persistentEntity.getTableName()).copy(frozen);
489+
return (persistentEntity.hasKeyspace()
490+
? getUserType(persistentEntity.getRequiredKeyspace(), persistentEntity.getTableName())
491+
: getUserType(persistentEntity.getTableName())).copy(frozen);
490492
}
491493

492494
private UserDefinedType getUserType(String userTypeName) {
495+
496+
if (userTypeName.contains(".") && !userTypeName.contains("\"")) {
497+
498+
String[] split = userTypeName.split("\\.");
499+
return getUserType(CqlIdentifier.fromCql(split[0]), CqlIdentifier.fromCql(split[1]));
500+
}
501+
493502
return getUserType(CqlIdentifier.fromCql(userTypeName));
494503
}
495504

@@ -504,6 +513,17 @@ private UserDefinedType getUserType(CqlIdentifier userTypeName) {
504513
return type;
505514
}
506515

516+
private UserDefinedType getUserType(CqlIdentifier keyspace, CqlIdentifier userTypeName) {
517+
518+
UserDefinedType type = userTypeResolver.resolveType(keyspace, userTypeName);
519+
520+
if (type == null) {
521+
throw new MappingException(String.format("User type [%s] in keyspace [%s] not found", userTypeName, keyspace));
522+
}
523+
524+
return type;
525+
}
526+
507527
private static void assertTypeArguments(int args, int expected) {
508528

509529
if (args != expected) {

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/IndexSpecificationFactory.java

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@
3434
import org.springframework.data.cassandra.core.mapping.SASI.Normalization;
3535
import org.springframework.data.cassandra.core.mapping.SASI.StandardAnalyzed;
3636
import org.springframework.data.mapping.MappingException;
37+
import org.springframework.lang.Nullable;
3738
import org.springframework.util.ObjectUtils;
3839
import org.springframework.util.StringUtils;
3940

41+
import com.datastax.oss.driver.api.core.CqlIdentifier;
42+
4043
/**
4144
* Factory to create {@link org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification} based on
4245
* index-annotated {@link CassandraPersistentProperty properties}.
@@ -70,13 +73,15 @@ class IndexSpecificationFactory {
7073
* @param property must not be {@literal null}.
7174
* @return {@link List} of {@link CreateIndexSpecification}.
7275
*/
73-
static List<CreateIndexSpecification> createIndexSpecifications(CassandraPersistentProperty property) {
76+
static List<CreateIndexSpecification> createIndexSpecifications(@Nullable CqlIdentifier keyspace,
77+
CassandraPersistentProperty property) {
7478

7579
List<CreateIndexSpecification> indexes = new ArrayList<>();
7680

7781
if (property.isAnnotationPresent(Indexed.class)) {
7882

79-
CreateIndexSpecification index = createIndexSpecification(property.findAnnotation(Indexed.class), property);
83+
CreateIndexSpecification index = createIndexSpecification(keyspace, property.findAnnotation(Indexed.class),
84+
property);
8085

8186
if (property.isMapLike()) {
8287
index.entries();
@@ -86,7 +91,7 @@ static List<CreateIndexSpecification> createIndexSpecifications(CassandraPersist
8691
}
8792

8893
if (property.isAnnotationPresent(SASI.class)) {
89-
indexes.add(createIndexSpecification(property.findAnnotation(SASI.class), property));
94+
indexes.add(createIndexSpecification(keyspace, property.findAnnotation(SASI.class), property));
9095
}
9196

9297
if (property.isMapLike()) {
@@ -113,41 +118,41 @@ static List<CreateIndexSpecification> createIndexSpecifications(CassandraPersist
113118
}
114119

115120
if (keyIndex != null) {
116-
indexes.add(createIndexSpecification(keyIndex, property).keys());
121+
indexes.add(createIndexSpecification(keyspace, keyIndex, property).keys());
117122
}
118123

119124
if (valueIndex != null) {
120-
indexes.add(createIndexSpecification(valueIndex, property).values());
125+
indexes.add(createIndexSpecification(keyspace, valueIndex, property).values());
121126
}
122127
}
123128
}
124129

125130
return indexes;
126131
}
127132

128-
static CreateIndexSpecification createIndexSpecification(Indexed annotation,
133+
static CreateIndexSpecification createIndexSpecification(@Nullable CqlIdentifier keyspace, Indexed annotation,
129134
CassandraPersistentProperty property) {
130135

131136
CreateIndexSpecification index;
132137

133138
if (StringUtils.hasText(annotation.value())) {
134-
index = CreateIndexSpecification.createIndex(annotation.value());
139+
index = CreateIndexSpecification.createIndex(keyspace, CqlIdentifier.fromCql(annotation.value()));
135140
} else {
136-
index = CreateIndexSpecification.createIndex();
141+
index = CreateIndexSpecification.createIndex(keyspace, null);
137142
}
138143

139144
return index.columnName(property.getRequiredColumnName());
140145
}
141146

142-
private static CreateIndexSpecification createIndexSpecification(SASI annotation,
147+
private static CreateIndexSpecification createIndexSpecification(@Nullable CqlIdentifier keyspace, SASI annotation,
143148
CassandraPersistentProperty property) {
144149

145150
CreateIndexSpecification index;
146151

147152
if (StringUtils.hasText(annotation.value())) {
148-
index = CreateIndexSpecification.createIndex(annotation.value());
153+
index = CreateIndexSpecification.createIndex(keyspace, CqlIdentifier.fromCql(annotation.value()));
149154
} else {
150-
index = CreateIndexSpecification.createIndex();
155+
index = CreateIndexSpecification.createIndex(keyspace, null);
151156
}
152157

153158
index.using("org.apache.cassandra.index.sasi.SASIIndex") //

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,19 @@ public MappingCassandraConverter(CassandraMappingContext mappingContext) {
141141

142142
Assert.notNull(mappingContext, "CassandraMappingContext must not be null");
143143

144-
UserTypeResolver userTypeResolver = userTypeName -> getUserTypeResolver().resolveType(userTypeName);
144+
UserTypeResolver userTypeResolver = new UserTypeResolver() {
145+
@Nullable
146+
@Override
147+
public UserDefinedType resolveType(CqlIdentifier typeName) {
148+
return MappingCassandraConverter.this.getUserTypeResolver().resolveType(typeName);
149+
}
150+
151+
@Nullable
152+
@Override
153+
public UserDefinedType resolveType(CqlIdentifier keyspace, CqlIdentifier typeName) {
154+
return MappingCassandraConverter.this.getUserTypeResolver().resolveType(keyspace, typeName);
155+
}
156+
};
145157

146158
this.mappingContext = mappingContext;
147159
this.setCodecRegistry(mappingContext.getCodecRegistry());

0 commit comments

Comments
 (0)