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;
17  
18  import static org.assertj.core.api.Assertions.assertThat;
19  import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
20  import static org.mybatis.dynamic.sql.SqlBuilder.*;
21  
22  import java.util.Collections;
23  import java.util.List;
24  import java.util.MissingResourceException;
25  import java.util.Optional;
26  
27  import org.jspecify.annotations.Nullable;
28  import org.junit.jupiter.api.Test;
29  import org.mybatis.dynamic.sql.common.OrderByModel;
30  import org.mybatis.dynamic.sql.configuration.StatementConfiguration;
31  import org.mybatis.dynamic.sql.exception.InvalidSqlException;
32  import org.mybatis.dynamic.sql.insert.BatchInsertModel;
33  import org.mybatis.dynamic.sql.insert.GeneralInsertModel;
34  import org.mybatis.dynamic.sql.insert.InsertColumnListModel;
35  import org.mybatis.dynamic.sql.insert.InsertModel;
36  import org.mybatis.dynamic.sql.insert.MultiRowInsertModel;
37  import org.mybatis.dynamic.sql.render.RenderingContext;
38  import org.mybatis.dynamic.sql.render.RenderingStrategies;
39  import org.mybatis.dynamic.sql.select.GroupByModel;
40  import org.mybatis.dynamic.sql.select.PagingModel;
41  import org.mybatis.dynamic.sql.select.QueryExpressionModel;
42  import org.mybatis.dynamic.sql.select.SelectModel;
43  import org.mybatis.dynamic.sql.select.join.JoinModel;
44  import org.mybatis.dynamic.sql.select.join.JoinSpecification;
45  import org.mybatis.dynamic.sql.select.join.JoinType;
46  import org.mybatis.dynamic.sql.select.render.FetchFirstPagingModelRenderer;
47  import org.mybatis.dynamic.sql.update.UpdateModel;
48  import org.mybatis.dynamic.sql.util.InternalError;
49  import org.mybatis.dynamic.sql.util.Messages;
50  
51  class InvalidSQLTest {
52  
53      private static final SqlTable person = new SqlTable("person");
54      private static final SqlColumn<Integer> id = person.column("id");
55  
56      @Test
57      void testInvalidGeneralInsertStatement() {
58          GeneralInsertModel.Builder builder = new GeneralInsertModel.Builder()
59                  .withTable(person);
60  
61          assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(builder::build)
62                  .withMessage(Messages.getString("ERROR.6"));
63      }
64  
65      @Test
66      void testInvalidGeneralInsertStatementWhenAllOptionalsAreDropped() {
67          GeneralInsertModel model = insertInto(person)
68                  .set(id).toValueWhenPresent((Integer) null)
69                  .build();
70  
71          assertThatExceptionOfType(InvalidSqlException.class)
72                  .isThrownBy(() -> model.render(RenderingStrategies.SPRING_NAMED_PARAMETER))
73                  .withMessage(Messages.getString("ERROR.9"));
74      }
75  
76      @Test
77      void testInvalidInsertStatement() {
78          InsertModel.Builder<TestRow> builder = new InsertModel.Builder<TestRow>()
79                  .withTable(person)
80                  .withRow(new TestRow());
81  
82          assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(builder::build)
83                  .withMessage(Messages.getString("ERROR.7"));
84      }
85  
86      @Test
87      void testInvalidInsertStatementWhenAllOptionalsAreDropped() {
88          TestRow testRow = new TestRow();
89  
90          InsertModel<TestRow> model = insert(testRow)
91                  .into(person)
92                  .map(id).toPropertyWhenPresent("id", testRow::getId)
93                  .build();
94  
95          assertThatExceptionOfType(InvalidSqlException.class)
96                  .isThrownBy(() -> model.render(RenderingStrategies.SPRING_NAMED_PARAMETER))
97                  .withMessage(Messages.getString("ERROR.10"));
98      }
99  
100     @Test
101     void testInvalidMultipleInsertStatementNoRecords() {
102         MultiRowInsertModel.Builder<TestRow> builder = new MultiRowInsertModel.Builder<TestRow>()
103                 .withTable(person);
104 
105         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(builder::build)
106                 .withMessage(Messages.getString("ERROR.20"));
107     }
108 
109     @Test
110     void testInvalidMultipleInsertStatementNoMappings() {
111         List<TestRow> records = List.of(new TestRow());
112 
113         MultiRowInsertModel.Builder<TestRow> builder = new MultiRowInsertModel.Builder<TestRow>()
114                 .withRecords(records)
115                 .withTable(person);
116 
117         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(builder::build)
118                 .withMessage(Messages.getString("ERROR.8"));
119     }
120 
121     @Test
122     void testInvalidBatchInsertStatementNoRecords() {
123         BatchInsertModel.Builder<TestRow> builder = new BatchInsertModel.Builder<TestRow>()
124                 .withTable(person);
125 
126         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(builder::build)
127                 .withMessage(Messages.getString("ERROR.19"));
128     }
129 
130     @Test
131     void testInvalidBatchInsertStatementNoMappings() {
132         List<TestRow> records = List.of(new TestRow());
133 
134         BatchInsertModel.Builder<TestRow> builder = new BatchInsertModel.Builder<TestRow>()
135                 .withRecords(records)
136                 .withTable(person);
137 
138         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(builder::build)
139                 .withMessage(Messages.getString("ERROR.5"));
140     }
141 
142     @Test
143     void testInvalidEmptyInsertColumnList() {
144         List<SqlColumn<?>> list = Collections.emptyList();
145         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(() -> InsertColumnListModel.of(list))
146                 .withMessage(Messages.getString("ERROR.4"));
147     }
148 
149     @Test
150     void testInvalidNullInsertColumnList() {
151         assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> InsertColumnListModel.of(null));
152     }
153 
154     @Test
155     void testInvalidSelectStatementWithoutQueryExpressions() {
156         SelectModel.Builder builder =
157                 new SelectModel.Builder().withStatementConfiguration(new StatementConfiguration());
158 
159         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(builder::build)
160                 .withMessage(Messages.getString("ERROR.14"));
161     }
162 
163     @Test
164     void testInvalidSelectStatementWithoutColumnList() {
165         QueryExpressionModel.Builder builder = new QueryExpressionModel.Builder()
166                 .withTable(person);
167 
168         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(builder::build)
169                 .withMessage(Messages.getString("ERROR.13"));
170     }
171 
172     @Test
173     void testInvalidSelectStatementEmptyJoinModel() {
174         List<JoinSpecification> list = Collections.emptyList();
175         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(() -> JoinModel.of(list))
176                 .withMessage(Messages.getString("ERROR.15"));
177     }
178 
179     @Test
180     void testInvalidSelectStatementNullJoinModel() {
181         assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> JoinModel.of(null));
182     }
183 
184     @Test
185     void testInvalidSelectStatementJoinSpecification() {
186         JoinSpecification.Builder builder = new JoinSpecification.Builder()
187                 .withJoinTable(person)
188                 .withJoinType(JoinType.LEFT);
189 
190         assertThatExceptionOfType(NullPointerException.class).isThrownBy(builder::build);
191     }
192     @Test
193     void testInvalidSelectStatementWithEmptyOrderByList() {
194         List<SortSpecification> list = Collections.emptyList();
195         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(() -> OrderByModel.of(list))
196                 .withMessage(Messages.getString("ERROR.12"));
197     }
198 
199     @Test
200     void testInvalidSelectStatementWithEmptyGroupByList() {
201         List<BasicColumn> list = Collections.emptyList();
202         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(() -> GroupByModel.of(list))
203                 .withMessage(Messages.getString("ERROR.11"));
204     }
205 
206     @Test
207     void testInvalidUpdateStatement() {
208         UpdateModel.Builder builder = new UpdateModel.Builder()
209                 .withTable(person);
210 
211         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(builder::build)
212                 .withMessage(Messages.getString("ERROR.17"));
213     }
214 
215     @Test
216     void testInvalidUpdateStatementWhenAllOptionalsAreDropped() {
217         UpdateModel model = update(person)
218                 .set(id).equalToWhenPresent((Integer) null)
219                 .build();
220 
221         assertThatExceptionOfType(InvalidSqlException.class)
222                 .isThrownBy(() -> model.render(RenderingStrategies.SPRING_NAMED_PARAMETER))
223                 .withMessage(Messages.getString("ERROR.18"));
224     }
225 
226     @Test
227     void testMissingMessage() {
228         assertThatExceptionOfType(MissingResourceException.class)
229                 .isThrownBy(() -> Messages.getString("MISSING_MESSAGE"));
230     }
231 
232     @Test
233     void testInvalidPagingModel() {
234         Optional<PagingModel> pagingModel = new PagingModel.Builder().withLimit(22L).build();
235 
236         RenderingContext renderingContext = RenderingContext
237                 .withRenderingStrategy(RenderingStrategies.MYBATIS3)
238                 .withStatementConfiguration(new StatementConfiguration())
239                 .build();
240 
241         assertThat(pagingModel).hasValueSatisfying(pm -> {
242             FetchFirstPagingModelRenderer renderer = new FetchFirstPagingModelRenderer(renderingContext, pm);
243 
244             assertThatExceptionOfType(InvalidSqlException.class)
245                     .isThrownBy(renderer::render)
246                     .withMessage(Messages.getInternalErrorString(InternalError.INTERNAL_ERROR_13));
247         });
248     }
249 
250     @Test
251     void testInvalidValueAlias() {
252         BoundValue<Integer> foo = value(1);
253 
254         assertThat(foo.alias()).isEmpty();
255         assertThatExceptionOfType(InvalidSqlException.class)
256                 .isThrownBy(() -> foo.as("foo"))
257                 .withMessage(Messages.getString("ERROR.38"));
258     }
259 
260     @Test
261     void testInvalidDoubleForUpdate() {
262         var dsl = select(id).from(person).limit(2).forUpdate();
263         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(dsl::forUpdate)
264         .withMessage(Messages.getString("ERROR.48"));
265     }
266 
267     @Test
268     void testInvalidDoubleForShare() {
269         var dsl = select(id).from(person).offset(2).forShare();
270         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(dsl::forShare)
271                 .withMessage(Messages.getString("ERROR.48"));
272     }
273 
274     @Test
275     void testInvalidDoubleForKeyShare() {
276         var dsl = select(id).from(person).forKeyShare();
277         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(dsl::forKeyShare)
278                 .withMessage(Messages.getString("ERROR.48"));
279     }
280 
281     @Test
282     void testInvalidDoubleForNoKeyUpdate() {
283         var dsl = select(id).from(person).where(id, isEqualTo(1)).forNoKeyUpdate();
284         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(dsl::forNoKeyUpdate)
285                 .withMessage(Messages.getString("ERROR.48"));
286     }
287 
288     @Test
289     void testInvalidDoubleForNoKeyUpdateAfterJoin() {
290         var dsl = select(id).from(person).join(person).on(id, isEqualTo(id)).skipLocked();
291         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(dsl::skipLocked)
292                 .withMessage(Messages.getString("ERROR.49"));
293     }
294 
295     @Test
296     void testInvalidDoubleForNoKeyUpdateAfterGroupBy() {
297         var dsl = select(id).from(person).groupBy(id).nowait();
298         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(dsl::nowait)
299                 .withMessage(Messages.getString("ERROR.49"));
300     }
301 
302     @Test
303     void testInvalidDoubleForNoKeyUpdateAfterHaving() {
304         var dsl = select(id).from(person).groupBy(id).having(id, isEqualTo(2)).nowait();
305         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(dsl::nowait)
306                 .withMessage(Messages.getString("ERROR.49"));
307     }
308 
309     static class TestRow {
310         private @Nullable Integer id;
311 
312         public @Nullable Integer getId() {
313             return id;
314         }
315 
316         public void setId(Integer id) {
317             this.id = id;
318         }
319     }
320 }