View Javadoc
1   /*
2    *    Copyright 2016-2026 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.dsl;
17  
18  import java.util.ArrayList;
19  import java.util.Arrays;
20  import java.util.Collection;
21  import java.util.List;
22  import java.util.Objects;
23  import java.util.function.Consumer;
24  
25  import org.jspecify.annotations.Nullable;
26  import org.mybatis.dynamic.sql.AndOrCriteriaGroup;
27  import org.mybatis.dynamic.sql.BasicColumn;
28  import org.mybatis.dynamic.sql.NullCriterion;
29  import org.mybatis.dynamic.sql.SortSpecification;
30  import org.mybatis.dynamic.sql.SqlCriterion;
31  import org.mybatis.dynamic.sql.SqlTable;
32  import org.mybatis.dynamic.sql.TableExpression;
33  import org.mybatis.dynamic.sql.common.OrderByModel;
34  import org.mybatis.dynamic.sql.configuration.StatementConfiguration;
35  import org.mybatis.dynamic.sql.select.GroupByModel;
36  import org.mybatis.dynamic.sql.select.HavingApplier;
37  import org.mybatis.dynamic.sql.select.HavingModel;
38  import org.mybatis.dynamic.sql.select.PagingModel;
39  import org.mybatis.dynamic.sql.select.QueryExpressionModel;
40  import org.mybatis.dynamic.sql.select.SelectModel;
41  import org.mybatis.dynamic.sql.select.join.JoinType;
42  import org.mybatis.dynamic.sql.util.Buildable;
43  import org.mybatis.dynamic.sql.util.ConfigurableStatement;
44  import org.mybatis.dynamic.sql.util.Validator;
45  import org.mybatis.dynamic.sql.where.WhereApplier;
46  import org.mybatis.dynamic.sql.where.WhereModel;
47  
48  public class SelectDSL implements
49          JoinOperations<SelectDSL.JoinSpecificationFinisher>,
50          WhereOperations<SelectDSL.QueryExpressionWhereBuilder>,
51          OrderByOperations<SelectDSL>,
52          GroupByOperations<SelectDSL>,
53          HavingOperations<SelectDSL.QueryExpressionHavingBuilder>,
54          LimitAndOffsetOperations<SelectDSL, SelectModel>,
55          ForAndWaitOperations<SelectDSL>,
56          ConfigurableStatement<SelectDSL>,
57          Buildable<SelectModel> {
58      private final StatementConfiguration statementConfiguration = new StatementConfiguration();
59      private CurrentQueryValues currentQueryValues = new CurrentQueryValues();
60      private final List<QueryExpressionModel> unionQueries = new ArrayList<>();
61      private @Nullable OrderByModel orderByModel;
62      private final LimitAndOffsetSupport limitAndOffsetSupport = new LimitAndOffsetSupport();
63      private @Nullable String forClause;
64      private @Nullable String waitClause;
65  
66      private static class CurrentQueryValues extends AbstractQueryingDSL {
67          QueryExpressionModel.Builder builder = new QueryExpressionModel.Builder();
68          @Nullable QueryExpressionWhereBuilder whereBuilder;
69          @Nullable QueryExpressionHavingBuilder havingBuilder;
70  
71          QueryExpressionModel toQueryExpressionModel() {
72              return builder
73                      .withTableAliases(tableAliases())
74                      .withTable(table())
75                      .withJoinModel(buildJoinModel())
76                      .withWhereModel(whereBuilder == null ? null : whereBuilder.buildWhereModel())
77                      .withHavingModel(havingBuilder == null ? null : havingBuilder.buildHavingModel())
78                      .build();
79          }
80      }
81  
82      private SelectDSL(Collection<? extends BasicColumn> selectList, boolean isDistinct) {
83          currentQueryValues.builder.withSelectList(selectList);
84          currentQueryValues.builder.isDistinct(isDistinct);
85      }
86  
87      public static SelectDSL select(BasicColumn... selectList) {
88          return select(Arrays.asList(selectList));
89      }
90  
91      public static SelectDSL select(Collection<? extends BasicColumn> selectList) {
92          return new SelectDSL(selectList, false);
93      }
94  
95      public static SelectDSL selectDistinct(BasicColumn... selectList) {
96          return selectDistinct(Arrays.asList(selectList));
97      }
98  
99      public static SelectDSL selectDistinct(Collection<? extends BasicColumn> selectList) {
100         return new SelectDSL(selectList, true);
101     }
102 
103     public SelectDSL from(Buildable<SelectModel> select) {
104         currentQueryValues.setTable(select);
105         return this;
106     }
107 
108     public SelectDSL from(Buildable<SelectModel> select, String tableAlias) {
109         currentQueryValues.setTable(select, tableAlias);
110         return this;
111     }
112 
113     public SelectDSL from(SqlTable table) {
114         currentQueryValues.setTable(table);
115         return this;
116     }
117 
118     public SelectDSL from(SqlTable table, String tableAlias) {
119         currentQueryValues.setTable(table, tableAlias);
120         return this;
121     }
122 
123     @Override
124     public JoinSpecificationFinisher join(JoinType joinType, TableExpression joinTable,
125                                           SqlCriterion initialCriterion) {
126         JoinSpecificationFinisher finisher = new JoinSpecificationFinisher(joinType, joinTable, initialCriterion);
127         currentQueryValues.addJoinSpecification(finisher);
128         return finisher;
129     }
130 
131     @Override
132     public JoinSpecificationFinisher join(JoinType joinType, SqlTable joinTable, String tableAlias,
133                                           SqlCriterion initialCriterion) {
134         currentQueryValues.addTableAlias(joinTable, tableAlias);
135         return join(joinType, joinTable, initialCriterion);
136     }
137 
138     @Override
139     public QueryExpressionWhereBuilder where() {
140         currentQueryValues.whereBuilder = Objects.requireNonNullElseGet(currentQueryValues.whereBuilder,
141                 () -> new QueryExpressionWhereBuilder(new NullCriterion()));
142         return currentQueryValues.whereBuilder;
143     }
144 
145     @Override
146     public QueryExpressionWhereBuilder where(SqlCriterion initialCriterion) {
147         Validator.assertNull(currentQueryValues.whereBuilder, Validator.ERROR_32);
148         currentQueryValues.whereBuilder = new QueryExpressionWhereBuilder(initialCriterion);
149         return currentQueryValues.whereBuilder;
150     }
151 
152     @Override
153     public QueryExpressionWhereBuilder applyWhere(WhereApplier whereApplier) {
154         Validator.assertNull(currentQueryValues.whereBuilder, Validator.ERROR_32);
155         currentQueryValues.whereBuilder =
156                 new QueryExpressionWhereBuilder(whereApplier.initialCriterion(), whereApplier.subCriteria());
157         return currentQueryValues.whereBuilder;
158     }
159 
160     @Override
161     public SelectDSL orderBy(Collection<? extends SortSpecification> columns) {
162         orderByModel = OrderByModel.of(columns);
163         return this;
164     }
165 
166     @Override
167     public SelectDSL groupBy(Collection<? extends BasicColumn> columns) {
168         currentQueryValues.builder.withGroupByModel(GroupByModel.of(columns));
169         return this;
170     }
171 
172     @Override
173     public QueryExpressionHavingBuilder having(SqlCriterion initialCriterion) {
174         Validator.assertNull(currentQueryValues.havingBuilder, "ERROR.31"); //$NON-NLS-1$
175         currentQueryValues.havingBuilder = new QueryExpressionHavingBuilder(initialCriterion);
176         return currentQueryValues.havingBuilder;
177     }
178 
179     @Override
180     public QueryExpressionHavingBuilder applyHaving(HavingApplier havingApplier) {
181         Validator.assertNull(currentQueryValues.havingBuilder, "ERROR.31"); //$NON-NLS-1$
182         currentQueryValues.havingBuilder =
183                 new QueryExpressionHavingBuilder(havingApplier.initialCriterion(), havingApplier.subCriteria());
184         return currentQueryValues.havingBuilder;
185     }
186 
187     @Override
188     public LimitFinisher<SelectDSL, SelectModel> limitWhenPresent(@Nullable Long limit) {
189         return limitAndOffsetSupport.limitWhenPresent(limit);
190     }
191 
192     @Override
193     public OffsetFirstFinisher<SelectDSL, SelectModel> offsetWhenPresent(@Nullable Long offset) {
194         return limitAndOffsetSupport.offsetWhenPresent(offset);
195     }
196 
197     @Override
198     public FetchFirstFinisher<SelectDSL> fetchFirstWhenPresent(@Nullable Long fetchFirstRows) {
199         return limitAndOffsetSupport.fetchFirstWhenPresent(fetchFirstRows);
200     }
201 
202     @Override
203     public SelectDSL setWaitClause(String waitClause) {
204         Validator.assertNull(this.waitClause, "ERROR.49"); //$NON-NLS-1$
205         this.waitClause = waitClause;
206         return this;
207     }
208 
209     @Override
210     public SelectDSL setForClause(String forClause) {
211         Validator.assertNull(this.forClause, "ERROR.48"); //$NON-NLS-1$
212         this.forClause = forClause;
213         return this;
214     }
215 
216     public UnionBuilder union() {
217         return new UnionBuilder("union"); //$NON-NLS-1$
218     }
219 
220     public UnionBuilder unionAll() {
221         return new UnionBuilder("union all"); //$NON-NLS-1$
222     }
223 
224     @Override
225     public SelectDSL configureStatement(Consumer<StatementConfiguration> consumer) {
226         consumer.accept(statementConfiguration);
227         return this;
228     }
229 
230     @Override
231     public SelectModel build() {
232         return new SelectModel.Builder()
233                 .withStatementConfiguration(statementConfiguration)
234                 .withQueryExpressions(unionQueries)
235                 .withQueryExpression(currentQueryValues.toQueryExpressionModel())
236                 .withOrderByModel(orderByModel)
237                 .withPagingModel(limitAndOffsetSupport.buildPagingModel())
238                 .withForClause(forClause)
239                 .withWaitClause(waitClause)
240                 .build();
241     }
242 
243     public class QueryExpressionWhereBuilder implements BooleanOperations<QueryExpressionWhereBuilder>,
244             ConfigurableStatement<QueryExpressionWhereBuilder>,
245             OrderByOperations<SelectDSL>,
246             GroupByOperations<SelectDSL>,
247             LimitAndOffsetOperations<SelectDSL, SelectModel>,
248             ForAndWaitOperations<SelectDSL>,
249             Buildable<SelectModel> {
250         private final SqlCriterion initialCriterion;
251         private final List<AndOrCriteriaGroup> subCriteria = new ArrayList<>();
252 
253         public QueryExpressionWhereBuilder(SqlCriterion initialCriterion) {
254             this.initialCriterion = initialCriterion;
255         }
256 
257         public QueryExpressionWhereBuilder(SqlCriterion initialCriterion, List<AndOrCriteriaGroup> subCriteria) {
258             this(initialCriterion);
259             this.subCriteria.addAll(subCriteria);
260         }
261 
262         @Override
263         public QueryExpressionWhereBuilder addSubCriterion(AndOrCriteriaGroup subCriterion) {
264             subCriteria.add(subCriterion);
265             return this;
266         }
267 
268         public UnionBuilder union() {
269             return SelectDSL.this.union();
270         }
271 
272         public UnionBuilder unionAll() {
273             return SelectDSL.this.unionAll();
274         }
275 
276         @Override
277         public LimitFinisher<SelectDSL, SelectModel> limitWhenPresent(@Nullable Long limit) {
278             return SelectDSL.this.limitWhenPresent(limit);
279         }
280 
281         @Override
282         public OffsetFirstFinisher<SelectDSL, SelectModel> offsetWhenPresent(@Nullable Long offset) {
283             return SelectDSL.this.offsetWhenPresent(offset);
284         }
285 
286         @Override
287         public FetchFirstFinisher<SelectDSL> fetchFirstWhenPresent(@Nullable Long fetchFirstRows) {
288             return SelectDSL.this.fetchFirstWhenPresent(fetchFirstRows);
289         }
290 
291         @Override
292         public SelectDSL orderBy(Collection<? extends SortSpecification> columns) {
293             return SelectDSL.this.orderBy(columns);
294         }
295 
296         @Override
297         public SelectDSL groupBy(Collection<? extends BasicColumn> columns) {
298             return SelectDSL.this.groupBy(columns);
299         }
300 
301         @Override
302         public SelectModel build() {
303             return SelectDSL.this.build();
304         }
305 
306         protected WhereModel buildWhereModel() {
307             return new WhereModel.Builder()
308                     .withInitialCriterion(initialCriterion)
309                     .withSubCriteria(subCriteria)
310                     .build();
311         }
312 
313         @Override
314         public QueryExpressionWhereBuilder configureStatement(Consumer<StatementConfiguration> consumer) {
315             SelectDSL.this.configureStatement(consumer);
316             return this;
317         }
318 
319         @Override
320         public SelectDSL setWaitClause(String waitClause) {
321             return SelectDSL.this.setWaitClause(waitClause);
322         }
323 
324         @Override
325         public SelectDSL setForClause(String forClause) {
326             return SelectDSL.this.setForClause(forClause);
327         }
328     }
329 
330     public class JoinSpecificationFinisher
331             extends AbstractJoinSupport<SelectDSL, JoinSpecificationFinisher>
332             implements WhereOperations<QueryExpressionWhereBuilder>,
333             ConfigurableStatement<JoinSpecificationFinisher>,
334             GroupByOperations<SelectDSL>,
335             OrderByOperations<SelectDSL>,
336             LimitAndOffsetOperations<SelectDSL, SelectModel>,
337             ForAndWaitOperations<SelectDSL>,
338             Buildable<SelectModel> {
339 
340         protected JoinSpecificationFinisher(JoinType joinType, TableExpression joinTable,
341                                             SqlCriterion initialCriterion) {
342             super(joinType, joinTable, initialCriterion);
343         }
344 
345         @Override
346         public SelectModel build() {
347             return SelectDSL.this.build();
348         }
349 
350         @Override
351         public JoinSpecificationFinisher configureStatement(Consumer<StatementConfiguration> consumer) {
352             SelectDSL.this.configureStatement(consumer);
353             return this;
354         }
355 
356         @Override
357         public QueryExpressionWhereBuilder where() {
358             return SelectDSL.this.where();
359         }
360 
361         @Override
362         public QueryExpressionWhereBuilder where(SqlCriterion initialCriterion) {
363             return SelectDSL.this.where(initialCriterion);
364         }
365 
366         @Override
367         public QueryExpressionWhereBuilder applyWhere(WhereApplier whereApplier) {
368             return SelectDSL.this.applyWhere(whereApplier);
369         }
370 
371         @Override
372         public SelectDSL groupBy(Collection<? extends BasicColumn> columns) {
373             return SelectDSL.this.groupBy(columns);
374         }
375 
376         public UnionBuilder union() {
377             return SelectDSL.this.union();
378         }
379 
380         public UnionBuilder unionAll() {
381             return SelectDSL.this.unionAll();
382         }
383 
384         @Override
385         public SelectDSL orderBy(Collection<? extends SortSpecification> columns) {
386             return SelectDSL.this.orderBy(columns);
387         }
388 
389         @Override
390         protected JoinSpecificationFinisher getThis() {
391             return this;
392         }
393 
394         @Override
395         public LimitFinisher<SelectDSL, SelectModel> limitWhenPresent(@Nullable Long limit) {
396             return SelectDSL.this.limitWhenPresent(limit);
397         }
398 
399         @Override
400         public OffsetFirstFinisher<SelectDSL, SelectModel> offsetWhenPresent(@Nullable Long offset) {
401             return SelectDSL.this.offsetWhenPresent(offset);
402         }
403 
404         @Override
405         public FetchFirstFinisher<SelectDSL> fetchFirstWhenPresent(@Nullable Long fetchFirstRows) {
406             return SelectDSL.this.fetchFirstWhenPresent(fetchFirstRows);
407         }
408 
409         @Override
410         public SelectDSL setWaitClause(String waitClause) {
411             return SelectDSL.this.setWaitClause(waitClause);
412         }
413 
414         @Override
415         public SelectDSL setForClause(String forClause) {
416             return SelectDSL.this.setForClause(forClause);
417         }
418 
419         @Override
420         public JoinSpecificationFinisher join(JoinType joinType, TableExpression joinTable,
421                                               SqlCriterion initialCriterion) {
422             return SelectDSL.this.join(joinType, joinTable, initialCriterion);
423         }
424 
425         @Override
426         public JoinSpecificationFinisher join(JoinType joinType, SqlTable joinTable, String tableAlias,
427                                               SqlCriterion initialCriterion) {
428             return SelectDSL.this.join(joinType, joinTable, tableAlias, initialCriterion);
429         }
430 
431         @Override
432         public SelectDSL endJoin() {
433             return SelectDSL.this;
434         }
435     }
436 
437     public class QueryExpressionHavingBuilder
438             implements BooleanOperations<QueryExpressionHavingBuilder>,
439             OrderByOperations<SelectDSL>,
440             LimitAndOffsetOperations<SelectDSL, SelectModel>,
441             ForAndWaitOperations<SelectDSL>,
442             Buildable<SelectModel> {
443         private final SqlCriterion initialCriterion;
444         private final List<AndOrCriteriaGroup> subCriteria = new ArrayList<>();
445 
446         public QueryExpressionHavingBuilder(SqlCriterion initialCriterion) {
447             this.initialCriterion = initialCriterion;
448         }
449 
450         public QueryExpressionHavingBuilder(SqlCriterion initialCriterion, List<AndOrCriteriaGroup> subCriteria) {
451             this(initialCriterion);
452             this.subCriteria.addAll(subCriteria);
453         }
454 
455         @Override
456         public QueryExpressionHavingBuilder addSubCriterion(AndOrCriteriaGroup subCriterion) {
457             subCriteria.add(subCriterion);
458             return this;
459         }
460 
461         @Override
462         public SelectDSL orderBy(Collection<? extends SortSpecification> columns) {
463             return SelectDSL.this.orderBy(columns);
464         }
465 
466         public UnionBuilder union() {
467             return SelectDSL.this.union();
468         }
469 
470         public UnionBuilder unionAll() {
471             return SelectDSL.this.unionAll();
472         }
473 
474         @Override
475         public SelectModel build() {
476             return SelectDSL.this.build();
477         }
478 
479         protected HavingModel buildHavingModel() {
480             return new HavingModel.Builder()
481                     .withInitialCriterion(initialCriterion)
482                     .withSubCriteria(subCriteria)
483                     .build();
484         }
485 
486         @Override
487         public LimitFinisher<SelectDSL, SelectModel> limitWhenPresent(@Nullable Long limit) {
488             return SelectDSL.this.limitWhenPresent(limit);
489         }
490 
491         @Override
492         public OffsetFirstFinisher<SelectDSL, SelectModel> offsetWhenPresent(@Nullable Long offset) {
493             return SelectDSL.this.offsetWhenPresent(offset);
494         }
495 
496         @Override
497         public FetchFirstFinisher<SelectDSL> fetchFirstWhenPresent(@Nullable Long fetchFirstRows) {
498             return SelectDSL.this.fetchFirstWhenPresent(fetchFirstRows);
499         }
500 
501         @Override
502         public SelectDSL setWaitClause(String waitClause) {
503             return SelectDSL.this.setWaitClause(waitClause);
504         }
505 
506         @Override
507         public SelectDSL setForClause(String forClause) {
508             return SelectDSL.this.setForClause(forClause);
509         }
510     }
511 
512     private class LimitAndOffsetSupport extends AbstractLimitAndOffsetSupport<SelectDSL, SelectModel> {
513         public LimitAndOffsetSupport() {
514             super(SelectDSL.this);
515         }
516 
517         protected @Nullable PagingModel buildPagingModel() {
518             return toPagingModel().orElse(null);
519         }
520 
521         @Override
522         protected SelectDSL getThis() {
523             return SelectDSL.this;
524         }
525     }
526 
527     public class UnionBuilder {
528         protected final String connector;
529 
530         public UnionBuilder(String connector) {
531             this.connector = connector;
532         }
533 
534         public SelectDSL select(BasicColumn... selectList) {
535             return select(Arrays.asList(selectList));
536         }
537 
538         public SelectDSL select(List<BasicColumn> selectList) {
539             unionQueries.add(currentQueryValues.toQueryExpressionModel());
540             currentQueryValues = new CurrentQueryValues();
541             currentQueryValues.builder.withConnector(connector);
542             currentQueryValues.builder.withSelectList(selectList);
543             return SelectDSL.this;
544         }
545 
546         public SelectDSL selectDistinct(BasicColumn... selectList) {
547             return selectDistinct(Arrays.asList(selectList));
548         }
549 
550         public SelectDSL selectDistinct(List<BasicColumn> selectList) {
551             select(selectList);
552             currentQueryValues.builder.isDistinct(true);
553             return SelectDSL.this;
554         }
555     }
556 }