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;
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(InvalidSqlException.class).isThrownBy(builder::build)
191                 .withMessage(Messages.getString("ERROR.16"));
192     }
193     @Test
194     void testInvalidSelectStatementWithEmptyOrderByList() {
195         List<SortSpecification> list = Collections.emptyList();
196         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(() -> OrderByModel.of(list))
197                 .withMessage(Messages.getString("ERROR.12"));
198     }
199 
200     @Test
201     void testInvalidSelectStatementWithEmptyGroupByList() {
202         List<BasicColumn> list = Collections.emptyList();
203         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(() -> GroupByModel.of(list))
204                 .withMessage(Messages.getString("ERROR.11"));
205     }
206 
207     @Test
208     void testInvalidUpdateStatement() {
209         UpdateModel.Builder builder = new UpdateModel.Builder()
210                 .withTable(person);
211 
212         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(builder::build)
213                 .withMessage(Messages.getString("ERROR.17"));
214     }
215 
216     @Test
217     void testInvalidUpdateStatementWhenAllOptionalsAreDropped() {
218         UpdateModel model = update(person)
219                 .set(id).equalToWhenPresent((Integer) null)
220                 .build();
221 
222         assertThatExceptionOfType(InvalidSqlException.class)
223                 .isThrownBy(() -> model.render(RenderingStrategies.SPRING_NAMED_PARAMETER))
224                 .withMessage(Messages.getString("ERROR.18"));
225     }
226 
227     @Test
228     void testMissingMessage() {
229         assertThatExceptionOfType(MissingResourceException.class)
230                 .isThrownBy(() -> Messages.getString("MISSING_MESSAGE"));
231     }
232 
233     @Test
234     void testInvalidPagingModel() {
235         Optional<PagingModel> pagingModel = new PagingModel.Builder().withLimit(22L).build();
236 
237         RenderingContext renderingContext = RenderingContext
238                 .withRenderingStrategy(RenderingStrategies.MYBATIS3)
239                 .withStatementConfiguration(new StatementConfiguration())
240                 .build();
241 
242         assertThat(pagingModel).hasValueSatisfying(pm -> {
243             FetchFirstPagingModelRenderer renderer = new FetchFirstPagingModelRenderer(renderingContext, pm);
244 
245             assertThatExceptionOfType(InvalidSqlException.class)
246                     .isThrownBy(renderer::render)
247                     .withMessage(Messages.getInternalErrorString(InternalError.INTERNAL_ERROR_13));
248         });
249     }
250 
251     @Test
252     void testInvalidValueAlias() {
253         BoundValue<Integer> foo = value(1);
254 
255         assertThat(foo.alias()).isEmpty();
256         assertThatExceptionOfType(InvalidSqlException.class)
257                 .isThrownBy(() -> foo.as("foo"))
258                 .withMessage(Messages.getString("ERROR.38"));
259     }
260 
261     @Test
262     void testInvalidDoubleForUpdate() {
263         var dsl = select(id).from(person).limit(2).forUpdate();
264         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(dsl::forUpdate)
265         .withMessage(Messages.getString("ERROR.48"));
266     }
267 
268     @Test
269     void testInvalidDoubleForShare() {
270         var dsl = select(id).from(person).offset(2).forShare();
271         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(dsl::forShare)
272                 .withMessage(Messages.getString("ERROR.48"));
273     }
274 
275     @Test
276     void testInvalidDoubleForKeyShare() {
277         var dsl = select(id).from(person).forKeyShare();
278         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(dsl::forKeyShare)
279                 .withMessage(Messages.getString("ERROR.48"));
280     }
281 
282     @Test
283     void testInvalidDoubleForNoKeyUpdate() {
284         var dsl = select(id).from(person).where(id, isEqualTo(1)).forNoKeyUpdate();
285         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(dsl::forNoKeyUpdate)
286                 .withMessage(Messages.getString("ERROR.48"));
287     }
288 
289     @Test
290     void testInvalidDoubleForNoKeyUpdateAfterJoin() {
291         var dsl = select(id).from(person).join(person).on(id, isEqualTo(id)).skipLocked();
292         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(dsl::skipLocked)
293                 .withMessage(Messages.getString("ERROR.49"));
294     }
295 
296     @Test
297     void testInvalidDoubleForNoKeyUpdateAfterGroupBy() {
298         var dsl = select(id).from(person).groupBy(id).nowait();
299         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(dsl::nowait)
300                 .withMessage(Messages.getString("ERROR.49"));
301     }
302 
303     @Test
304     void testInvalidDoubleForNoKeyUpdateAfterHaving() {
305         var dsl = select(id).from(person).groupBy(id).having(id, isEqualTo(2)).nowait();
306         assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(dsl::nowait)
307                 .withMessage(Messages.getString("ERROR.49"));
308     }
309 
310     static class TestRow {
311         private @Nullable Integer id;
312 
313         public @Nullable Integer getId() {
314             return id;
315         }
316 
317         public void setId(Integer id) {
318             this.id = id;
319         }
320     }
321 }