Skip to content

Commit e0e76bf

Browse files
committed
Documentation
1 parent 3723182 commit e0e76bf

File tree

2 files changed

+106
-33
lines changed

2 files changed

+106
-33
lines changed

CHANGELOG.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,53 @@ The major themes of this release include the following:
1313
1. Add support for subqueries in select statements - both in a from clause and a join clause.
1414
1. Add support for the "exists" and "not exists" operator. This will work in "where" clauses anywhere
1515
they are supported.
16+
1. Refactor and improve the built-in conditions for consistency (see below)
1617
1. Continue to refine the Kotlin DSL. Most changes to the Kotlin DSL are internal and should be source code
1718
compatible with existing code. There is one breaking change detailed below.
1819
1. Remove deprecated code from prior releases.
1920

21+
### Built-In Condition Refactoring
22+
All built-in conditions have been rafactored. The changes should have no impact for the vast majority of users.
23+
However, there are some changes in behavior and one breaking change.
24+
25+
1. The existing "then" and "when" methods have been deprecated and replaced with "map" and "filter" respectively.
26+
The new method names are more familiar and more representative of what these methods actually do. In effect,
27+
these methods mimic the function of the "map" and "filter" methods on "java.util.Optional" and they are used
28+
for a similar purpose.
29+
1. The new "filter" method works a bit differently than the "when" method it replaces. The "when" method could not
30+
be chained - if it was called multiple times, only the last call would take effect. The new "filter" methods works
31+
as it should and every call will take effect.
32+
1. All the "WhenPresent" conditions have been removed as separate classes. The methods that produced these conditions
33+
in the SqlBuilder remain, and they will now produce a condition with a "NotNull" filter applied. So at the API level,
34+
things will function exactly as before, but the intermediate classes will be different.
35+
1. One breaking change is that the builder for List value conditions has been removed without replacement. If you
36+
were using this builder, then the replacement is to build a new List value condition and then call the "map" and
37+
"filter" methods as needed. For example, prior code looked like this
38+
39+
```java
40+
public static IsIn<String> isIn(String...values) {
41+
return new IsIn.Builder<String>()
42+
.withValues(Arrays.asList(values))
43+
.withValueStreamTransformer(s -> s.filter(Objects::nonNull)
44+
.map(String::trim)
45+
.filter(st -> !st.isEmpty()))
46+
.build();
47+
}
48+
```
49+
50+
New code should look like this:
51+
52+
```java
53+
public static IsIn<String> isIn(String...values) {
54+
return SqlBuilder.isIn(values)
55+
.filter(Objects::nonNull)
56+
.map(String::trim)
57+
.filter(st -> !st.isEmpty());
58+
}
59+
```
60+
61+
We think this is a marked improvement!
62+
2063
### Breaking Change for Kotlin
2164

2265
In this release the Kotlin support for `select` and `count` statements has been refactored. This will not impact code

src/site/markdown/docs/conditions.md

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,11 @@ Column comparison conditions can be used to write where clauses comparing the va
6464

6565
## Optional Conditions
6666

67-
All conditions support optionality - meaning they can be configured to render into the final SQL if a configured test passes.
67+
All conditions support optionality - meaning they can be configured to render into the final SQL if a configured test
68+
passes. Optionality is implemented via standard "filter" and "map" methods - which behave very similarly to the "filter"
69+
and "map" methods in `java.util.Optional`. In general, if a condition's "filter" method is not satisfied, then the
70+
condition will not be rendered. The "map" method can be used the alter the value in a condition before the condition
71+
is rendered.
6872

6973
For example, you could code a search like this:
7074

