Skip to content

Commit e773c05

Browse files
authored
Merge pull request #550 from jeffgbutler/having-support
Add "having" Support to Select Statements
2 parents 58e02cb + 2e90ca1 commit e773c05

28 files changed

+903
-271
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Copyright 2016-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql.common;
17+
18+
import java.util.ArrayList;
19+
import java.util.Arrays;
20+
import java.util.List;
21+
22+
import org.jetbrains.annotations.NotNull;
23+
import org.mybatis.dynamic.sql.AndOrCriteriaGroup;
24+
import org.mybatis.dynamic.sql.BindableColumn;
25+
import org.mybatis.dynamic.sql.ColumnAndConditionCriterion;
26+
import org.mybatis.dynamic.sql.CriteriaGroup;
27+
import org.mybatis.dynamic.sql.ExistsCriterion;
28+
import org.mybatis.dynamic.sql.ExistsPredicate;
29+
import org.mybatis.dynamic.sql.SqlCriterion;
30+
import org.mybatis.dynamic.sql.VisitableCondition;
31+
32+
public abstract class AbstractBooleanExpressionDSL<T extends AbstractBooleanExpressionDSL<T>> {
33+
protected SqlCriterion initialCriterion; // WARNING - may be null!
34+
protected final List<AndOrCriteriaGroup> subCriteria = new ArrayList<>();
35+
36+
@NotNull
37+
public <S> T and(BindableColumn<S> column, VisitableCondition<S> condition,
38+
AndOrCriteriaGroup... subCriteria) {
39+
return and(column, condition, Arrays.asList(subCriteria));
40+
}
41+
42+
@NotNull
43+
public <S> T and(BindableColumn<S> column, VisitableCondition<S> condition,
44+
List<AndOrCriteriaGroup> subCriteria) {
45+
addSubCriteria("and", buildCriterion(column, condition), subCriteria); //$NON-NLS-1$
46+
return getThis();
47+
}
48+
49+
@NotNull
50+
public T and(ExistsPredicate existsPredicate, AndOrCriteriaGroup... subCriteria) {
51+
return and(existsPredicate, Arrays.asList(subCriteria));
52+
}
53+
54+
@NotNull
55+
public T and(ExistsPredicate existsPredicate, List<AndOrCriteriaGroup> subCriteria) {
56+
addSubCriteria("and", buildCriterion(existsPredicate), subCriteria); //$NON-NLS-1$
57+
return getThis();
58+
}
59+
60+
@NotNull
61+
public T and(SqlCriterion initialCriterion, AndOrCriteriaGroup... subCriteria) {
62+
return and(initialCriterion, Arrays.asList(subCriteria));
63+
}
64+
65+
@NotNull
66+
public T and(SqlCriterion initialCriterion, List<AndOrCriteriaGroup> subCriteria) {
67+
addSubCriteria("and", buildCriterion(initialCriterion), subCriteria); //$NON-NLS-1$
68+
return getThis();
69+
}
70+
71+
@NotNull
72+
public T and(List<AndOrCriteriaGroup> criteria) {
73+
addSubCriteria("and", criteria); //$NON-NLS-1$
74+
return getThis();
75+
}
76+
77+
@NotNull
78+
public <S> T or(BindableColumn<S> column, VisitableCondition<S> condition,
79+
AndOrCriteriaGroup... subCriteria) {
80+
return or(column, condition, Arrays.asList(subCriteria));
81+
}
82+
83+
@NotNull
84+
public <S> T or(BindableColumn<S> column, VisitableCondition<S> condition,
85+
List<AndOrCriteriaGroup> subCriteria) {
86+
addSubCriteria("or", buildCriterion(column, condition), subCriteria); //$NON-NLS-1$
87+
return getThis();
88+
}
89+
90+
@NotNull
91+
public T or(ExistsPredicate existsPredicate, AndOrCriteriaGroup... subCriteria) {
92+
return or(existsPredicate, Arrays.asList(subCriteria));
93+
}
94+
95+
@NotNull
96+
public T or(ExistsPredicate existsPredicate, List<AndOrCriteriaGroup> subCriteria) {
97+
addSubCriteria("or", buildCriterion(existsPredicate), subCriteria); //$NON-NLS-1$
98+
return getThis();
99+
}
100+
101+
@NotNull
102+
public T or(SqlCriterion initialCriterion, AndOrCriteriaGroup... subCriteria) {
103+
return or(initialCriterion, Arrays.asList(subCriteria));
104+
}
105+
106+
@NotNull
107+
public T or(SqlCriterion initialCriterion, List<AndOrCriteriaGroup> subCriteria) {
108+
addSubCriteria("or", buildCriterion(initialCriterion), subCriteria); //$NON-NLS-1$
109+
return getThis();
110+
}
111+
112+
@NotNull
113+
public T or(List<AndOrCriteriaGroup> criteria) {
114+
addSubCriteria("or", criteria); //$NON-NLS-1$
115+
return getThis();
116+
}
117+
118+
private <R> SqlCriterion buildCriterion(BindableColumn<R> column, VisitableCondition<R> condition) {
119+
return ColumnAndConditionCriterion.withColumn(column).withCondition(condition).build();
120+
}
121+
122+
private SqlCriterion buildCriterion(ExistsPredicate existsPredicate) {
123+
return new ExistsCriterion.Builder().withExistsPredicate(existsPredicate).build();
124+
}
125+
126+
private SqlCriterion buildCriterion(SqlCriterion initialCriterion) {
127+
return new CriteriaGroup.Builder().withInitialCriterion(initialCriterion).build();
128+
}
129+
130+
private void addSubCriteria(String connector, SqlCriterion initialCriterion,
131+
List<AndOrCriteriaGroup> subCriteria) {
132+
this.subCriteria.add(new AndOrCriteriaGroup.Builder()
133+
.withInitialCriterion(initialCriterion)
134+
.withConnector(connector)
135+
.withSubCriteria(subCriteria)
136+
.build());
137+
}
138+
139+
private void addSubCriteria(String connector, List<AndOrCriteriaGroup> criteria) {
140+
this.subCriteria.add(new AndOrCriteriaGroup.Builder()
141+
.withConnector(connector)
142+
.withSubCriteria(criteria)
143+
.build());
144+
}
145+
146+
protected abstract T getThis();
147+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2016-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql.common;
17+
18+
import java.util.ArrayList;
19+
import java.util.Collections;
20+
import java.util.List;
21+
import java.util.Optional;
22+
23+
import org.mybatis.dynamic.sql.AndOrCriteriaGroup;
24+
import org.mybatis.dynamic.sql.SqlCriterion;
25+
26+
public abstract class AbstractBooleanExpressionModel {
27+
private final SqlCriterion initialCriterion;
28+
private final List<AndOrCriteriaGroup> subCriteria = new ArrayList<>();
29+
30+
protected AbstractBooleanExpressionModel(SqlCriterion initialCriterion, List<AndOrCriteriaGroup> subCriteria) {
31+
this.initialCriterion = initialCriterion;
32+
this.subCriteria.addAll(subCriteria);
33+
}
34+
35+
public Optional<SqlCriterion> initialCriterion() {
36+
return Optional.ofNullable(initialCriterion);
37+
}
38+
39+
public List<AndOrCriteriaGroup> subCriteria() {
40+
return Collections.unmodifiableList(subCriteria);
41+
}
42+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright 2016-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql.common;
17+
18+
import static org.mybatis.dynamic.sql.util.StringUtilities.spaceAfter;
19+
20+
import java.util.Objects;
21+
import java.util.Optional;
22+
import java.util.concurrent.atomic.AtomicInteger;
23+
import java.util.stream.Collectors;
24+
25+
import org.mybatis.dynamic.sql.SqlCriterion;
26+
import org.mybatis.dynamic.sql.render.RenderingStrategy;
27+
import org.mybatis.dynamic.sql.render.TableAliasCalculator;
28+
import org.mybatis.dynamic.sql.util.FragmentAndParameters;
29+
import org.mybatis.dynamic.sql.util.FragmentCollector;
30+
import org.mybatis.dynamic.sql.where.render.CriterionRenderer;
31+
import org.mybatis.dynamic.sql.where.render.RenderedCriterion;
32+
33+
public abstract class AbstractBooleanExpressionRenderer<M extends AbstractBooleanExpressionModel> {
34+
protected final M model;
35+
private final String prefix;
36+
private final CriterionRenderer criterionRenderer;
37+
38+
protected AbstractBooleanExpressionRenderer(String prefix, AbstractBuilder<M, ?> builder) {
39+
model = Objects.requireNonNull(builder.model);
40+
this.prefix = Objects.requireNonNull(prefix);
41+
42+
criterionRenderer = new CriterionRenderer.Builder()
43+
.withSequence(builder.sequence)
44+
.withRenderingStrategy(builder.renderingStrategy)
45+
.withTableAliasCalculator(builder.tableAliasCalculator)
46+
.withParameterName(builder.parameterName)
47+
.build();
48+
}
49+
50+
public Optional<FragmentAndParameters> render() {
51+
return model.initialCriterion()
52+
.map(this::renderWithInitialCriterion)
53+
.orElseGet(this::renderWithoutInitialCriterion)
54+
.map(rc -> FragmentAndParameters.withFragment(rc.fragmentAndParameters().fragment())
55+
.withParameters(rc.fragmentAndParameters().parameters())
56+
.build()
57+
);
58+
}
59+
60+
private Optional<RenderedCriterion> renderWithInitialCriterion(SqlCriterion initialCriterion) {
61+
return criterionRenderer.render(initialCriterion, model.subCriteria(), this::calculateClause);
62+
}
63+
64+
private Optional<RenderedCriterion> renderWithoutInitialCriterion() {
65+
return criterionRenderer.render(model.subCriteria(), this::calculateClause);
66+
}
67+
68+
private String calculateClause(FragmentCollector collector) {
69+
if (collector.hasMultipleFragments()) {
70+
return collector.fragments()
71+
.collect(Collectors.joining(" ", spaceAfter(prefix), "")); //$NON-NLS-1$ //$NON-NLS-2$
72+
} else {
73+
return collector.firstFragment()
74+
.map(this::stripEnclosingParenthesesIfPresent)
75+
.map(this::addPrefix)
76+
.orElse(""); //$NON-NLS-1$
77+
}
78+
}
79+
80+
private String stripEnclosingParenthesesIfPresent(String fragment) {
81+
// The fragment will have surrounding open/close parentheses if there is more than one rendered condition.
82+
// Since there is only a single fragment, we don't need these in the final rendered clause
83+
if (fragment.startsWith("(") && fragment.endsWith(")")) { //$NON-NLS-1$ //$NON-NLS-2$
84+
return fragment.substring(1, fragment.length() - 1);
85+
} else {
86+
return fragment;
87+
}
88+
}
89+
90+
private String addPrefix(String fragment) {
91+
return spaceAfter(prefix) + fragment;
92+
}
93+
94+
public abstract static class AbstractBuilder<M, B extends AbstractBuilder<M, B>> {
95+
private final M model;
96+
private RenderingStrategy renderingStrategy;
97+
private TableAliasCalculator tableAliasCalculator;
98+
private AtomicInteger sequence;
99+
private String parameterName;
100+
101+
protected AbstractBuilder(M model) {
102+
this.model = model;
103+
}
104+
105+
public B withRenderingStrategy(RenderingStrategy renderingStrategy) {
106+
this.renderingStrategy = renderingStrategy;
107+
return getThis();
108+
}
109+
110+
public B withTableAliasCalculator(TableAliasCalculator tableAliasCalculator) {
111+
this.tableAliasCalculator = tableAliasCalculator;
112+
return getThis();
113+
}
114+
115+
public B withSequence(AtomicInteger sequence) {
116+
this.sequence = sequence;
117+
return getThis();
118+
}
119+
120+
public B withParameterName(String parameterName) {
121+
this.parameterName = parameterName;
122+
return getThis();
123+
}
124+
125+
protected abstract B getThis();
126+
}
127+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2016-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql.select;
17+
18+
import java.util.List;
19+
20+
import org.mybatis.dynamic.sql.AndOrCriteriaGroup;
21+
import org.mybatis.dynamic.sql.SqlCriterion;
22+
import org.mybatis.dynamic.sql.common.AbstractBooleanExpressionModel;
23+
24+
public class HavingModel extends AbstractBooleanExpressionModel {
25+
public HavingModel(SqlCriterion initialCriterion, List<AndOrCriteriaGroup> subCriteria) {
26+
super(initialCriterion, subCriteria);
27+
}
28+
}

0 commit comments

Comments
 (0)