diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Condition.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Condition.java index 06f713e72..7934bb471 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Condition.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Condition.java @@ -48,9 +48,19 @@ public interface Condition extends Segment { /** * Creates a {@link Condition} that negates this {@link Condition}. * - * @return the negated {@link Condition}. + * @return the negated {@link Condition}. */ default Condition not() { return new Not(this); } + + /** + * Wraps a {@link Condition} into a condition group that groups the nested {@link Condition} using parentheses. + * + * @return the grouped {@link Condition}. + * @since 2.0 + */ + default Condition group() { + return new ConditionGroup(this); + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/ConditionGroup.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/ConditionGroup.java new file mode 100644 index 000000000..18d9bd4d3 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/ConditionGroup.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; + +/** + * Condition group wrapping a nested {@link Condition} with parentheses. + * + * @author Mark Paluch + * @since 2.0 + */ +public class ConditionGroup extends MultipleCondition implements Condition { + + ConditionGroup(Condition condition) { + super("", condition); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "(" + super.toString() + ")"; + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ConditionGroupVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ConditionGroupVisitor.java new file mode 100644 index 000000000..7521b6a7b --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ConditionGroupVisitor.java @@ -0,0 +1,77 @@ +/* + * 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.Condition; +import org.springframework.data.relational.core.sql.ConditionGroup; +import org.springframework.data.relational.core.sql.Visitable; +import org.springframework.lang.Nullable; + +/** + * Renderer for {@link ConditionGroup}. Uses a {@link RenderTarget} to call back for render results. + * + * @author Mark Paluch + * @since 2.0 + */ +class ConditionGroupVisitor extends TypedSubtreeVisitor { + + private final RenderContext context; + private final RenderTarget target; + + private @Nullable ConditionVisitor conditionVisitor; + + ConditionGroupVisitor(RenderContext context, RenderTarget target) { + this.context = context; + this.target = target; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#enterNested(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + Delegation enterNested(Visitable segment) { + + DelegatingVisitor visitor = getDelegation(segment); + + return visitor != null ? Delegation.delegateTo(visitor) : Delegation.retain(); + } + + @Nullable + private DelegatingVisitor getDelegation(Visitable segment) { + + if (segment instanceof Condition) { + return conditionVisitor = new ConditionVisitor(context); + } + + return null; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#leaveNested(org.springframework.data.relational.core.sql.Visitable) + */ + @Override + Delegation leaveNested(Visitable segment) { + + if (conditionVisitor != null) { + target.onRendered("(" + conditionVisitor.getRenderedPart() + ")"); + conditionVisitor = null; + } + + return super.leaveNested(segment); + } +} 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 714bccc6e..7a85706e9 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 @@ -18,6 +18,7 @@ package org.springframework.data.relational.core.sql.render; import org.springframework.data.relational.core.sql.AndCondition; import org.springframework.data.relational.core.sql.Comparison; import org.springframework.data.relational.core.sql.Condition; +import org.springframework.data.relational.core.sql.ConditionGroup; import org.springframework.data.relational.core.sql.In; import org.springframework.data.relational.core.sql.IsNull; import org.springframework.data.relational.core.sql.Like; @@ -41,7 +42,7 @@ import org.springframework.lang.Nullable; class ConditionVisitor extends TypedSubtreeVisitor implements PartRenderer { private final RenderContext context; - private StringBuilder builder = new StringBuilder(); + private final StringBuilder builder = new StringBuilder(); ConditionVisitor(RenderContext context) { this.context = context; @@ -86,6 +87,10 @@ class ConditionVisitor extends TypedSubtreeVisitor implements PartRen return new InVisitor(context, builder::append); } + if (segment instanceof ConditionGroup) { + return new ConditionGroupVisitor(context, builder::append); + } + return null; } 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 b64700d60..fc5b2f5cf 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 @@ -37,12 +37,39 @@ public class ConditionRendererUnitTests { @Test // DATAJDBC-309 public void shouldRenderEquals() { - String sql = SqlRenderer - .toString(StatementBuilder.select(left).from(table).where(left.isEqualTo(right)).build()); + String sql = SqlRenderer.toString(StatementBuilder.select(left).from(table).where(left.isEqualTo(right)).build()); assertThat(sql).endsWith("WHERE my_table.left = my_table.right"); } + @Test // DATAJDBC-490 + public void shouldRenderEqualsGroup() { + + String sql = SqlRenderer + .toString(StatementBuilder.select(left).from(table).where(left.isEqualTo(right).group()).build()); + + assertThat(sql).endsWith("WHERE (my_table.left = my_table.right)"); + } + + @Test // DATAJDBC-490 + public void shouldRenderAndGroup() { + + String sql = SqlRenderer.toString(StatementBuilder.select(left).from(table) + .where(left.isEqualTo(right).and(left.isGreater(right)).group()).build()); + + assertThat(sql).endsWith("WHERE (my_table.left = my_table.right AND my_table.left > my_table.right)"); + } + + @Test // DATAJDBC-490 + public void shouldRenderAndGroupOr() { + + String sql = SqlRenderer.toString(StatementBuilder.select(left).from(table) + .where(left.isEqualTo(right).and(left.isGreater(right)).group().or(left.like(right))).build()); + + assertThat(sql).endsWith( + "WHERE (my_table.left = my_table.right AND my_table.left > my_table.right) OR my_table.left LIKE my_table.right"); + } + @Test // DATAJDBC-309 public void shouldRenderNotEquals() { @@ -76,8 +103,7 @@ public class ConditionRendererUnitTests { @Test // DATAJDBC-309 public void shouldRenderIsGreater() { - String sql = SqlRenderer - .toString(StatementBuilder.select(left).from(table).where(left.isGreater(right)).build()); + String sql = SqlRenderer.toString(StatementBuilder.select(left).from(table).where(left.isGreater(right)).build()); assertThat(sql).endsWith("WHERE my_table.left > my_table.right"); }