Skip to content

Commit fd7d6e8

Browse files
committed
Documentation
1 parent bc4beca commit fd7d6e8

File tree

3 files changed

+116
-13
lines changed

3 files changed

+116
-13
lines changed

src/site/markdown/docs/extending.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,3 +292,86 @@ it. You can write your own rendering support if you are dissatisfied with the S
292292
Writing a custom renderer is quite complex. If you want to undertake that task, we suggest that you take the time to
293293
understand how the default renderers work first. Feel free to ask questions about this topic on the MyBatis mailing
294294
list.
295+
296+
## Writing Custom Conditions
297+
298+
The library supplies a full range of conditions for all the common SQL operators (=, !=, like, between, etc.) Some
299+
databases support extensions to the standard operators. For example, MySQL supports an extension to the "LIKE"
300+
condition - the "ESCAPE" clause. If you need to implement a condition like that, then you will need to code a
301+
custom condition.
302+
303+
Here's an example of implementing a LIKE condition that supports ESCAPE:
304+
305+
```java
306+
@NullMarked
307+
public class IsLikeEscape<T> extends AbstractSingleValueCondition<T> {
308+
private static final IsLikeEscape<?> EMPTY = new IsLikeEscape<Object>(-1, null) {
309+
@Override
310+
public Object value() {
311+
throw new NoSuchElementException("No value present"); //$NON-NLS-1$
312+
}
313+
314+
@Override
315+
public boolean isEmpty() {
316+
return true;
317+
}
318+
};
319+
320+
public static <T> IsLikeEscape<T> empty() {
321+
@SuppressWarnings("unchecked")
322+
IsLikeEscape<T> t = (IsLikeEscape<T>) EMPTY;
323+
return t;
324+
}
325+
326+
private final @Nullable Character escapeCharacter;
327+
328+
protected IsLikeEscape(T value, @Nullable Character escapeCharacter) {
329+
super(value);
330+
this.escapeCharacter = escapeCharacter;
331+
}
332+
333+
@Override
334+
public String operator() {
335+
return "like";
336+
}
337+
338+
@Override
339+
public FragmentAndParameters renderCondition(RenderingContext renderingContext, BindableColumn<T> leftColumn) {
340+
var fragment = super.renderCondition(renderingContext, leftColumn);
341+
if (escapeCharacter != null) {
342+
fragment = fragment.mapFragment(this::addEscape);
343+
}
344+
345+
return fragment;
346+
}
347+
348+
private String addEscape(String s) {
349+
return s + " ESCAPE '" + escapeCharacter + "'";
350+
}
351+
352+
@Override
353+
public IsLikeEscape<T> filter(Predicate<? super T> predicate) {
354+
return filterSupport(predicate, IsLikeEscape::empty, this);
355+
}
356+
357+
public <R> IsLikeEscape<R> map(Function<? super T, ? extends R> mapper) {
358+
return mapSupport(mapper, v -> new IsLikeEscape<>(v, escapeCharacter), IsLikeEscape::empty);
359+
}
360+
361+
public static <T> IsLikeEscape<T> isLike(T value) {
362+
return new IsLikeEscape<>(value, null);
363+
}
364+
365+
public static <T> IsLikeEscape<T> isLike(T value, Character escapeCharacter) {
366+
return new IsLikeEscape<>(value, escapeCharacter);
367+
}
368+
}
369+
```
370+
371+
Important notes:
372+
373+
1. The class extends `AbstractSingleValueCondition` - which is appropriate for like conditions
374+
2. The class constructor accepts an escape character that will be rendered into an ESCAPE phrase
375+
3. The class overrides `renderCondition` and changes the library generated `FragmentAndParameters` to add the ESCAPE
376+
phrase. **This is the key to what's needed to implement a custom condition.**
377+
4. The class provides `map` and `filter` functions as is expected for any condition in the library

src/test/java/examples/mysql/IsLikeEscape.java

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,18 @@
1717

1818
import java.util.NoSuchElementException;
1919
import java.util.function.Function;
20+
import java.util.function.Predicate;
2021

