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.Collections;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Objects;
25  import java.util.Optional;
26  import java.util.function.Supplier;
27  
28  import org.jspecify.annotations.Nullable;
29  import org.mybatis.dynamic.sql.AndOrCriteriaGroup;
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.exception.DuplicateTableAliasException;
34  import org.mybatis.dynamic.sql.select.join.JoinModel;
35  import org.mybatis.dynamic.sql.select.join.JoinSpecification;
36  import org.mybatis.dynamic.sql.select.join.JoinType;
37  import org.mybatis.dynamic.sql.util.Buildable;
38  import org.mybatis.dynamic.sql.where.AbstractWhereFinisher;
39  import org.mybatis.dynamic.sql.where.AbstractWhereStarter;
40  
41  public abstract class AbstractQueryExpressionDSL<W extends AbstractWhereFinisher<?>,
42              T extends AbstractQueryExpressionDSL<W, T>>
43          implements AbstractWhereStarter<W, T> {
44  
45      private final List<Supplier<JoinSpecification>> joinSpecificationSuppliers = new ArrayList<>();
46      private final Map<SqlTable, String> tableAliases = new HashMap<>();
47      private final TableExpression table;
48  
49      protected AbstractQueryExpressionDSL(TableExpression table) {
50          this.table = Objects.requireNonNull(table);
51      }
52  
53      public TableExpression table() {
54          return table;
55      }
56  
57      public T join(SqlTable joinTable, SqlCriterion onJoinCriterion,
58                    AndOrCriteriaGroup... andJoinCriteria) {
59          addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.INNER, Arrays.asList(andJoinCriteria));
60          return getThis();
61      }
62  
63      public T join(SqlTable joinTable, String tableAlias, SqlCriterion onJoinCriterion,
64                    AndOrCriteriaGroup... andJoinCriteria) {
65          addTableAlias(joinTable, tableAlias);
66          return join(joinTable, onJoinCriterion, andJoinCriteria);
67      }
68  
69      public T join(SqlTable joinTable, @Nullable SqlCriterion onJoinCriterion,
70              List<AndOrCriteriaGroup> andJoinCriteria) {
71          addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.INNER, andJoinCriteria);
72          return getThis();
73      }
74  
75      public T join(SqlTable joinTable, String tableAlias, @Nullable SqlCriterion onJoinCriterion,
76              List<AndOrCriteriaGroup> andJoinCriteria) {
77          addTableAlias(joinTable, tableAlias);
78          return join(joinTable, onJoinCriterion, andJoinCriteria);
79      }
80  
81      public T join(Buildable<SelectModel> subQuery, @Nullable String tableAlias, @Nullable SqlCriterion onJoinCriterion,
82                    List<AndOrCriteriaGroup> andJoinCriteria) {
83          addJoinSpecificationSupplier(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.INNER,
84                  andJoinCriteria);
85          return getThis();
86      }
87  
88      public T leftJoin(SqlTable joinTable, SqlCriterion onJoinCriterion,
89                        AndOrCriteriaGroup... andJoinCriteria) {
90          addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.LEFT, Arrays.asList(andJoinCriteria));
91          return getThis();
92      }
93  
94      public T leftJoin(SqlTable joinTable, String tableAlias, SqlCriterion onJoinCriterion,
95                        AndOrCriteriaGroup... andJoinCriteria) {
96          addTableAlias(joinTable, tableAlias);
97          return leftJoin(joinTable, onJoinCriterion, andJoinCriteria);
98      }
99  
100     public T leftJoin(SqlTable joinTable, @Nullable SqlCriterion onJoinCriterion,
101             List<AndOrCriteriaGroup> andJoinCriteria) {
102         addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.LEFT, andJoinCriteria);
103         return getThis();
104     }
105 
106     public T leftJoin(SqlTable joinTable, String tableAlias, @Nullable SqlCriterion onJoinCriterion,
107             List<AndOrCriteriaGroup> andJoinCriteria) {
108         addTableAlias(joinTable, tableAlias);
109         return leftJoin(joinTable, onJoinCriterion, andJoinCriteria);
110     }
111 
112     public T leftJoin(Buildable<SelectModel> subQuery, @Nullable String tableAlias,
113                       @Nullable SqlCriterion onJoinCriterion, List<AndOrCriteriaGroup> andJoinCriteria) {
114         addJoinSpecificationSupplier(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.LEFT,
115                 andJoinCriteria);
116         return getThis();
117     }
118 
119     public T rightJoin(SqlTable joinTable, SqlCriterion onJoinCriterion,
120                        AndOrCriteriaGroup... andJoinCriteria) {
121         addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.RIGHT, Arrays.asList(andJoinCriteria));
122         return getThis();
123     }
124 
125     public T rightJoin(SqlTable joinTable, String tableAlias, SqlCriterion onJoinCriterion,
126                        AndOrCriteriaGroup... andJoinCriteria) {
127         addTableAlias(joinTable, tableAlias);
128         return rightJoin(joinTable, onJoinCriterion, andJoinCriteria);
129     }
130 
131     public T rightJoin(SqlTable joinTable, @Nullable SqlCriterion onJoinCriterion,
132             List<AndOrCriteriaGroup> andJoinCriteria) {
133         addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.RIGHT, andJoinCriteria);
134         return getThis();
135     }
136 
137     public T rightJoin(SqlTable joinTable, String tableAlias, @Nullable SqlCriterion onJoinCriterion,
138             List<AndOrCriteriaGroup> andJoinCriteria) {
139         addTableAlias(joinTable, tableAlias);
140         return rightJoin(joinTable, onJoinCriterion, andJoinCriteria);
141     }
142 
143     public T rightJoin(Buildable<SelectModel> subQuery, @Nullable String tableAlias,
144                        @Nullable SqlCriterion onJoinCriterion, List<AndOrCriteriaGroup> andJoinCriteria) {
145         addJoinSpecificationSupplier(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.RIGHT,
146                 andJoinCriteria);
147         return getThis();
148     }
149 
150     public T fullJoin(SqlTable joinTable, SqlCriterion onJoinCriterion,
151                       AndOrCriteriaGroup... andJoinCriteria) {
152         addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.FULL, Arrays.asList(andJoinCriteria));
153         return getThis();
154     }
155 
156     public T fullJoin(SqlTable joinTable, String tableAlias, SqlCriterion onJoinCriterion,
157                       AndOrCriteriaGroup... andJoinCriteria) {
158         addTableAlias(joinTable, tableAlias);
159         return fullJoin(joinTable, onJoinCriterion, andJoinCriteria);
160     }
161 
162     public T fullJoin(SqlTable joinTable, @Nullable SqlCriterion onJoinCriterion,
163             List<AndOrCriteriaGroup> andJoinCriteria) {
164         addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.FULL, andJoinCriteria);
165         return getThis();
166     }
167 
168     public T fullJoin(SqlTable joinTable, String tableAlias, @Nullable SqlCriterion onJoinCriterion,
169             List<AndOrCriteriaGroup> andJoinCriteria) {
170         addTableAlias(joinTable, tableAlias);
171         return fullJoin(joinTable, onJoinCriterion, andJoinCriteria);
172     }
173 
174     public T fullJoin(Buildable<SelectModel> subQuery, @Nullable String tableAlias,
175                       @Nullable SqlCriterion onJoinCriterion, List<AndOrCriteriaGroup> andJoinCriteria) {
176         addJoinSpecificationSupplier(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.FULL,
177                 andJoinCriteria);
178         return getThis();
179     }
180 
181     private void addJoinSpecificationSupplier(TableExpression joinTable, @Nullable SqlCriterion onJoinCriterion,
182                                               JoinType joinType, List<AndOrCriteriaGroup> andJoinCriteria) {
183         joinSpecificationSuppliers.add(() -> new JoinSpecification.Builder()
184                 .withJoinTable(joinTable)
185                 .withJoinType(joinType)
186                 .withInitialCriterion(onJoinCriterion)
187                 .withSubCriteria(andJoinCriteria).build());
188     }
189 
190     protected void addJoinSpecificationSupplier(Supplier<JoinSpecification> joinSpecificationSupplier) {
191         joinSpecificationSuppliers.add(joinSpecificationSupplier);
192     }
193 
194     protected Optional<JoinModel> buildJoinModel() {
195         if (joinSpecificationSuppliers.isEmpty()) {
196             return Optional.empty();
197         }
198 
199         return Optional.of(JoinModel.of(joinSpecificationSuppliers.stream()
200                 .map(Supplier::get)
201                 .toList()));
202     }
203 
204     protected void addTableAlias(SqlTable table, String tableAlias) {
205         if (tableAliases.containsKey(table)) {
206             throw new DuplicateTableAliasException(table, tableAlias, tableAliases.get(table));
207         }
208 
209         tableAliases.put(table, tableAlias);
210     }
211 
212     protected Map<SqlTable, String> tableAliases() {
213         return Collections.unmodifiableMap(tableAliases);
214     }
215 
216     protected static SubQuery buildSubQuery(Buildable<SelectModel> selectModel) {
217         return new SubQuery.Builder()
218                 .withSelectModel(selectModel.build())
219                 .build();
220     }
221 
222     protected static SubQuery buildSubQuery(Buildable<SelectModel> selectModel, @Nullable String alias) {
223         return new SubQuery.Builder()
224                 .withSelectModel(selectModel.build())
225                 .withAlias(alias)
226                 .build();
227     }
228 
229     protected abstract T getThis();
230 }