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