View Javadoc
1   /*
2    *    Copyright 2016-2025 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.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.BindableColumn;
29  import org.mybatis.dynamic.sql.ColumnAndConditionCriterion;
30  import org.mybatis.dynamic.sql.CriteriaGroup;
31  import org.mybatis.dynamic.sql.RenderableCondition;
32  import org.mybatis.dynamic.sql.SortSpecification;
33  import org.mybatis.dynamic.sql.SqlTable;
34  import org.mybatis.dynamic.sql.TableExpression;
35  import org.mybatis.dynamic.sql.common.AbstractBooleanExpressionDSL;
36  import org.mybatis.dynamic.sql.configuration.StatementConfiguration;
37  import org.mybatis.dynamic.sql.select.join.JoinSpecification;
38  import org.mybatis.dynamic.sql.select.join.JoinType;
39  import org.mybatis.dynamic.sql.util.Buildable;
40  import org.mybatis.dynamic.sql.where.AbstractWhereFinisher;
41  import org.mybatis.dynamic.sql.where.AbstractWhereStarter;
42  import org.mybatis.dynamic.sql.where.EmbeddedWhereModel;
43  
44  public class QueryExpressionDSL<R>
45          extends AbstractQueryExpressionDSL<QueryExpressionDSL<R>.QueryExpressionWhereBuilder, QueryExpressionDSL<R>>
46          implements Buildable<R>, SelectDSLOperations<R> {
47  
48      private final @Nullable String connector;
49      private final SelectDSL<R> selectDSL;
50      private final boolean isDistinct;
51      private final List<BasicColumn> selectList;
52      private @Nullable QueryExpressionWhereBuilder whereBuilder;
53      private @Nullable GroupByModel groupByModel;
54      private @Nullable QueryExpressionHavingBuilder havingBuilder;
55  
56      protected QueryExpressionDSL(FromGatherer<R> fromGatherer, TableExpression table) {
57          super(table);
58          connector = fromGatherer.connector;
59          selectList = fromGatherer.selectList;
60          isDistinct = fromGatherer.isDistinct;
61          selectDSL = Objects.requireNonNull(fromGatherer.selectDSL);
62          selectDSL.registerQueryExpression(this);
63      }
64  
65      protected QueryExpressionDSL(FromGatherer<R> fromGatherer, SqlTable table, String tableAlias) {
66          this(fromGatherer, table);
67          addTableAlias(table, tableAlias);
68      }
69  
70      @Override
71      public QueryExpressionWhereBuilder where() {
72          whereBuilder = Objects.requireNonNullElseGet(whereBuilder, QueryExpressionWhereBuilder::new);
73          return whereBuilder;
74      }
75  
76      @Override
77      public QueryExpressionDSL<R> configureStatement(Consumer<StatementConfiguration> consumer) {
78          selectDSL.configureStatement(consumer);
79          return this;
80      }
81  
82      /**
83       * This method is protected here because it doesn't make sense at this point in the DSL.
84       *
85       * @return The having builder
86       */
87      protected QueryExpressionHavingBuilder having() {
88          havingBuilder = Objects.requireNonNullElseGet(havingBuilder, QueryExpressionHavingBuilder::new);
89          return havingBuilder;
90      }
91  
92      /**
93       * This method is meant for use by the Kotlin DSL. We expect a full set of criteria.
94       *
95       * @param criteriaGroup the full criteria for a Kotlin Having clause
96       */
97      protected void applyHaving(CriteriaGroup criteriaGroup) {
98          having().initialize(criteriaGroup);
99      }
100 
101     @Override
102     public R build() {
103         return selectDSL.build();
104     }
105 
106     public JoinSpecificationStarter join(SqlTable joinTable) {
107         return new JoinSpecificationStarter(joinTable, JoinType.INNER);
108     }
109 
110     public JoinSpecificationStarter join(SqlTable joinTable, String tableAlias) {
111         addTableAlias(joinTable, tableAlias);
112         return join(joinTable);
113     }
114 
115     public JoinSpecificationStarter join(Buildable<SelectModel> joinTable, String tableAlias) {
116         return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.INNER);
117     }
118 
119     public JoinSpecificationStarter leftJoin(SqlTable joinTable) {
120         return new JoinSpecificationStarter(joinTable, JoinType.LEFT);
121     }
122 
123     public JoinSpecificationStarter leftJoin(SqlTable joinTable, String tableAlias) {
124         addTableAlias(joinTable, tableAlias);
125         return leftJoin(joinTable);
126     }
127 
128     public JoinSpecificationStarter leftJoin(Buildable<SelectModel> joinTable, String tableAlias) {
129         return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.LEFT);
130     }
131 
132     public JoinSpecificationStarter rightJoin(SqlTable joinTable) {
133         return new JoinSpecificationStarter(joinTable, JoinType.RIGHT);
134     }
135 
136     public JoinSpecificationStarter rightJoin(SqlTable joinTable, String tableAlias) {
137         addTableAlias(joinTable, tableAlias);
138         return rightJoin(joinTable);
139     }
140 
141     public JoinSpecificationStarter rightJoin(Buildable<SelectModel> joinTable, String tableAlias) {
142         return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.RIGHT);
143     }
144 
145     public JoinSpecificationStarter fullJoin(SqlTable joinTable) {
146         return new JoinSpecificationStarter(joinTable, JoinType.FULL);
147     }
148 
149     public JoinSpecificationStarter fullJoin(SqlTable joinTable, String tableAlias) {
150         addTableAlias(joinTable, tableAlias);
151         return fullJoin(joinTable);
152     }
153 
154     public JoinSpecificationStarter fullJoin(Buildable<SelectModel> joinTable, String tableAlias) {
155         return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.FULL);
156     }
157 
158     public GroupByFinisher groupBy(BasicColumn... columns) {
159         return groupBy(Arrays.asList(columns));
160     }
161 
162     public GroupByFinisher groupBy(Collection<? extends BasicColumn> columns) {
163         groupByModel = GroupByModel.of(columns);
164         return new GroupByFinisher();
165     }
166 
167     public SelectDSL<R> orderBy(SortSpecification... columns) {
168         return orderBy(Arrays.asList(columns));
169     }
170 
171     public SelectDSL<R> orderBy(Collection<? extends SortSpecification> columns) {
172         selectDSL.orderBy(columns);
173         return selectDSL;
174     }
175 
176     public UnionBuilder union() {
177         return new UnionBuilder("union"); //$NON-NLS-1$
178     }
179 
180     public UnionBuilder unionAll() {
181         return new UnionBuilder("union all"); //$NON-NLS-1$
182     }
183 
184     protected QueryExpressionModel buildModel() {
185         return QueryExpressionModel.withSelectList(selectList)
186                 .withConnector(connector)
187                 .withTable(table())
188                 .isDistinct(isDistinct)
189                 .withTableAliases(tableAliases())
190                 .withJoinModel(buildJoinModel().orElse(null))
191                 .withGroupByModel(groupByModel)
192                 .withWhereModel(whereBuilder == null ? null : whereBuilder.buildWhereModel())
193                 .withHavingModel(havingBuilder == null ? null : havingBuilder.buildHavingModel())
194                 .build();
195     }
196 
197     @Override
198     protected QueryExpressionDSL<R> getThis() {
199         return this;
200     }
201 
202     @Override
203     public SelectDSL<R> getSelectDSL() {
204         return selectDSL;
205     }
206 
207     public static class FromGatherer<R> {
208         private final @Nullable String connector;
209         private final List<BasicColumn> selectList;
210         private final SelectDSL<R> selectDSL;
211         private final boolean isDistinct;
212 
213         public FromGatherer(Builder<R> builder) {
214             this.connector = builder.connector;
215             this.selectList = builder.selectList;
216             this.selectDSL = Objects.requireNonNull(builder.selectDSL);
217             this.isDistinct = builder.isDistinct;
218         }
219 
220         public QueryExpressionDSL<R> from(Buildable<SelectModel> select) {
221             return new QueryExpressionDSL<>(this, buildSubQuery(select));
222         }
223 
224         public QueryExpressionDSL<R> from(Buildable<SelectModel> select, String tableAlias) {
225             return new QueryExpressionDSL<>(this, buildSubQuery(select, tableAlias));
226         }
227 
228         public QueryExpressionDSL<R> from(SqlTable table) {
229             return new QueryExpressionDSL<>(this, table);
230         }
231 
232         public QueryExpressionDSL<R> from(SqlTable table, String tableAlias) {
233             return new QueryExpressionDSL<>(this, table, tableAlias);
234         }
235 
236         public static class Builder<R> {
237             private @Nullable String connector;
238             private final List<BasicColumn> selectList = new ArrayList<>();
239             private @Nullable SelectDSL<R> selectDSL;
240             private boolean isDistinct;
241 
242             public Builder<R> withConnector(String connector) {
243                 this.connector = connector;
244                 return this;
245             }
246 
247             public Builder<R> withSelectList(Collection<? extends BasicColumn> selectList) {
248                 this.selectList.addAll(selectList);
249                 return this;
250             }
251 
252             public Builder<R> withSelectDSL(SelectDSL<R> selectDSL) {
253                 this.selectDSL = selectDSL;
254                 return this;
255             }
256 
257             public Builder<R> isDistinct() {
258                 this.isDistinct = true;
259                 return this;
260             }
261 
262             public FromGatherer<R> build() {
263                 return new FromGatherer<>(this);
264             }
265         }
266     }
267 
268     public class QueryExpressionWhereBuilder extends AbstractWhereFinisher<QueryExpressionWhereBuilder>
269             implements Buildable<R>, SelectDSLOperations<R> {
270         private QueryExpressionWhereBuilder() {
271             super(QueryExpressionDSL.this);
272         }
273 
274         public UnionBuilder union() {
275             return QueryExpressionDSL.this.union();
276         }
277 
278         public UnionBuilder unionAll() {
279             return QueryExpressionDSL.this.unionAll();
280         }
281 
282         public SelectDSL<R> orderBy(SortSpecification... columns) {
283             return orderBy(Arrays.asList(columns));
284         }
285 
286         public SelectDSL<R> orderBy(Collection<? extends SortSpecification> columns) {
287             return QueryExpressionDSL.this.orderBy(columns);
288         }
289 
290         public GroupByFinisher groupBy(BasicColumn... columns) {
291             return groupBy(Arrays.asList(columns));
292         }
293 
294         public GroupByFinisher groupBy(Collection<? extends BasicColumn> columns) {
295             return QueryExpressionDSL.this.groupBy(columns);
296         }
297 
298         @Override
299         public R build() {
300             return QueryExpressionDSL.this.build();
301         }
302 
303         @Override
304         protected QueryExpressionWhereBuilder getThis() {
305             return this;
306         }
307 
308         @Override
309         public SelectDSL<R> getSelectDSL() {
310             return QueryExpressionDSL.this.getSelectDSL();
311         }
312 
313         protected EmbeddedWhereModel buildWhereModel() {
314             return super.buildModel();
315         }
316     }
317 
318     public class JoinSpecificationStarter {
319         private final TableExpression joinTable;
320         private final JoinType joinType;
321 
322         public JoinSpecificationStarter(TableExpression joinTable, JoinType joinType) {
323             this.joinTable = joinTable;
324             this.joinType = joinType;
325         }
326 
327         public <T> JoinSpecificationFinisher on(BindableColumn<T> joinColumn, RenderableCondition<T> joinCondition) {
328             return new JoinSpecificationFinisher(joinTable, joinColumn, joinCondition, joinType);
329         }
330 
331         public <T> JoinSpecificationFinisher on(BindableColumn<T> joinColumn, RenderableCondition<T> onJoinCondition,
332                                                 AndOrCriteriaGroup... subCriteria) {
333             return new JoinSpecificationFinisher(joinTable, joinColumn, onJoinCondition, joinType, subCriteria);
334         }
335     }
336 
337     public class JoinSpecificationFinisher
338             extends AbstractBooleanExpressionDSL<JoinSpecificationFinisher>
339             implements AbstractWhereStarter<QueryExpressionWhereBuilder, JoinSpecificationFinisher>, Buildable<R>,
340             SelectDSLOperations<R> {
341 
342         private final TableExpression table;
343         private final JoinType joinType;
344 
345         public <T> JoinSpecificationFinisher(TableExpression table, BindableColumn<T> joinColumn,
346                                              RenderableCondition<T> joinCondition, JoinType joinType) {
347             this.table = table;
348             this.joinType = joinType;
349             addJoinSpecificationSupplier(this::buildJoinSpecification);
350 
351             ColumnAndConditionCriterion<T> criterion = ColumnAndConditionCriterion.withColumn(joinColumn)
352                     .withCondition(joinCondition)
353                     .build();
354 
355             setInitialCriterion(criterion);
356         }
357 
358         public <T> JoinSpecificationFinisher(TableExpression table, BindableColumn<T> joinColumn,
359                                              RenderableCondition<T> joinCondition, JoinType joinType,
360                                              AndOrCriteriaGroup... subCriteria) {
361             this.table = table;
362             this.joinType = joinType;
363             addJoinSpecificationSupplier(this::buildJoinSpecification);
364 
365             ColumnAndConditionCriterion<T> criterion = ColumnAndConditionCriterion.withColumn(joinColumn)
366                     .withCondition(joinCondition)
367                     .withSubCriteria(Arrays.asList(subCriteria))
368                     .build();
369 
370             setInitialCriterion(criterion);
371         }
372 
373         private JoinSpecification buildJoinSpecification() {
374             return JoinSpecification.withJoinTable(table)
375                     .withJoinType(joinType)
376                     .withInitialCriterion(getInitialCriterion())
377                     .withSubCriteria(subCriteria)
378                     .build();
379         }
380 
381         @Override
382         public R build() {
383             return QueryExpressionDSL.this.build();
384         }
385 
386         @Override
387         public JoinSpecificationFinisher configureStatement(Consumer<StatementConfiguration> consumer) {
388             selectDSL.configureStatement(consumer);
389             return this;
390         }
391 
392         @Override
393         public QueryExpressionWhereBuilder where() {
394             return QueryExpressionDSL.this.where();
395         }
396 
397         public JoinSpecificationStarter join(SqlTable joinTable) {
398             return QueryExpressionDSL.this.join(joinTable);
399         }
400 
401         public JoinSpecificationStarter join(SqlTable joinTable, String tableAlias) {
402             return QueryExpressionDSL.this.join(joinTable, tableAlias);
403         }
404 
405         public JoinSpecificationStarter join(Buildable<SelectModel> joinTable, String tableAlias) {
406             return QueryExpressionDSL.this.join(joinTable, tableAlias);
407         }
408 
409         public JoinSpecificationStarter leftJoin(SqlTable joinTable) {
410             return QueryExpressionDSL.this.leftJoin(joinTable);
411         }
412 
413         public JoinSpecificationStarter leftJoin(SqlTable joinTable, String tableAlias) {
414             return QueryExpressionDSL.this.leftJoin(joinTable, tableAlias);
415         }
416 
417         public JoinSpecificationStarter leftJoin(Buildable<SelectModel> joinTable, String tableAlias) {
418             return QueryExpressionDSL.this.leftJoin(joinTable, tableAlias);
419         }
420 
421         public JoinSpecificationStarter rightJoin(SqlTable joinTable) {
422             return QueryExpressionDSL.this.rightJoin(joinTable);
423         }
424 
425         public JoinSpecificationStarter rightJoin(SqlTable joinTable, String tableAlias) {
426             return QueryExpressionDSL.this.rightJoin(joinTable, tableAlias);
427         }
428 
429         public JoinSpecificationStarter rightJoin(Buildable<SelectModel> joinTable, String tableAlias) {
430             return QueryExpressionDSL.this.rightJoin(joinTable, tableAlias);
431         }
432 
433         public JoinSpecificationStarter fullJoin(SqlTable joinTable) {
434             return QueryExpressionDSL.this.fullJoin(joinTable);
435         }
436 
437         public JoinSpecificationStarter fullJoin(SqlTable joinTable, String tableAlias) {
438             return QueryExpressionDSL.this.fullJoin(joinTable, tableAlias);
439         }
440 
441         public JoinSpecificationStarter fullJoin(Buildable<SelectModel> joinTable, String tableAlias) {
442             return QueryExpressionDSL.this.fullJoin(joinTable, tableAlias);
443         }
444 
445         public GroupByFinisher groupBy(BasicColumn... columns) {
446             return groupBy(Arrays.asList(columns));
447         }
448 
449         public GroupByFinisher groupBy(Collection<? extends BasicColumn> columns) {
450             return QueryExpressionDSL.this.groupBy(columns);
451         }
452 
453         public UnionBuilder union() {
454             return QueryExpressionDSL.this.union();
455         }
456 
457         public UnionBuilder unionAll() {
458             return QueryExpressionDSL.this.unionAll();
459         }
460 
461         public SelectDSL<R> orderBy(SortSpecification... columns) {
462             return orderBy(Arrays.asList(columns));
463         }
464 
465         public SelectDSL<R> orderBy(Collection<? extends SortSpecification> columns) {
466             return QueryExpressionDSL.this.orderBy(columns);
467         }
468 
469         @Override
470         protected JoinSpecificationFinisher getThis() {
471             return this;
472         }
473 
474         @Override
475         public SelectDSL<R> getSelectDSL() {
476             return QueryExpressionDSL.this.getSelectDSL();
477         }
478     }
479 
480     public class GroupByFinisher implements AbstractHavingStarter<QueryExpressionHavingBuilder>,
481             Buildable<R>, SelectDSLOperations<R> {
482         public SelectDSL<R> orderBy(SortSpecification... columns) {
483             return orderBy(Arrays.asList(columns));
484         }
485 
486         public SelectDSL<R> orderBy(Collection<? extends SortSpecification> columns) {
487             return QueryExpressionDSL.this.orderBy(columns);
488         }
489 
490         @Override
491         public R build() {
492             return QueryExpressionDSL.this.build();
493         }
494 
495         public UnionBuilder union() {
496             return QueryExpressionDSL.this.union();
497         }
498 
499         public UnionBuilder unionAll() {
500             return QueryExpressionDSL.this.unionAll();
501         }
502 
503         @Override
504         public QueryExpressionHavingBuilder having() {
505             return QueryExpressionDSL.this.having();
506         }
507 
508         @Override
509         public SelectDSL<R> getSelectDSL() {
510             return QueryExpressionDSL.this.getSelectDSL();
511         }
512     }
513 
514     public class UnionBuilder {
515         protected final String connector;
516 
517         public UnionBuilder(String connector) {
518             this.connector = connector;
519         }
520 
521         public FromGatherer<R> select(BasicColumn... selectList) {
522             return select(Arrays.asList(selectList));
523         }
524 
525         public FromGatherer<R> select(List<BasicColumn> selectList) {
526             return new FromGatherer.Builder<R>()
527                     .withConnector(connector)
528                     .withSelectList(selectList)
529                     .withSelectDSL(selectDSL)
530                     .build();
531         }
532 
533         public FromGatherer<R> selectDistinct(BasicColumn... selectList) {
534             return selectDistinct(Arrays.asList(selectList));
535         }
536 
537         public FromGatherer<R> selectDistinct(List<BasicColumn> selectList) {
538             return new FromGatherer.Builder<R>()
539                     .withConnector(connector)
540                     .withSelectList(selectList)
541                     .withSelectDSL(selectDSL)
542                     .isDistinct()
543                     .build();
544         }
545     }
546 
547     public class QueryExpressionHavingBuilder extends AbstractHavingFinisher<QueryExpressionHavingBuilder>
548             implements Buildable<R>, SelectDSLOperations<R> {
549 
550         public SelectDSL<R> orderBy(SortSpecification... columns) {
551             return orderBy(Arrays.asList(columns));
552         }
553 
554         public SelectDSL<R> orderBy(Collection<? extends SortSpecification> columns) {
555             return QueryExpressionDSL.this.orderBy(columns);
556         }
557 
558         public UnionBuilder union() {
559             return QueryExpressionDSL.this.union();
560         }
561 
562         public UnionBuilder unionAll() {
563             return QueryExpressionDSL.this.unionAll();
564         }
565 
566         @Override
567         public R build() {
568             return QueryExpressionDSL.this.build();
569         }
570 
571         @Override
572         protected QueryExpressionHavingBuilder getThis() {
573             return this;
574         }
575 
576         protected HavingModel buildHavingModel() {
577             return super.buildModel();
578         }
579 
580         @Override
581         public SelectDSL<R> getSelectDSL() {
582             return QueryExpressionDSL.this.getSelectDSL();
583         }
584     }
585 }