Introduce SqlSort.

SqlSort allows the specification of unsafe order-by-expressions.
Order-by-expressions that are not declared unsafe are only accepted when they
either match a property or consist only of digits, letters, underscore, dot, or parentheses.

 Closes #1507
This commit is contained in:
Jens Schauder
2023-04-28 11:49:59 +02:00
parent 15980174af
commit a26557e76b
7 changed files with 517 additions and 10 deletions

View File

@@ -38,6 +38,7 @@ import org.springframework.data.relational.core.query.CriteriaDefinition;
import org.springframework.data.relational.core.query.CriteriaDefinition.Comparator;
import org.springframework.data.relational.core.query.ValueFunction;
import org.springframework.data.relational.core.sql.*;
import org.springframework.data.relational.domain.SqlSort;
import org.springframework.data.util.Pair;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
@@ -56,6 +57,7 @@ import org.springframework.util.ClassUtils;
* @author Mark Paluch
* @author Roman Chigvintsev
* @author Manousos Mathioudakis
* @author Jens Schauder
*/
public class QueryMapper {
@@ -111,6 +113,8 @@ public class QueryMapper {
for (Sort.Order order : sort) {
SqlSort.validate(order);
Field field = createPropertyField(entity, SqlIdentifier.unquoted(order.getProperty()), this.mappingContext);
mappedOrder.add(
Sort.Order.by(toSql(field.getMappedColumnName())).with(order.getNullHandling()).with(order.getDirection()));
@@ -133,13 +137,22 @@ public class QueryMapper {
for (Sort.Order order : sort) {
Field field = createPropertyField(entity, SqlIdentifier.unquoted(order.getProperty()), this.mappingContext);
OrderByField orderBy = OrderByField.from(table.column(field.getMappedColumnName()))
OrderByField simpleOrderByField = createSimpleOrderByField(table, entity, order);
OrderByField orderBy = simpleOrderByField
.withNullHandling(order.getNullHandling());
mappedOrder.add(order.isAscending() ? orderBy.asc() : orderBy.desc());
}
return mappedOrder;
}
private OrderByField createSimpleOrderByField(Table table, RelationalPersistentEntity<?> entity, Sort.Order order) {
SqlSort.validate(order);
Field field = createPropertyField(entity, SqlIdentifier.unquoted(order.getProperty()), this.mappingContext);
return OrderByField.from(table.column(field.getMappedColumnName()));
}
/**

View File

@@ -20,6 +20,8 @@ import static org.mockito.Mockito.*;
import static org.springframework.data.domain.Sort.Order.*;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.junit.jupiter.api.Test;
import org.springframework.core.convert.converter.Converter;
@@ -35,6 +37,7 @@ import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.sql.Expression;
import org.springframework.data.relational.core.sql.Functions;
import org.springframework.data.relational.core.sql.OrderByField;
import org.springframework.data.relational.core.sql.Table;
import org.springframework.r2dbc.core.Parameter;
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
@@ -47,6 +50,7 @@ import org.testcontainers.shaded.com.fasterxml.jackson.databind.node.TextNode;
*
* @author Mark Paluch
* @author Mingyuan Wu
* @author Jens Schauder
*/
class QueryMapperUnitTests {
@@ -423,6 +427,42 @@ class QueryMapperUnitTests {
assertThat(mapped.getOrderFor("alternative_name")).isEqualTo(desc("alternative_name"));
}
@Test // GH-1507
public void shouldMapSortWithUnknownField() {
Sort sort = Sort.by(desc("unknownField"));
List<OrderByField> fields = mapper.getMappedSort(Table.create("tbl"), sort,
mapper.getMappingContext().getRequiredPersistentEntity(Person.class));
assertThat(fields) //
.extracting(Objects::toString) //
.containsExactly("tbl.unknownField DESC");
}
@Test // GH-1507
public void shouldMapSortWithAllowedSpecialCharacters() {
Sort sort = Sort.by(desc("x(._)x"));
List<OrderByField> fields = mapper.getMappedSort(Table.create("tbl"), sort,
mapper.getMappingContext().getRequiredPersistentEntity(Person.class));
assertThat(fields) //
.extracting(Objects::toString) //
.containsExactly("tbl.x(._)x DESC");
}
@Test // GH-1507
public void shouldNotMapSortWithIllegalExpression() {
Sort sort = Sort.by(desc("unknown Field"));
assertThatThrownBy(() -> mapper.getMappedSort(Table.create("tbl"), sort,
mapper.getMappingContext().getRequiredPersistentEntity(Person.class))).isInstanceOf(IllegalArgumentException.class);
}
@Test // gh-369
void mapQueryForPropertyPathInPrimitiveShouldFallBackToColumnName() {