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 5e33271c8..28d04403d 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 @@ -18,7 +18,6 @@ package org.springframework.data.jdbc.repository; import static java.util.Arrays.*; import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.SoftAssertions.*; -import static org.springframework.data.jdbc.testing.TestDatabaseFeatures.Feature.*; import static org.springframework.test.context.TestExecutionListeners.MergeMode.*; import lombok.Data; @@ -44,7 +43,6 @@ import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.repository.query.Query; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.jdbc.testing.AssumeFeatureRule; -import org.springframework.data.jdbc.testing.EnabledOnFeature; import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.relational.core.mapping.event.AbstractRelationalEvent; import org.springframework.data.relational.core.mapping.event.AfterLoadEvent; @@ -339,6 +337,38 @@ public class JdbcRepositoryIntegrationTests { }); } + @Test // DATAJDBC-604 + public void existsInWorksAsExpected() { + + DummyEntity dummy = repository.save(createDummyEntity()); + + assertSoftly(softly -> { + + softly.assertThat(repository.existsByNameIn(dummy.getName())) // + .describedAs("Positive") // + .isTrue(); + softly.assertThat(repository.existsByNameIn()) // + .describedAs("Negative") // + .isFalse(); + }); + } + + @Test // DATAJDBC-604 + public void existsNotInWorksAsExpected() { + + DummyEntity dummy = repository.save(createDummyEntity()); + + assertSoftly(softly -> { + + softly.assertThat(repository.existsByNameNotIn(dummy.getName())) // + .describedAs("Positive") // + .isFalse(); + softly.assertThat(repository.existsByNameNotIn()) // + .describedAs("Negative") // + .isTrue(); + }); + } + @Test // DATAJDBC-534 public void countByQueryDerivation() { @@ -370,6 +400,10 @@ public class JdbcRepositoryIntegrationTests { @Query("SELECT id_Prop from dummy_entity where id_Prop = :id") DummyEntity withMissingColumn(@Param("id") Long id); + boolean existsByNameIn(String... names); + + boolean existsByNameNotIn(String... names); + boolean existsByName(String name); int countByName(String name); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/FalseCondition.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/FalseCondition.java new file mode 100644 index 000000000..4694f4026 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/FalseCondition.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 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; + +/** + * Simple condition that evaluates to SQL {@code FALSE}. + * + * @author Mark Paluch + * @since 2.1 + */ +public class FalseCondition implements Condition { + + public static final FalseCondition INSTANCE = new FalseCondition(); + + private FalseCondition() {} + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "1 = 0"; + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/In.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/In.java index 4c41c6666..c9a11f3ab 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/In.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/In.java @@ -149,7 +149,7 @@ public class In extends AbstractSegment implements Condition { return new In(columnOrExpression, Arrays.asList(expressions), true); } - /* + /* * (non-Javadoc) * @see org.springframework.data.relational.core.sql.Condition#not() */ @@ -158,13 +158,26 @@ public class In extends AbstractSegment implements Condition { return new In(left, expressions, !notIn); } + /** + * @return {@code true} if this condition has at least one expression. + * @since 2.1 + */ + public boolean hasExpressions() { + return !expressions.isEmpty(); + } + /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { - return left + (notIn ? " NOT" : "") + " IN (" + StringUtils.collectionToDelimitedString(expressions, ", ") + ")"; + + if (hasExpressions()) { + return left + (notIn ? " NOT" : "") + " IN (" + StringUtils.collectionToDelimitedString(expressions, ", ") + ")"; + } + + return notIn ? TrueCondition.INSTANCE.toString() : FalseCondition.INSTANCE.toString(); } public boolean isNotIn() { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/TrueCondition.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/TrueCondition.java new file mode 100644 index 000000000..d5831b9e9 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/TrueCondition.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 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; + +/** + * Simple condition that evaluates to SQL {@code TRUE}. + * + * @author Mark Paluch + * @since 2.1 + */ +public class TrueCondition implements Condition { + + public static final TrueCondition INSTANCE = new TrueCondition(); + + private TrueCondition() {} + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "1 = 1"; + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ConditionVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ConditionVisitor.java index 49b1d33b3..b0477ed29 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ConditionVisitor.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ConditionVisitor.java @@ -89,7 +89,12 @@ class ConditionVisitor extends TypedSubtreeVisitor implements PartRen } if (segment instanceof In) { - return new InVisitor(context, builder::append); + + if (((In) segment).hasExpressions()) { + return new InVisitor(context, builder::append); + } else { + return new EmptyInVisitor(context, builder::append); + } } if (segment instanceof NestedCondition) { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/EmptyInVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/EmptyInVisitor.java new file mode 100644 index 000000000..933cb691d --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/EmptyInVisitor.java @@ -0,0 +1,48 @@ +/* + * Copyright 2019-2020 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.render; + +import org.springframework.data.relational.core.sql.FalseCondition; +import org.springframework.data.relational.core.sql.In; +import org.springframework.data.relational.core.sql.TrueCondition; + +/** + * Renderer for empty {@link In}. Uses a {@link RenderTarget} to call back for render results. + * + * @author Mark Paluch + * @since 2.1 + */ +class EmptyInVisitor extends TypedSingleConditionRenderSupport { + + private final RenderTarget target; + + EmptyInVisitor(RenderContext context, RenderTarget target) { + super(context); + this.target = target; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#leaveMatched(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + Delegation leaveMatched(In segment) { + + target.onRendered(segment.isNotIn() ? TrueCondition.INSTANCE.toString() : FalseCondition.INSTANCE.toString()); + + return super.leaveMatched(segment); + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/InTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/InTests.java new file mode 100644 index 000000000..470640d5f --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/InTests.java @@ -0,0 +1,46 @@ +/* +* Copyright 2020 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; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link In}. + * + * @author Mark Paluch + */ +public class InTests { + + @Test // DATAJDBC-604 + void shouldRenderToString() { + + Table table = Table.create("table"); + + assertThat(In.create(table.column("col"), SQL.bindMarker())).hasToString("table.col IN (?)"); + assertThat(In.create(table.column("col"), SQL.bindMarker()).not()).hasToString("table.col NOT IN (?)"); + } + + @Test // DATAJDBC-604 + void shouldRenderEmptyExpressionToString() { + + Table table = Table.create("table"); + + assertThat(In.create(table.column("col"))).hasToString("1 = 0"); + assertThat(In.create(table.column("col")).not()).hasToString("1 = 1"); + } +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ConditionRendererUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ConditionRendererUnitTests.java index 28b7c0bdc..b9be32cdf 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ConditionRendererUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ConditionRendererUnitTests.java @@ -167,6 +167,22 @@ public class ConditionRendererUnitTests { assertThat(sql).endsWith("WHERE my_table.left IN (my_table.right)"); } + @Test // DATAJDBC-604 + public void shouldRenderEmptyIn() { + + String sql = SqlRenderer.toString(StatementBuilder.select(left).from(table).where(left.in()).build()); + + assertThat(sql).endsWith("WHERE 1 = 0"); + } + + @Test // DATAJDBC-604 + public void shouldRenderEmptyNotIn() { + + String sql = SqlRenderer.toString(StatementBuilder.select(left).from(table).where(left.notIn()).build()); + + assertThat(sql).endsWith("WHERE 1 = 1"); + } + @Test // DATAJDBC-309 public void shouldRenderLike() {