2122
import org.jspecify.annotations.NullMarked;
23+
import org.jspecify.annotations.Nullable;
24+
import org.mybatis.dynamic.sql.AbstractSingleValueCondition;
2225
import org.mybatis.dynamic.sql.BindableColumn;
2326
import org.mybatis.dynamic.sql.render.RenderingContext;
2427
import org.mybatis.dynamic.sql.util.FragmentAndParameters;
25-
import org.mybatis.dynamic.sql.where.condition.IsLike;
2628

2729
@NullMarked
28-
public class IsLikeEscape<T> extends IsLike<T> {
29-
private static final IsLikeEscape<?> EMPTY = new IsLikeEscape<Object>(-1, "") {
30+
public class IsLikeEscape<T> extends AbstractSingleValueCondition<T> {
31+
private static final IsLikeEscape<?> EMPTY = new IsLikeEscape<Object>(-1, null) {
3032
@Override
3133
public Object value() {
3234
throw new NoSuchElementException("No value present"); //$NON-NLS-1$
@@ -44,28 +46,46 @@ public static <T> IsLikeEscape<T> empty() {
4446
return t;
4547
}
4648

47-
private final String escapeString;
49+
private final @Nullable Character escapeCharacter;
4850

49-
protected IsLikeEscape(T value, String escapeString) {
51+
protected IsLikeEscape(T value, @Nullable Character escapeCharacter) {
5052
super(value);
51-
this.escapeString = escapeString;
53+
this.escapeCharacter = escapeCharacter;
54+
}
55+
56+
@Override
57+
public String operator() {
58+
return "like";
5259
}
5360

5461
@Override
5562
public FragmentAndParameters renderCondition(RenderingContext renderingContext, BindableColumn<T> leftColumn) {
56-
return super.renderCondition(renderingContext, leftColumn).mapFragment(this::addEscape);
63+
var fragment = super.renderCondition(renderingContext, leftColumn);
64+
if (escapeCharacter != null) {
65+
fragment = fragment.mapFragment(this::addEscape);
66+
}
67+
68+
return fragment;
5769
}
5870

5971
private String addEscape(String s) {
60-
return s + " ESCAPE '" + escapeString + "'";
72+
return s + " ESCAPE '" + escapeCharacter + "'";
6173
}
6274

6375
@Override
64-
public <R> IsLike<R> map(Function<? super T, ? extends R> mapper) {
65-
return mapSupport(mapper, v -> new IsLikeEscape<>(v, escapeString), IsLikeEscape::empty);
76+
public IsLikeEscape<T> filter(Predicate<? super T> predicate) {
77+
return filterSupport(predicate, IsLikeEscape::empty, this);
78+
}
79+
80+
public <R> IsLikeEscape<R> map(Function<? super T, ? extends R> mapper) {
81+
return mapSupport(mapper, v -> new IsLikeEscape<>(v, escapeCharacter), IsLikeEscape::empty);
82+
}
83+
84+
public static <T> IsLikeEscape<T> isLike(T value) {
85+
return new IsLikeEscape<>(value, null);
6686
}
6787

68-
public static <T> IsLikeEscape<T> isLike(T value, String escapeString) {
69-
return new IsLikeEscape<>(value, escapeString);
88+
public static <T> IsLikeEscape<T> isLike(T value, Character escapeCharacter) {
89+
return new IsLikeEscape<>(value, escapeCharacter);
7090
}
7191
}

src/test/java/examples/mysql/MySQLTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ void testIsLikeEscape() {
127127

128128
SelectStatementProvider selectStatement = select(id, description)
129129
.from(items)
130-
.where(description, IsLikeEscape.isLike("Item 1%", "#").map(s -> s))
130+
.where(description, IsLikeEscape.isLike("Item 1%", '#').map(s -> s))
131131
.orderBy(id)
132132
.build()
133133
.render(RenderingStrategies.MYBATIS3);

0 commit comments

Comments
 (0)