@@ -73,18 +77,22 @@ For example, you could code a search like this:
7377
...
7478
SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight)
7579
.from(animalData)
76-
.where(animalName, isEqualTo(animalName_).when(Objects::nonNull))
77-
.and(bodyWeight, isEqualToWhen(bodyWeight_).when(Objects::nonNull))
78-
.and(brainWeight, isEqualToWhen(brainWeight_).when(Objects::nonNull))
80+
.where(animalName, isEqualTo(animalName_).filter(Objects::nonNull))
81+
.and(bodyWeight, isEqualToWhen(bodyWeight_).filter(Objects::nonNull))
82+
.and(brainWeight, isEqualToWhen(brainWeight_).filter(Objects::nonNull))
7983
.build()
8084
.render(RenderingStrategies.MYBATIS3);
8185
...
8286
}
8387
```
8488

85-
In this example, the three conditions will only be rendered if the values passed to them are not null. If all three values are null, then no where clause will be generated.
89+
In this example, the three conditions will only be rendered if the values passed to them are not null.
90+
If all three values are null, then no where clause will be generated.
8691

87-
Each of the conditions accepts a lambda expression that can be used to determine if the condition should render or not. The lambdas will all be of standard JDK types (either `java.util.function.BooleanSupplier`, `java.util.function.Predicate`, or `java.util.function.BiPredicate` depending on the type of condition). The following table lists the optional conditions and shows how to use them:
92+
Each of the conditions accepts a lambda expression that can be used to determine if the condition should render or not.
93+
The lambdas will all be of standard JDK types (either `java.util.function.BooleanSupplier`,
94+
`java.util.function.Predicate`, or `java.util.function.BiPredicate` depending on the type of condition). The following
95+
table lists the optional conditions and shows how to use them:
8896

8997
| Condition | Example | Rendering Rules |
9098
|-----------|---------|-----------------|
@@ -103,8 +111,9 @@ Each of the conditions accepts a lambda expression that can be used to determine
103111
| Not Null | where(id, isNotNull().when(BooleanSupplier) | The condition will render if BooleanSupplier.getAsBoolean() returns true |
104112
| Null | where(id, isNull().when(BooleanSupplier) | The condition will render if BooleanSupplier.getAsBoolean() returns true |
105113

106-
### "When Present" Optional Conditions
107-
The library supplies several specializations of optional conditions to be used in the common case of checking for null values. The table below lists the rendering rules for each of these "when present" optional conditions.
114+
### "When Present" Condition Builders
115+
The library supplies several methods that supply conditions to be used in the common case of checking for null
116+
values. The table below lists the rendering rules for each of these "when present" condition builder methods.
108117

109118
| Condition | Example | Rendering Rules |
110119
|-----------|---------|-----------------|
@@ -121,12 +130,33 @@ The library supplies several specializations of optional conditions to be used i
121130
| Not Like | where(id, isNotLikeWhenPresent(x)) | The condition will render if x is non-null |
122131
| Not Like Case Insensitive | where(id, isNotLikeCaseInsensitiveWhenPresent(x)) | The condition will render if x is non-null |
123132

124-
### Optionality with the "In" Conditions
125-
Optionality with the "in" and "not in" conditions is a bit more complex than the other types of conditions. The first thing to know is that no "in" or "not in" condition will render if the list of values is empty. For example, there will never be rendered SQL like `where name in ()`. So optionality of the "in" conditions is more about optionality of the *values* of the condition. The library comes with functions that will filter out null values, and will upper case String values to enable case insensitive queries. There are extension points to add additional filtering and mapping if you so desire.
133+
Note that these methods simply apply a "NotNull" filter to a condition. For example:
126134

127-
We think it is a good thing that the library will not render invalid SQL. An "in" condition will always be dropped from rendering if the list of values is empty. But there is some danger with this stance. Because the condition could be dropped from the rendered SQL, more rows could be impacted than expected if the list ends up empty for whatever reason. Our recommended solution is to make sure that you validate list values - especially if they are coming from direct user input. Another option is to take some action when the list is empty. This can be especially helpful when you are applying filters to the value list or using one of the built in "when present" conditions. In that case, it is possible that the list of values could end up empty after a validation check.
135+
```java
136+
// the following two lines are functionally equivalent
137+
... where (id, isEqualToWhenPresent(x)) ...
138+
... where (id, isEqualTo(x).filter(Objects::nonNull)) ...
139+
```
128140

129-
The "In" conditions support a callback that will be invoked when the value list is empty and just before the statement is rendered. You could use the callback to create a condition that will throw an exception when the list is empty. For example:
141+
### Optionality with the "In" Conditions
142+
Optionality with the "in" and "not in" conditions is a bit more complex than the other types of conditions. The first
143+
thing to know is that no "in" or "not in" condition will render if the list of values is empty. For example, there
144+
will never be rendered SQL like `where name in ()`. So optionality of the "in" conditions is more about optionality
145+
of the *values* of the condition. The library comes with functions that will filter out null values, and will upper
146+
case String values to enable case insensitive queries. There are extension points to add additional filtering and
147+
mapping if you so desire.
148+
149+
We think it is a good thing that the library will not render invalid SQL. An "in" condition will always be dropped from
150+
rendering if the list of values is empty. But there is some danger with this stance. Because the condition could be
151+
dropped from the rendered SQL, more rows could be impacted than expected if the list ends up empty for whatever reason.
152+
Our recommended solution is to make sure that you validate list values - especially if they are coming from direct user
153+
input. Another option is to take some action when the list is empty. This can be especially helpful when you are
154+
applying filters to the value list or using one of the built-in "when present" conditions. In that case, it is
155+
possible that the list of values could end up empty after a validation check.
156+
157+
The "In" conditions support a callback that will be invoked when the value list is empty and just before the statement
158+
is rendered. You could use the callback to create a condition that will throw an exception when the list is empty.
159+
For example:
130160

131161
```java
132162
private static <T> IsIn<T> isInRequired(Collection<T> values) {
@@ -139,7 +169,8 @@ The "In" conditions support a callback that will be invoked when the value list
139169
}
140170
```
141171

142-
The following table shows the different supplied In conditions and how they will render for different sets of inputs. The table assumes the following types of input:
172+
The following table shows the different supplied In conditions and how they will render for different sets of inputs.
173+
The table assumes the following types of input:
143174

144175
- Example 1 assumes an input list of ("foo", null, "bar") - like `where(name, isIn("foo", null, "bar"))`
145176
- Example 2 assumes an input list of (null) - like `where(name, isIn((String)null))`
@@ -156,35 +187,39 @@ The following table shows the different supplied In conditions and how they will
156187
| IsNotInCaseInsensitive | No | Yes | upper(name) not in ('FOO', null, 'BAR') | upper(name) not in (null) |
157188
| IsNotInCaseInsensitiveWhenPresent | Yes | Yes | upper(name) not in ('FOO', 'BAR') | No Render |
158189

159-
If none of these options meet your needs, there is an extension point where you can add your own filter and/or map conditions to the value stream. This gives you great flexibility to alter or filter the value list before the condition is rendered.
190+
If none of these options meet your needs, the "In" conditions also support "map" and "filter" methods for the values.
191+
This gives you great flexibility to alter or filter the value list before the condition
192+
is rendered.
160193

161-
The extension point for modifying the value list is the method `then(UnaryOperator<Stream<T>>)`. This method accepts a `UnaryOperator<Stream<T>>` in which you can specify map and/or filter operations for the value stream. For example, suppose you wanted to code an "in" condition that accepted a list of strings, but you want to filter out any null or blank string, and you want to trim all strings. This can be accomplished with code like this:
194+
For example, suppose you wanted to code an "in" condition that accepted a list of strings, but you want to filter out
195+
any null or blank string, and you want to trim all strings. This can be accomplished with code like this:
162196

163197
```java
164198
SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight)
165199
.from(animalData)
166200
.where(animalName, isIn(" Mouse", " ", null, "", "Musk shrew ")
167-
.then(s -> s.filter(Objects::nonNull)
168-
.map(String::trim)
169-
.filter(st -> !st.isEmpty())))
201+
.filter(Objects::nonNull)
202+
.map(String::trim)
203+
.filter(st -> !st.isEmpty()))
170204
.orderBy(id)
171205
.build()
172206
.render(RenderingStrategies.MYBATIS3);
173207
```
174208

175-
This code is a bit cumbersome, so if this is a common use case you could build a specialization of the `IsIn` condition as follows:
209+
This code is a bit cumbersome, so if this is a common use case you could build a specialization of the `IsIn` condition
210+
as follows:
176211

177212
```java
178-
public class MyInCondition {
179-
public static IsIn<String> isIn(String...values) {
180-
return new IsIn.Builder<String>()
181-
.withValues(Arrays.asList(values))
182-
.withValueStreamTransformer(s -> s.filter(Objects::nonNull)
183-
.map(String::trim)
184-
.filter(st -> !st.isEmpty()))
185-
.build();
186-
}
213+
import org.mybatis.dynamic.sql.SqlBuilder;
214+
215+
public class MyInCondition {
216+
public static IsIn<String> isIn(String... values) {
217+
return SqlBuilder.isIn(values)
218+
.filter(Objects::nonNull)
219+
.map(String::trim)
220+
.filter(st -> !st.isEmpty());
187221
}
222+
}
188223
```
189224

190225
Then the condition could be used in a query as follows:
@@ -197,8 +232,3 @@ Then the condition could be used in a query as follows:
197232
.build()
198233
.render(RenderingStrategies.MYBATIS3);
199234
```
200-
201-
You can apply value stream operations to the conditions `IsIn` and `IsNotIn`. The built in conditions
202-
`IsInCaseInsensitive`, `IsInCaseInsensitiveWhenPresent`, `IsInWhenPresent`, `IsNotInCaseInsensitive`,
203-
`IsNotInCaseInsensitiveWhenPresent`, and `IsNotInWhenPresent` do not support additional value stream operations as they
204-
already implement a value stream operation as part of the condition.

0 commit comments

Comments
 (0)