Skip to content

Commit 696136c

Browse files
authored
Merge pull request #594 from jeffgbutler/reusable-where-clauses-v2
Major Refactoring of Where/Having Support
2 parents 45e35e7 + 955b519 commit 696136c

33 files changed

+841
-391
lines changed

CHANGELOG.md

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,38 @@ This log will detail notable changes to MyBatis Dynamic SQL. Full details are av
66

77
GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/milestone/12?closed=1](https://github.com/mybatis/mybatis-dynamic-sql/milestone/12?closed=1)
88

9+
### Potentially Breaking Changes
10+
11+
This release includes a major refactoring of the "where" clause support. This is done to support common code for
12+
"having" clauses which is a new feature (see below). Most changes are source code compatible with previous
13+
releases and should be transparent with no impact. Following is a list of some more visible changes...
14+
15+
First, the "where" methods in `SqlBuilder` now return an instance of `WhereDSL.StandaloneWhereFinisher` rather than
16+
`WhereDSL`. This will only impact you if you are using the WhereDSL directly which is a rare use case.
17+
18+
Second, if you are using independent or reusable where clauses you will need to make changes. Previously you might have
19+
coded an independent where clause like this:
20+
21+
```java
22+
private WhereApplier commonWhere = d -> d.where(id, isEqualTo(1)).or(occupation, isNull());
23+
```
24+
25+
Code like this will no longer compile. There are two options for updates. The simplest change to make is to
26+
replace "where" with "and" or "or" in the above code. For example...
27+
28+
```java
29+
private WhereApplier commonWhere = d -> d.and(id, isEqualTo(1)).or(occupation, isNull());
30+
```
31+
32+
This will function as before, but you may think it looks a bit strange because the phrase starts with "and". If you
33+
want this to look more like true SQL, you can write code like this:
34+
35+
```java
36+
private final WhereApplier commonWhere = where(id, isEqualTo(1)).or(occupation, isNull()).toWhereApplier();
37+
```
38+
39+
This uses a `where` method from `SqlBuilder`.
40+
941
### "Having" Clause Support
1042

1143
This release adds support for "having" clauses in select statements. This includes a refactoring of the "where"
@@ -22,10 +54,6 @@ where (a < 2 and b > 3)
2254

2355
The renderer will now remove the open/close parentheses in a case like this.
2456

25-
The "having" support is not as full-featured as the "where" support in that we don't support independent and
26-
reusable having clauses, and we don't support composable having functions. If you have a reasonable use case
27-
for that kind of support, please let us know.
28-
2957
In the Java DSL, a "having" clause can only be coded after a "group by" clause - which is a reasonable restriction
3058
as "having" is only needed if there is a "group by".
3159

@@ -38,7 +66,29 @@ The pull request for this change is ([#550](https://github.com/mybatis/mybatis-d
3866
### Multi-Select Queries
3967

4068
A multi-select query is a special case of a union select statement. The difference is that it allows "order by" and
41-
paging clauses to be applied to the nested queries.
69+
paging clauses to be applied to the nested queries. A multi-select query looks like this:
70+
71+
```java
72+
SelectStatementProvider selectStatement = multiSelect(
73+
select(id.as("A_ID"), firstName, lastName, birthDate, employed, occupation, addressId)
74+
.from(person)
75+
.where(id, isLessThanOrEqualTo(2))
76+
.orderBy(id)
77+
.limit(1)
78+
).unionAll(
79+
select(id.as("A_ID"), firstName, lastName, birthDate, employed, occupation, addressId)
80+
.from(person)
81+
.where(id, isGreaterThanOrEqualTo(4))
82+
.orderBy(id.descending())
83+
.limit(1)
84+
).orderBy(sortColumn("A_ID"))
85+
.fetchFirst(2).rowsOnly()
86+
.build()
87+
.render(RenderingStrategies.MYBATIS3);
88+
```
89+
90+
Notice how both inner queries have `order by` and `limit` phrases, then there is an `order by` phrase
91+
for the entire query.
4292

4393
The pull request for this change is ([#591](https://github.com/mybatis/mybatis-dynamic-sql/pull/591))
4494

src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.mybatis.dynamic.sql.insert.MultiRowInsertDSL;
3131
import org.mybatis.dynamic.sql.select.ColumnSortSpecification;
3232
import org.mybatis.dynamic.sql.select.CountDSL;
33+
import org.mybatis.dynamic.sql.select.HavingDSL;
3334
import org.mybatis.dynamic.sql.select.MultiSelectDSL;
3435
import org.mybatis.dynamic.sql.select.QueryExpressionDSL.FromGatherer;
3536
import org.mybatis.dynamic.sql.select.SelectDSL;
@@ -240,21 +241,30 @@ static UpdateDSL<UpdateModel> update(SqlTable table, String tableAlias) {
240241
return UpdateDSL.update(table, tableAlias);
241242
}
242243

243-
static WhereDSL where() {
244-
return WhereDSL.where();
244+
static WhereDSL.StandaloneWhereFinisher where() {
245+
return new WhereDSL().where();
245246
}
246247

247-
static <T> WhereDSL where(BindableColumn<T> column, VisitableCondition<T> condition,
248-
AndOrCriteriaGroup... subCriteria) {
249-
return WhereDSL.where().where(column, condition, subCriteria);
248+
static <T> WhereDSL.StandaloneWhereFinisher where(BindableColumn<T> column, VisitableCondition<T> condition,
249+
AndOrCriteriaGroup... subCriteria) {
250+
return new WhereDSL().where(column, condition, subCriteria);
250251
}
251252

252-
static WhereDSL where(SqlCriterion initialCriterion, AndOrCriteriaGroup... subCriteria) {
253-
return WhereDSL.where().where(initialCriterion, subCriteria);
253+
static WhereDSL.StandaloneWhereFinisher where(SqlCriterion initialCriterion, AndOrCriteriaGroup... subCriteria) {
254+
return new WhereDSL().where(initialCriterion, subCriteria);
254255
}
255256

256-
static WhereDSL where(ExistsPredicate existsPredicate, AndOrCriteriaGroup... subCriteria) {
257-
return WhereDSL.where().where(existsPredicate, subCriteria);
257+
static WhereDSL.StandaloneWhereFinisher where(ExistsPredicate existsPredicate, AndOrCriteriaGroup... subCriteria) {
258+
return new WhereDSL().where(existsPredicate, subCriteria);
259+
}
260+
261+
static <T> HavingDSL.StandaloneHavingFinisher having(BindableColumn<T> column, VisitableCondition<T> condition,
262+
AndOrCriteriaGroup... subCriteria) {
263+
return new HavingDSL().having(column, condition, subCriteria);
264+
}
265+
266+
static HavingDSL.StandaloneHavingFinisher having(SqlCriterion initialCriterion, AndOrCriteriaGroup... subCriteria) {
267+
return new HavingDSL().having(initialCriterion, subCriteria);
258268
}
259269

260270
// where condition connectors

src/main/java/org/mybatis/dynamic/sql/common/AbstractBooleanExpressionDSL.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,9 @@ private void addSubCriteria(String connector, List<AndOrCriteriaGroup> criteria)
145145
.build());
146146
}
147147

148-
protected void setInitialCriterion(SqlCriterion initialCriterion) {
148+
protected void setInitialCriterion(SqlCriterion initialCriterion, StatementType statementType) {
149149
if (this.initialCriterion != null) {
150-
throw new InvalidSqlException(Messages.getString("ERROR.32")); //$NON-NLS-1$
150+
throw new InvalidSqlException(Messages.getString(statementType.messageNumber())); //$NON-NLS-1$
151151
}
152152

153153
this.initialCriterion = initialCriterion;
@@ -159,4 +159,19 @@ protected SqlCriterion getInitialCriterion() {
159159
}
160160

161161
protected abstract T getThis();
162+
163+
public enum StatementType {
164+
WHERE("ERROR.32"), //$NON-NLS-1$
165+
HAVING("ERROR.31"); //$NON-NLS-1$
166+
167+
private final String messageNumber;
168+
169+
public String messageNumber() {
170+
return messageNumber;
171+
}
172+
173+
StatementType(String messageNumber) {
174+
this.messageNumber = messageNumber;
175+
}
176+
}
162177
}

src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSL.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@
2727
import org.mybatis.dynamic.sql.common.OrderByModel;
2828
import org.mybatis.dynamic.sql.configuration.StatementConfiguration;
2929
import org.mybatis.dynamic.sql.util.Buildable;
30-
import org.mybatis.dynamic.sql.where.AbstractWhereDSL;
31-
import org.mybatis.dynamic.sql.where.AbstractWhereSupport;
30+
import org.mybatis.dynamic.sql.where.AbstractWhereFinisher;
31+
import org.mybatis.dynamic.sql.where.AbstractWhereStarter;
3232
import org.mybatis.dynamic.sql.where.WhereModel;
3333

34-
public class DeleteDSL<R> extends AbstractWhereSupport<DeleteDSL<R>.DeleteWhereBuilder, DeleteDSL<R>>
34+
public class DeleteDSL<R> extends AbstractWhereStarter<DeleteDSL<R>.DeleteWhereBuilder, DeleteDSL<R>>
3535
implements Buildable<R> {
3636

3737
private final Function<DeleteModel, R> adapterFunction;
@@ -109,7 +109,7 @@ public static DeleteDSL<DeleteModel> deleteFrom(SqlTable table, String tableAlia
109109
return deleteFrom(Function.identity(), table, tableAlias);
110110
}
111111

112-
public class DeleteWhereBuilder extends AbstractWhereDSL<DeleteWhereBuilder> implements Buildable<R> {
112+
public class DeleteWhereBuilder extends AbstractWhereFinisher<DeleteWhereBuilder> implements Buildable<R> {
113113

114114
private DeleteWhereBuilder() {
115115
super(statementConfiguration);
@@ -140,7 +140,7 @@ protected DeleteWhereBuilder getThis() {
140140
}
141141

142142
protected WhereModel buildWhereModel() {
143-
return internalBuild();
143+
return buildModel();
144144
}
145145
}
146146
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2016-2023 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 org.mybatis.dynamic.sql.SqlCriterion;
19+
import org.mybatis.dynamic.sql.common.AbstractBooleanExpressionDSL;
20+
21+
public abstract class AbstractHavingFinisher<T extends AbstractHavingFinisher<T>>
22+
extends AbstractBooleanExpressionDSL<T> {
23+
void initialize(SqlCriterion sqlCriterion) {
24+
setInitialCriterion(sqlCriterion, StatementType.HAVING);
25+
}
26+
27+
protected HavingModel buildModel() {
28+
return new HavingModel(getInitialCriterion(), subCriteria);
29+
}
30+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2016-2023 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.Arrays;
19+
import java.util.List;
20+
21+
import org.mybatis.dynamic.sql.AndOrCriteriaGroup;
22+
import org.mybatis.dynamic.sql.BindableColumn;
23+
import org.mybatis.dynamic.sql.ColumnAndConditionCriterion;
24+
import org.mybatis.dynamic.sql.CriteriaGroup;
25+
import org.mybatis.dynamic.sql.SqlCriterion;
26+
import org.mybatis.dynamic.sql.VisitableCondition;
27+
28+
public abstract class AbstractHavingStarter<F extends AbstractHavingFinisher<?>> {
29+
30+
public <T> F having(BindableColumn<T> column, VisitableCondition<T> condition,
31+
AndOrCriteriaGroup... subCriteria) {
32+
return having(column, condition, Arrays.asList(subCriteria));
33+
}
34+
35+
public <T> F having(BindableColumn<T> column, VisitableCondition<T> condition,
36+
List<AndOrCriteriaGroup> subCriteria) {
37+
SqlCriterion sqlCriterion = ColumnAndConditionCriterion.withColumn(column)
38+
.withCondition(condition)
39+
.withSubCriteria(subCriteria)
40+
.build();
41+
42+
return initialize(sqlCriterion);
43+
}
44+
45+
public F having(SqlCriterion initialCriterion, AndOrCriteriaGroup... subCriteria) {
46+
return having(initialCriterion, Arrays.asList(subCriteria));
47+
}
48+
49+
public F having(SqlCriterion initialCriterion, List<AndOrCriteriaGroup> subCriteria) {
50+
SqlCriterion sqlCriterion = new CriteriaGroup.Builder()
51+
.withInitialCriterion(initialCriterion)
52+
.withSubCriteria(subCriteria)
53+
.build();
54+
55+
return initialize(sqlCriterion);
56+
}
57+
58+
protected abstract F having();
59+
60+
public F applyHaving(HavingApplier havingApplier) {
61+
F finisher = having();
62+
havingApplier.accept(finisher);
63+
return finisher;
64+
}
65+
66+
private F initialize(SqlCriterion sqlCriterion) {
67+
F finisher = having();
68+
finisher.initialize(sqlCriterion);
69+
return finisher;
70+
}
71+
}

src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@
3333
import org.mybatis.dynamic.sql.select.join.JoinSpecification;
3434
import org.mybatis.dynamic.sql.select.join.JoinType;
3535
import org.mybatis.dynamic.sql.util.Buildable;
36-
import org.mybatis.dynamic.sql.where.AbstractWhereDSL;
37-
import org.mybatis.dynamic.sql.where.AbstractWhereSupport;
36+
import org.mybatis.dynamic.sql.where.AbstractWhereFinisher;
37+
import org.mybatis.dynamic.sql.where.AbstractWhereStarter;
3838

39-
public abstract class AbstractQueryExpressionDSL<W extends AbstractWhereDSL<?>,
39+
public abstract class AbstractQueryExpressionDSL<W extends AbstractWhereFinisher<?>,
4040
T extends AbstractQueryExpressionDSL<W, T>>
41-
extends AbstractWhereSupport<W, T> {
41+
extends AbstractWhereStarter<W, T> {
4242

4343
private final List<JoinSpecification.Builder> joinSpecificationBuilders = new ArrayList<>();
4444
private final Map<SqlTable, String> tableAliases = new HashMap<>();

src/main/java/org/mybatis/dynamic/sql/select/CountDSL.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import org.mybatis.dynamic.sql.SqlTable;
2626
import org.mybatis.dynamic.sql.configuration.StatementConfiguration;
2727
import org.mybatis.dynamic.sql.util.Buildable;
28-
import org.mybatis.dynamic.sql.where.AbstractWhereDSL;
28+
import org.mybatis.dynamic.sql.where.AbstractWhereFinisher;
2929
import org.mybatis.dynamic.sql.where.WhereModel;
3030

3131
/**
@@ -131,7 +131,7 @@ public CountDSL<R> from(SqlTable table) {
131131
}
132132
}
133133

134-
public class CountWhereBuilder extends AbstractWhereDSL<CountWhereBuilder>
134+
public class CountWhereBuilder extends AbstractWhereFinisher<CountWhereBuilder>
135135
implements Buildable<R> {
136136
private CountWhereBuilder() {
137137
super(statementConfiguration);
@@ -149,7 +149,7 @@ protected CountWhereBuilder getThis() {
149149
}
150150

151151
protected WhereModel buildWhereModel() {
152-
return internalBuild();
152+
return super.buildModel();
153153
}
154154
}
155155
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2016-2023 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.function.Consumer;
19+
20+
import org.mybatis.dynamic.sql.common.AbstractBooleanExpressionDSL;
21+
22+
@FunctionalInterface
23+
public interface HavingApplier {
24+
25+
void accept(AbstractHavingFinisher<?> havingFinisher);
26+
27+
/**
28+
* Return a composed having applier that performs this operation followed by the after operation.
29+
*
30+
* @param after the operation to perform after this operation
31+
*
32+
* @return a composed having applier that performs this operation followed by the after operation.
33+
*/
34+
default HavingApplier andThen(Consumer<AbstractBooleanExpressionDSL<?>> after) {
35+
return t -> {
36+
accept(t);
37+
after.accept(t);
38+
};
39+
}
40+
}

0 commit comments

Comments
 (0)