Skip to content

Commit 2ecc51c

Browse files
committed
DATACASS-828 - Resolve selection projection and ordering of composite key properties into multiple columns.
We now resolve property names pointing to a composite key property into its individual column names to be applies in a SELECT projection or for ordering.
1 parent 5d0295c commit 2ecc51c

File tree

3 files changed

+69
-34
lines changed

3 files changed

+69
-34
lines changed

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

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
import java.util.List;
2323
import java.util.Optional;
2424
import java.util.Set;
25-
import java.util.stream.Collectors;
2625

26+
import org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity;
2727
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
2828
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
2929
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
@@ -167,8 +167,14 @@ public List<Selector> getMappedSelectors(Columns columns, CassandraPersistentEnt
167167

168168
Field field = createPropertyField(entity, column);
169169

170-
columns.getSelector(column).ifPresent(selector -> getCqlIdentifier(column, field)
171-
.ifPresent(cqlIdentifier -> selectors.add(getMappedSelector(selector, cqlIdentifier))));
170+
columns.getSelector(column).ifPresent(selector -> {
171+
172+
List<CqlIdentifier> mappedColumnNames = getCqlIdentifier(column, field);
173+
174+
for (CqlIdentifier mappedColumnName : mappedColumnNames) {
175+
selectors.add(getMappedSelector(selector, mappedColumnName));
176+
}
177+
});
172178
}
173179

