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

@@ -41,6 +41,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.jdbc.core.namedparam.MapSqlParameterSource;
@@ -92,13 +93,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

@@ -62,6 +62,7 @@ import org.springframework.data.jdbc.testing.AssumeFeatureTestExecutionListener;
import org.springframework.data.jdbc.testing.EnabledOnFeature;
import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.jdbc.testing.TestDatabaseFeatures;
import org.springframework.data.mapping.context.InvalidPersistentPropertyPath;
import org.springframework.data.relational.core.conversion.DbActionExecutionException;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.InsertOnlyProperty;
@@ -275,6 +276,17 @@ class JdbcAggregateTemplateIntegrationTests {
.containsExactly("Frozen", "Star", null);
}
@Test //
@EnabledOnFeature({ SUPPORTS_QUOTED_IDS})
void findByNonPropertySortFails() {
assertThatThrownBy(() -> template.findAll(LegoSet.class,
Sort.by("somethingNotExistant"))).isInstanceOf(InvalidPersistentPropertyPath.class);
}
@Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadManyEntitiesByIdWithReferencedEntity() {

View File

@@ -21,13 +21,12 @@ 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.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.data.domain.Sort;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.QueryMapper;
import org.springframework.data.jdbc.core.convert.RelationResolver;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.relational.core.dialect.PostgresDialect;
import org.springframework.data.relational.core.mapping.Column;
@@ -37,12 +36,14 @@ 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.data.relational.domain.SqlSort;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
/**
* Unit tests for {@link QueryMapper}.
*
* @author Mark Paluch
* @author Jens Schauder
*/
public class QueryMapperUnitTests {
@@ -376,8 +377,60 @@ public class QueryMapperUnitTests {
List<OrderByField> fields = mapper.getMappedSort(Table.create("tbl"), sort,
context.getRequiredPersistentEntity(Person.class));
assertThat(fields).hasSize(1);
assertThat(fields.get(0)).hasToString("tbl.\"another_name\" DESC");
assertThat(fields) //
.extracting(Objects::toString) //
.containsExactly("tbl.\"another_name\" DESC");
}
@Test // GH-1507
public void shouldMapSortWithUnknownField() {
Sort sort = Sort.by(desc("unknownField"));
List<OrderByField> fields = mapper.getMappedSort(Table.create("tbl"), sort,
context.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,
context.getRequiredPersistentEntity(Person.class));
assertThat(fields) //
.extracting(Objects::toString) //
.containsExactly("tbl.x(._)x DESC");
}
@ParameterizedTest // GH-1507
@ValueSource(strings = { " ", ";", "--" })
public void shouldNotMapSortWithIllegalExpression(String input) {
Sort sort = Sort.by(desc("unknown" + input + "Field"));
assertThatThrownBy(
() -> mapper.getMappedSort(Table.create("tbl"), sort, context.getRequiredPersistentEntity(Person.class)))
.isInstanceOf(IllegalArgumentException.class);
}
@Test // GH-1507
public void shouldMapSortWithUnsafeExpression() {
String unsafeExpression = "arbitrary expression that may include evil stuff like ; & --";
Sort sort = SqlSort.unsafe(unsafeExpression);
List<OrderByField> fields = mapper.getMappedSort(Table.create("tbl"), sort,
context.getRequiredPersistentEntity(Person.class));
assertThat(fields) //
.extracting(Objects::toString) //
.containsExactly("tbl." + unsafeExpression + " ASC");
}
private Condition map(Criteria criteria) {