From 09ff2d63af7bf1364e229f7ae0ddc5b239043902 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 23 Sep 2024 14:33:33 +0200 Subject: [PATCH 1/3] 1813-improve-error-message - Prepare branch --- pom.xml | 2 +- spring-data-jdbc-distribution/pom.xml | 2 +- spring-data-jdbc/pom.xml | 4 ++-- spring-data-r2dbc/pom.xml | 4 ++-- spring-data-relational/pom.xml | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 335f5969b87..e933d3ac2b6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 3.4.0-SNAPSHOT + 3.4.0-1813-improve-error-message-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index 84eeb178e07..1ea4218b338 100644 --- a/spring-data-jdbc-distribution/pom.xml +++ b/spring-data-jdbc-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 3.4.0-SNAPSHOT + 3.4.0-1813-improve-error-message-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index 266af71b96c..f902f562267 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-jdbc - 3.4.0-SNAPSHOT + 3.4.0-1813-improve-error-message-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.4.0-SNAPSHOT + 3.4.0-1813-improve-error-message-SNAPSHOT diff --git a/spring-data-r2dbc/pom.xml b/spring-data-r2dbc/pom.xml index e335b56501b..648460352f1 100644 --- a/spring-data-r2dbc/pom.xml +++ b/spring-data-r2dbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-r2dbc - 3.4.0-SNAPSHOT + 3.4.0-1813-improve-error-message-SNAPSHOT Spring Data R2DBC Spring Data module for R2DBC @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.4.0-SNAPSHOT + 3.4.0-1813-improve-error-message-SNAPSHOT diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 6be1155d38e..eabbf68bc2b 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-relational - 3.4.0-SNAPSHOT + 3.4.0-1813-improve-error-message-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 3.4.0-SNAPSHOT + 3.4.0-1813-improve-error-message-SNAPSHOT From 89093cf189e5f40b97e4dcf9cc6d0ab31cd91935 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 23 Sep 2024 14:38:29 +0200 Subject: [PATCH 2/3] Polishing. See #1813 --- .../ProjectingRepositoryIntegrationTests.java | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/ProjectingRepositoryIntegrationTests.java b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/ProjectingRepositoryIntegrationTests.java index d55b34023a2..6d7b3860652 100644 --- a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/ProjectingRepositoryIntegrationTests.java +++ b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/ProjectingRepositoryIntegrationTests.java @@ -27,7 +27,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -51,14 +50,12 @@ @ExtendWith(SpringExtension.class) public class ProjectingRepositoryIntegrationTests { - @Autowired - private ImmutableObjectRepository repository; + @Autowired private ImmutableObjectRepository repository; private JdbcTemplate jdbc; @Configuration - @EnableR2dbcRepositories( - includeFilters = @ComponentScan.Filter(value = ImmutableObjectRepository.class, type = FilterType.ASSIGNABLE_TYPE), - considerNestedRepositories = true) + @EnableR2dbcRepositories(includeFilters = @ComponentScan.Filter(value = ImmutableObjectRepository.class, + type = FilterType.ASSIGNABLE_TYPE), considerNestedRepositories = true) static class TestConfiguration extends AbstractR2dbcConfiguration { @Override public ConnectionFactory connectionFactory() { @@ -74,9 +71,7 @@ void before() { try { this.jdbc.execute("DROP TABLE immutable_non_null"); - } - catch (DataAccessException e) { - } + } catch (DataAccessException e) {} this.jdbc.execute("CREATE TABLE immutable_non_null (id serial PRIMARY KEY, name varchar(255), email varchar(255))"); this.jdbc.execute("INSERT INTO immutable_non_null VALUES (42, 'Walter', 'heisenberg@the-white-family.com')"); @@ -100,8 +95,7 @@ protected ConnectionFactory createConnectionFactory() { return H2TestSupport.createConnectionFactory(); } - @Test - // GH-1687 + @Test // GH-1687 void shouldApplyProjectionDirectly() { repository.findProjectionByEmail("heisenberg@the-white-family.com") // @@ -111,8 +105,7 @@ void shouldApplyProjectionDirectly() { }).verifyComplete(); } - @Test - // GH-1687 + @Test // GH-1687 void shouldApplyEntityQueryProjectionDirectly() { repository.findAllByEmail("heisenberg@the-white-family.com") // @@ -134,8 +127,7 @@ interface ImmutableObjectRepository extends ReactiveCrudRepository Date: Tue, 24 Sep 2024 08:52:29 +0200 Subject: [PATCH 3/3] Provide more expressive exception for projections without properties. Closes #1813 --- .../repository/query/JdbcQueryCreator.java | 13 ++++++- .../JdbcRepositoryIntegrationTests.java | 36 +++++++++++++------ .../core/sql/EmptySelectListException.java | 29 +++++++++++++++ .../relational/core/sql/SelectValidator.java | 2 +- 4 files changed, 67 insertions(+), 13 deletions(-) create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/EmptySelectListException.java diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java index 8351db58b7b..141b5c88bc7 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java @@ -33,6 +33,7 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.query.Criteria; import org.springframework.data.relational.core.sql.Column; +import org.springframework.data.relational.core.sql.EmptySelectListException; import org.springframework.data.relational.core.sql.Expression; import org.springframework.data.relational.core.sql.Expressions; import org.springframework.data.relational.core.sql.Functions; @@ -177,13 +178,23 @@ protected ParametrizedQuery complete(@Nullable Criteria criteria, Sort sort) { completedBuildSelect = selectOrderBuilder.lock(this.lockMode.get().value()); } - Select select = completedBuildSelect.build(); + Select select = getSelect(completedBuildSelect); String sql = SqlRenderer.create(renderContextFactory.createRenderContext()).render(select); return new ParametrizedQuery(sql, parameterSource); } + private Select getSelect(SelectBuilder.BuildSelect completedBuildSelect) { + + try { + return completedBuildSelect.build(); + } catch (EmptySelectListException cause) { + throw new IllegalStateException( + returnedType.getReturnedType().getName() + " does not define any properties to select", cause); + } + } + SelectBuilder.SelectOrdered applyOrderBy(Sort sort, RelationalPersistentEntity entity, Table table, SelectBuilder.SelectOrdered selectOrdered) { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index 008f9232083..75cb7e1e09d 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -41,7 +41,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.context.ApplicationListener; @@ -51,16 +50,7 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.data.annotation.Id; -import org.springframework.data.domain.Example; -import org.springframework.data.domain.ExampleMatcher; -import org.springframework.data.domain.Limit; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.ScrollPosition; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.Sort; -import org.springframework.data.domain.Window; +import org.springframework.data.domain.*; import org.springframework.data.jdbc.core.mapping.AggregateReference; import org.springframework.data.jdbc.repository.query.Modifying; import org.springframework.data.jdbc.repository.query.Query; @@ -572,6 +562,24 @@ public void partTreeQueryProjectionShouldReturnProjectedEntities() { assertThat(result.get(0).getName()).isEqualTo("Entity Name"); } + @Test // GH-1813 + public void partTreeQueryDynamicProjectionShouldReturnProjectedEntities() { + + repository.save(createDummyEntity()); + + List result = repository.findDynamicProjectedByName("Entity Name", DummyProjection.class); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getName()).isEqualTo("Entity Name"); + } + + @Test // GH-1813 + public void partTreeQueryDynamicProjectionWithBrokenProjectionShouldError() { + + assertThatThrownBy(() -> repository.findDynamicProjectedByName("Entity Name", BrokenProjection.class)) + .hasMessageContaining("BrokenProjection does not define any properties to select"); + } + @Test // GH-971 public void pageQueryProjectionShouldReturnProjectedEntities() { @@ -1428,6 +1436,8 @@ interface DummyEntityRepository extends CrudRepository, Query List findProjectedByName(String name); + List findDynamicProjectedByName(String name, Class projection); + @Query(value = "SELECT * FROM DUMMY_ENTITY", rowMapperClass = CustomRowMapper.class) List findAllWithCustomMapper(); @@ -1941,6 +1951,10 @@ interface DummyProjection { String getName(); } + interface BrokenProjection { + String name(); + } + static final class DtoProjection { private final String name; diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/EmptySelectListException.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/EmptySelectListException.java new file mode 100644 index 00000000000..2b5abf9a16b --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/EmptySelectListException.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 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.relational.core.sql; + +/** + * Exception denoting the absence of a select list from a query. + * + * @author Jens Schauder + * @since 3.4 + */ +public class EmptySelectListException extends IllegalStateException { + public EmptySelectListException() { + super("SELECT does not declare a select list"); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java index cdaef37344c..89c09c85eeb 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java @@ -54,7 +54,7 @@ private void doValidate(Select select) { select.visit(this); if (selectFieldCount == 0) { - throw new IllegalStateException("SELECT does not declare a select list"); + throw new EmptySelectListException(); } for (TableLike table : requiredBySelect) {