174180
if (columns.isEmpty()) {
@@ -207,17 +213,15 @@ private Selector getMappedSelector(Selector selector, CqlIdentifier cqlIdentifie
207213

208214
FunctionCall functionCall = (FunctionCall) selector;
209215

210-
List<Object> mappedParameters = functionCall.getParameters().stream().map(obj -> {
216+
FunctionCall mappedFunctionCall = FunctionCall.from(functionCall.getExpression(),
217+
functionCall.getParameters().stream().map(obj -> {
211218

212-
if (obj instanceof Selector) {
213-
return getMappedSelector((Selector) obj, cqlIdentifier);
214-
}
215-
216-
return obj;
217-
}) //
218-
.collect(Collectors.toList());
219+
if (obj instanceof Selector) {
220+
return getMappedSelector((Selector) obj, cqlIdentifier);
221+
}
219222

220-
FunctionCall mappedFunctionCall = FunctionCall.from(functionCall.getExpression(), mappedParameters.toArray());
223+
return obj;
224+
}).toArray());
221225

222226
return functionCall.getAlias() //
223227
.map(mappedFunctionCall::as) //
@@ -252,11 +256,10 @@ public List<CqlIdentifier> getMappedColumnNames(Columns columns, CassandraPersis
252256
for (ColumnName column : columns) {
253257

254258
Field field = createPropertyField(entity, column);
255-
256259
field.getProperty().ifPresent(seen::add);
257260

258261
columns.getSelector(column).filter(selector -> selector instanceof ColumnSelector)
259-
.ifPresent(columnSelector -> getCqlIdentifier(column, field).ifPresent(columnNames::add));
262+
.ifPresent(columnSelector -> columnNames.addAll(getCqlIdentifier(column, field)));
260263
}
261264

262265
if (columns.isEmpty()) {
@@ -293,40 +296,49 @@ public Sort getMappedSort(Sort sort, CassandraPersistentEntity<?> entity) {
293296

294297
Field field = createPropertyField(entity, columnName);
295298

296-
Order mappedOrder = getCqlIdentifier(columnName, field)
297-
.map(cqlIdentifier -> new Order(order.getDirection(), cqlIdentifier.toString())).orElse(order);
299+
List<CqlIdentifier> mappedColumnNames = getCqlIdentifier(columnName, field);
298300

299-
mappedOrders.add(mappedOrder);
301+
if (mappedColumnNames.isEmpty()) {
302+
mappedOrders.add(order);
303+
} else {
304+
for (CqlIdentifier mappedColumnName : mappedColumnNames) {
305+
mappedOrders.add(new Order(order.getDirection(), mappedColumnName.toString()));
306+
}
307+
}
300308
}
301309

302310
return Sort.by(mappedOrders);
303311
}
304312

305-
private Optional<CqlIdentifier> getCqlIdentifier(ColumnName column, Field field) {
313+
private List<CqlIdentifier> getCqlIdentifier(ColumnName column, Field field) {
306314

315+
List<CqlIdentifier> identifiers = new ArrayList<>(1);
307316
try {
308317
if (field.getProperty().isPresent()) {
309318

310-
return field.getProperty().map(cassandraPersistentProperty -> {
319+
CassandraPersistentProperty property = field.getProperty().get();
311320

312-
if (cassandraPersistentProperty.isCompositePrimaryKey()) {
313-
throw new IllegalArgumentException(
314-
"Cannot use composite primary key directly. Reference a property of the composite primary key");
315-
}
321+
if (property.isCompositePrimaryKey()) {
316322

317-
return cassandraPersistentProperty.getRequiredColumnName();
318-
});
319-
}
323+
BasicCassandraPersistentEntity<?> primaryKeyEntity = mappingContext.getRequiredPersistentEntity(property);
320324

321-
if (column.getColumnName().isPresent()) {
322-
return column.getColumnName().map(CqlIdentifier::fromCql);
325+
primaryKeyEntity.forEach(it -> {
326+
identifiers.add(it.getRequiredColumnName());
327+
});
328+
} else {
329+
identifiers.add(property.getRequiredColumnName());
330+
}
331+
} else if (column.getColumnName().isPresent()) {
332+
identifiers.add(CqlIdentifier.fromCql(column.getColumnName().get()));
333+
} else {
334+
column.getCqlIdentifier().ifPresent(identifiers::add);
323335
}
324336

325-
return column.getCqlIdentifier();
326-
327337
} catch (IllegalStateException cause) {
328338
throw new IllegalArgumentException(cause.getMessage(), cause);
329339
}
340+
341+
return identifiers;
330342
}
331343

332344
Field createPropertyField(@Nullable CassandraPersistentEntity<?> entity, ColumnName key) {

spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraTemplateIntegrationTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,21 @@ void shouldSelectProjectionWithCompositeKey() {
197197
assertThat(loadedEntity.getComment()).isNotNull();
198198
}
199199

200+
@Test // DATACASS-828
201+
void shouldSelectClosedProjectionWithCompositeKey() {
202+
203+
CompositeKey key = new CompositeKey("Walter", "White");
204+
TypeWithCompositeKey user = new TypeWithCompositeKey(key, "comment");
205+
206+
template.insert(user);
207+
208+
WithCompositeKeyProjection loaded = template.query(TypeWithCompositeKey.class).as(WithCompositeKeyProjection.class)
209+
.firstValue();
210+
assertThat(loaded).isNotNull();
211+
212+
assertThat(loaded.getKey()).isEqualTo(key);
213+
}
214+
200215
@Test // DATACASS-343
201216
void shouldSelectOneByQuery() {
202217

@@ -733,6 +748,11 @@ static class TypeWithCompositeKey {
733748
String comment;
734749
}
735750

751+
interface WithCompositeKeyProjection {
752+
753+
CompositeKey getKey();
754+
}
755+
736756
@Data
737757
@PrimaryKeyClass
738758
@AllArgsConstructor

spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/QueryMapperUnitTests.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static org.assertj.core.api.Assertions.*;
1919
import static org.mockito.Matchers.any;
2020
import static org.mockito.Mockito.*;
21+
import static org.springframework.data.domain.Sort.Order.*;
2122

2223
import lombok.AllArgsConstructor;
2324
import lombok.Data;
@@ -328,13 +329,15 @@ void shouldMapSortWithCompositePrimaryKeyClass() {
328329
assertThat(mappedObject).contains(new Order(Direction.ASC, "first_name"));
329330
}
330331

331-
@Test // DATACASS-343
332-
void shouldFailMappingSortByCompositePrimaryKeyClass() {
332+
@Test // DATACASS-828
333+
void allowSortByCompositeKey() {
333334

334335
Sort sort = Sort.by("key");
336+
Query.empty().columns(Columns.from("key"));
335337

336-
assertThatIllegalArgumentException().isThrownBy(
337-
() -> queryMapper.getMappedSort(sort, mappingContext.getRequiredPersistentEntity(TypeWithKeyClass.class)));
338+
Sort mappedSort = queryMapper.getMappedSort(sort,
339+
mappingContext.getRequiredPersistentEntity(TypeWithKeyClass.class));
340+
assertThat(mappedSort).isEqualTo(Sort.by(asc("first_name"), asc("lastname")));
338341
}
339342

340343
@Test // DATACASS-343

0 commit comments

Comments
 (0)