View Javadoc
1   /*
2    *    Copyright 2009-2023 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.apache.ibatis.jdbc;
17  
18  import java.io.IOException;
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.List;
23  
24  /**
25   * @author Clinton Begin
26   * @author Jeff Butler
27   * @author Adam Gent
28   * @author Kazuki Shimizu
29   */
30  public abstract class AbstractSQL<T> {
31  
32    private static final String AND = ") \nAND (";
33    private static final String OR = ") \nOR (";
34  
35    private final SQLStatement sql = new SQLStatement();
36  
37    public abstract T getSelf();
38  
39    public T UPDATE(String table) {
40      sql().statementType = SQLStatement.StatementType.UPDATE;
41      sql().tables.add(table);
42      return getSelf();
43    }
44  
45    public T SET(String sets) {
46      sql().sets.add(sets);
47      return getSelf();
48    }
49  
50    /**
51     * Sets the.
52     *
53     * @param sets
54     *          the sets
55     *
56     * @return the t
57     *
58     * @since 3.4.2
59     */
60    public T SET(String... sets) {
61      sql().sets.addAll(Arrays.asList(sets));
62      return getSelf();
63    }
64  
65    public T INSERT_INTO(String tableName) {
66      sql().statementType = SQLStatement.StatementType.INSERT;
67      sql().tables.add(tableName);
68      return getSelf();
69    }
70  
71    public T VALUES(String columns, String values) {
72      INTO_COLUMNS(columns);
73      INTO_VALUES(values);
74      return getSelf();
75    }
76  
77    /**
78     * Into columns.
79     *
80     * @param columns
81     *          the columns
82     *
83     * @return the t
84     *
85     * @since 3.4.2
86     */
87    public T INTO_COLUMNS(String... columns) {
88      sql().columns.addAll(Arrays.asList(columns));
89      return getSelf();
90    }
91  
92    /**
93     * Into values.
94     *
95     * @param values
96     *          the values
97     *
98     * @return the t
99     *
100    * @since 3.4.2
101    */
102   public T INTO_VALUES(String... values) {
103     List<String> list = sql().valuesList.get(sql().valuesList.size() - 1);
104     Collections.addAll(list, values);
105     return getSelf();
106   }
107 
108   public T SELECT(String columns) {
109     sql().statementType = SQLStatement.StatementType.SELECT;
110     sql().select.add(columns);
111     return getSelf();
112   }
113 
114   /**
115    * Select.
116    *
117    * @param columns
118    *          the columns
119    *
120    * @return the t
121    *
122    * @since 3.4.2
123    */
124   public T SELECT(String... columns) {
125     sql().statementType = SQLStatement.StatementType.SELECT;
126     sql().select.addAll(Arrays.asList(columns));
127     return getSelf();
128   }
129 
130   public T SELECT_DISTINCT(String columns) {
131     sql().distinct = true;
132     SELECT(columns);
133     return getSelf();
134   }
135 
136   /**
137    * Select distinct.
138    *
139    * @param columns
140    *          the columns
141    *
142    * @return the t
143    *
144    * @since 3.4.2
145    */
146   public T SELECT_DISTINCT(String... columns) {
147     sql().distinct = true;
148     SELECT(columns);
149     return getSelf();
150   }
151 
152   public T DELETE_FROM(String table) {
153     sql().statementType = SQLStatement.StatementType.DELETE;
154     sql().tables.add(table);
155     return getSelf();
156   }
157 
158   public T FROM(String table) {
159     sql().tables.add(table);
160     return getSelf();
161   }
162 
163   /**
164    * From.
165    *
166    * @param tables
167    *          the tables
168    *
169    * @return the t
170    *
171    * @since 3.4.2
172    */
173   public T FROM(String... tables) {
174     sql().tables.addAll(Arrays.asList(tables));
175     return getSelf();
176   }
177 
178   public T JOIN(String join) {
179     sql().join.add(join);
180     return getSelf();
181   }
182 
183   /**
184    * Join.
185    *
186    * @param joins
187    *          the joins
188    *
189    * @return the t
190    *
191    * @since 3.4.2
192    */
193   public T JOIN(String... joins) {
194     sql().join.addAll(Arrays.asList(joins));
195     return getSelf();
196   }
197 
198   public T INNER_JOIN(String join) {
199     sql().innerJoin.add(join);
200     return getSelf();
201   }
202 
203   /**
204    * Inner join.
205    *
206    * @param joins
207    *          the joins
208    *
209    * @return the t
210    *
211    * @since 3.4.2
212    */
213   public T INNER_JOIN(String... joins) {
214     sql().innerJoin.addAll(Arrays.asList(joins));
215     return getSelf();
216   }
217 
218   public T LEFT_OUTER_JOIN(String join) {
219     sql().leftOuterJoin.add(join);
220     return getSelf();
221   }
222 
223   /**
224    * Left outer join.
225    *
226    * @param joins
227    *          the joins
228    *
229    * @return the t
230    *
231    * @since 3.4.2
232    */
233   public T LEFT_OUTER_JOIN(String... joins) {
234     sql().leftOuterJoin.addAll(Arrays.asList(joins));
235     return getSelf();
236   }
237 
238   public T RIGHT_OUTER_JOIN(String join) {
239     sql().rightOuterJoin.add(join);
240     return getSelf();
241   }
242 
243   /**
244    * Right outer join.
245    *
246    * @param joins
247    *          the joins
248    *
249    * @return the t
250    *
251    * @since 3.4.2
252    */
253   public T RIGHT_OUTER_JOIN(String... joins) {
254     sql().rightOuterJoin.addAll(Arrays.asList(joins));
255     return getSelf();
256   }
257 
258   public T OUTER_JOIN(String join) {
259     sql().outerJoin.add(join);
260     return getSelf();
261   }
262 
263   /**
264    * Outer join.
265    *
266    * @param joins
267    *          the joins
268    *
269    * @return the t
270    *
271    * @since 3.4.2
272    */
273   public T OUTER_JOIN(String... joins) {
274     sql().outerJoin.addAll(Arrays.asList(joins));
275     return getSelf();
276   }
277 
278   public T WHERE(String conditions) {
279     sql().where.add(conditions);
280     sql().lastList = sql().where;
281     return getSelf();
282   }
283 
284   /**
285    * Where.
286    *
287    * @param conditions
288    *          the conditions
289    *
290    * @return the t
291    *
292    * @since 3.4.2
293    */
294   public T WHERE(String... conditions) {
295     sql().where.addAll(Arrays.asList(conditions));
296     sql().lastList = sql().where;
297     return getSelf();
298   }
299 
300   public T OR() {
301     sql().lastList.add(OR);
302     return getSelf();
303   }
304 
305   public T AND() {
306     sql().lastList.add(AND);
307     return getSelf();
308   }
309 
310   public T GROUP_BY(String columns) {
311     sql().groupBy.add(columns);
312     return getSelf();
313   }
314 
315   /**
316    * Group by.
317    *
318    * @param columns
319    *          the columns
320    *
321    * @return the t
322    *
323    * @since 3.4.2
324    */
325   public T GROUP_BY(String... columns) {
326     sql().groupBy.addAll(Arrays.asList(columns));
327     return getSelf();
328   }
329 
330   public T HAVING(String conditions) {
331     sql().having.add(conditions);
332     sql().lastList = sql().having;
333     return getSelf();
334   }
335 
336   /**
337    * Having.
338    *
339    * @param conditions
340    *          the conditions
341    *
342    * @return the t
343    *
344    * @since 3.4.2
345    */
346   public T HAVING(String... conditions) {
347     sql().having.addAll(Arrays.asList(conditions));
348     sql().lastList = sql().having;
349     return getSelf();
350   }
351 
352   public T ORDER_BY(String columns) {
353     sql().orderBy.add(columns);
354     return getSelf();
355   }
356 
357   /**
358    * Order by.
359    *
360    * @param columns
361    *          the columns
362    *
363    * @return the t
364    *
365    * @since 3.4.2
366    */
367   public T ORDER_BY(String... columns) {
368     sql().orderBy.addAll(Arrays.asList(columns));
369     return getSelf();
370   }
371 
372   /**
373    * Set the limit variable string(e.g. {@code "#{limit}"}).
374    *
375    * @param variable
376    *          a limit variable string
377    *
378    * @return a self instance
379    *
380    * @see #OFFSET(String)
381    *
382    * @since 3.5.2
383    */
384   public T LIMIT(String variable) {
385     sql().limit = variable;
386     sql().limitingRowsStrategy = SQLStatement.LimitingRowsStrategy.OFFSET_LIMIT;
387     return getSelf();
388   }
389 
390   /**
391    * Set the limit value.
392    *
393    * @param value
394    *          an offset value
395    *
396    * @return a self instance
397    *
398    * @see #OFFSET(long)
399    *
400    * @since 3.5.2
401    */
402   public T LIMIT(int value) {
403     return LIMIT(String.valueOf(value));
404   }
405 
406   /**
407    * Set the offset variable string(e.g. {@code "#{offset}"}).
408    *
409    * @param variable
410    *          a offset variable string
411    *
412    * @return a self instance
413    *
414    * @see #LIMIT(String)
415    *
416    * @since 3.5.2
417    */
418   public T OFFSET(String variable) {
419     sql().offset = variable;
420     sql().limitingRowsStrategy = SQLStatement.LimitingRowsStrategy.OFFSET_LIMIT;
421     return getSelf();
422   }
423 
424   /**
425    * Set the offset value.
426    *
427    * @param value
428    *          an offset value
429    *
430    * @return a self instance
431    *
432    * @see #LIMIT(int)
433    *
434    * @since 3.5.2
435    */
436   public T OFFSET(long value) {
437     return OFFSET(String.valueOf(value));
438   }
439 
440   /**
441    * Set the fetch first rows variable string(e.g. {@code "#{fetchFirstRows}"}).
442    *
443    * @param variable
444    *          a fetch first rows variable string
445    *
446    * @return a self instance
447    *
448    * @see #OFFSET_ROWS(String)
449    *
450    * @since 3.5.2
451    */
452   public T FETCH_FIRST_ROWS_ONLY(String variable) {
453     sql().limit = variable;
454     sql().limitingRowsStrategy = SQLStatement.LimitingRowsStrategy.ISO;
455     return getSelf();
456   }
457 
458   /**
459    * Set the fetch first rows value.
460    *
461    * @param value
462    *          a fetch first rows value
463    *
464    * @return a self instance
465    *
466    * @see #OFFSET_ROWS(long)
467    *
468    * @since 3.5.2
469    */
470   public T FETCH_FIRST_ROWS_ONLY(int value) {
471     return FETCH_FIRST_ROWS_ONLY(String.valueOf(value));
472   }
473 
474   /**
475    * Set the offset rows variable string(e.g. {@code "#{offset}"}).
476    *
477    * @param variable
478    *          a offset rows variable string
479    *
480    * @return a self instance
481    *
482    * @see #FETCH_FIRST_ROWS_ONLY(String)
483    *
484    * @since 3.5.2
485    */
486   public T OFFSET_ROWS(String variable) {
487     sql().offset = variable;
488     sql().limitingRowsStrategy = SQLStatement.LimitingRowsStrategy.ISO;
489     return getSelf();
490   }
491 
492   /**
493    * Set the offset rows value.
494    *
495    * @param value
496    *          an offset rows value
497    *
498    * @return a self instance
499    *
500    * @see #FETCH_FIRST_ROWS_ONLY(int)
501    *
502    * @since 3.5.2
503    */
504   public T OFFSET_ROWS(long value) {
505     return OFFSET_ROWS(String.valueOf(value));
506   }
507 
508   /**
509    * used to add a new inserted row while do multi-row insert.
510    *
511    * @return the t
512    *
513    * @since 3.5.2
514    */
515   public T ADD_ROW() {
516     sql().valuesList.add(new ArrayList<>());
517     return getSelf();
518   }
519 
520   private SQLStatement sql() {
521     return sql;
522   }
523 
524   public <A extends Appendable> A usingAppender(A a) {
525     sql().sql(a);
526     return a;
527   }
528 
529   @Override
530   public String toString() {
531     StringBuilder sb = new StringBuilder();
532     sql().sql(sb);
533     return sb.toString();
534   }
535 
536   private static class SafeAppendable {
537     private final Appendable appendable;
538     private boolean empty = true;
539 
540     public SafeAppendable(Appendable a) {
541       this.appendable = a;
542     }
543 
544     public SafeAppendable append(CharSequence s) {
545       try {
546         if (empty && s.length() > 0) {
547           empty = false;
548         }
549         appendable.append(s);
550       } catch (IOException e) {
551         throw new RuntimeException(e);
552       }
553       return this;
554     }
555 
556     public boolean isEmpty() {
557       return empty;
558     }
559 
560   }
561 
562   private static class SQLStatement {
563 
564     public enum StatementType {
565 
566       DELETE,
567 
568       INSERT,
569 
570       SELECT,
571 
572       UPDATE
573 
574     }
575 
576     private enum LimitingRowsStrategy {
577       NOP {
578         @Override
579         protected void appendClause(SafeAppendable builder, String offset, String limit) {
580           // NOP
581         }
582       },
583       ISO {
584         @Override
585         protected void appendClause(SafeAppendable builder, String offset, String limit) {
586           if (offset != null) {
587             builder.append(" OFFSET ").append(offset).append(" ROWS");
588           }
589           if (limit != null) {
590             builder.append(" FETCH FIRST ").append(limit).append(" ROWS ONLY");
591           }
592         }
593       },
594       OFFSET_LIMIT {
595         @Override
596         protected void appendClause(SafeAppendable builder, String offset, String limit) {
597           if (limit != null) {
598             builder.append(" LIMIT ").append(limit);
599           }
600           if (offset != null) {
601             builder.append(" OFFSET ").append(offset);
602           }
603         }
604       };
605 
606       protected abstract void appendClause(SafeAppendable builder, String offset, String limit);
607 
608     }
609 
610     StatementType statementType;
611     List<String> sets = new ArrayList<>();
612     List<String> select = new ArrayList<>();
613     List<String> tables = new ArrayList<>();
614     List<String> join = new ArrayList<>();
615     List<String> innerJoin = new ArrayList<>();
616     List<String> outerJoin = new ArrayList<>();
617     List<String> leftOuterJoin = new ArrayList<>();
618     List<String> rightOuterJoin = new ArrayList<>();
619     List<String> where = new ArrayList<>();
620     List<String> having = new ArrayList<>();
621     List<String> groupBy = new ArrayList<>();
622     List<String> orderBy = new ArrayList<>();
623     List<String> lastList = new ArrayList<>();
624     List<String> columns = new ArrayList<>();
625     List<List<String>> valuesList = new ArrayList<>();
626     boolean distinct;
627     String offset;
628     String limit;
629     LimitingRowsStrategy limitingRowsStrategy = LimitingRowsStrategy.NOP;
630 
631     public SQLStatement() {
632       // Prevent Synthetic Access
633       valuesList.add(new ArrayList<>());
634     }
635 
636     private void sqlClause(SafeAppendable builder, String keyword, List<String> parts, String open, String close,
637         String conjunction) {
638       if (!parts.isEmpty()) {
639         if (!builder.isEmpty()) {
640           builder.append("\n");
641         }
642         builder.append(keyword);
643         builder.append(" ");
644         builder.append(open);
645         String last = "________";
646         for (int i = 0, n = parts.size(); i < n; i++) {
647           String part = parts.get(i);
648           if (i > 0 && !part.equals(AND) && !part.equals(OR) && !last.equals(AND) && !last.equals(OR)) {
649             builder.append(conjunction);
650           }
651           builder.append(part);
652           last = part;
653         }
654         builder.append(close);
655       }
656     }
657 
658     private String selectSQL(SafeAppendable builder) {
659       if (distinct) {
660         sqlClause(builder, "SELECT DISTINCT", select, "", "", ", ");
661       } else {
662         sqlClause(builder, "SELECT", select, "", "", ", ");
663       }
664 
665       sqlClause(builder, "FROM", tables, "", "", ", ");
666       joins(builder);
667       sqlClause(builder, "WHERE", where, "(", ")", " AND ");
668       sqlClause(builder, "GROUP BY", groupBy, "", "", ", ");
669       sqlClause(builder, "HAVING", having, "(", ")", " AND ");
670       sqlClause(builder, "ORDER BY", orderBy, "", "", ", ");
671       limitingRowsStrategy.appendClause(builder, offset, limit);
672       return builder.toString();
673     }
674 
675     private void joins(SafeAppendable builder) {
676       sqlClause(builder, "JOIN", join, "", "", "\nJOIN ");
677       sqlClause(builder, "INNER JOIN", innerJoin, "", "", "\nINNER JOIN ");
678       sqlClause(builder, "OUTER JOIN", outerJoin, "", "", "\nOUTER JOIN ");
679       sqlClause(builder, "LEFT OUTER JOIN", leftOuterJoin, "", "", "\nLEFT OUTER JOIN ");
680       sqlClause(builder, "RIGHT OUTER JOIN", rightOuterJoin, "", "", "\nRIGHT OUTER JOIN ");
681     }
682 
683     private String insertSQL(SafeAppendable builder) {
684       sqlClause(builder, "INSERT INTO", tables, "", "", "");
685       sqlClause(builder, "", columns, "(", ")", ", ");
686       for (int i = 0; i < valuesList.size(); i++) {
687         sqlClause(builder, i > 0 ? "," : "VALUES", valuesList.get(i), "(", ")", ", ");
688       }
689       return builder.toString();
690     }
691 
692     private String deleteSQL(SafeAppendable builder) {
693       sqlClause(builder, "DELETE FROM", tables, "", "", "");
694       sqlClause(builder, "WHERE", where, "(", ")", " AND ");
695       limitingRowsStrategy.appendClause(builder, null, limit);
696       return builder.toString();
697     }
698 
699     private String updateSQL(SafeAppendable builder) {
700       sqlClause(builder, "UPDATE", tables, "", "", "");
701       joins(builder);
702       sqlClause(builder, "SET", sets, "", "", ", ");
703       sqlClause(builder, "WHERE", where, "(", ")", " AND ");
704       limitingRowsStrategy.appendClause(builder, null, limit);
705       return builder.toString();
706     }
707 
708     public String sql(Appendable a) {
709       SafeAppendable builder = new SafeAppendable(a);
710       if (statementType == null) {
711         return null;
712       }
713 
714       String answer;
715 
716       switch (statementType) {
717         case DELETE:
718           answer = deleteSQL(builder);
719           break;
720 
721         case INSERT:
722           answer = insertSQL(builder);
723           break;
724 
725         case SELECT:
726           answer = selectSQL(builder);
727           break;
728 
729         case UPDATE:
730           answer = updateSQL(builder);
731           break;
732 
733         default:
734           answer = null;
735       }
736 
737       return answer;
738     }
739   }
740 }