001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.jboot.db.model;
017
018import io.jboot.db.dialect.JbootMysqlDialect;
019import io.jboot.db.dialect.JbootSqlServerDialect;
020import io.jboot.utils.StrUtil;
021
022import java.io.Serializable;
023import java.util.*;
024
025/**
026 * Column 的工具类,用于方便组装sql
027 */
028public class Columns implements Serializable {
029
030    public static final Columns EMPTY = Columns.create();
031
032    private List<Column> cols;
033
034    /**
035     * 在很多场景下,只会根据字段来查询,如果字段值为 null 的情况,Columns 会直接忽略 null 值,此时会造成结果不准确的情况
036     * <p>
037     * 比如 :
038     * ```
039     * public ShopInfo findFirstByAccountId(BigInteger accountId) {
040     * return findFirstByColumns(Columns.create("account_id", accountId));
041     * }
042     * ```
043     * 根据账户 id 来查询该账户对应的 ShopInfo,此时 如果传入 null 值,则返回了 第一个 ShopInfo,这个 ShopInfo 可能并不是该账户的。
044     * <p>
045     * 在这种场景下,我们就不应该允许用户传入 null 值进行查询,当传入 null 的时候直接抛出异常即可 。
046     * <p>
047     * 此时,我们可以使用如下代码进行查询。
048     * <p>
049     * ```
050     * public ShopInfo findFirstByAccountId(BigInteger accountId) {
051     * return findFirstByColumns(Columns.safeMode().eq("account_id", accountId));
052     * }
053     * ```
054     */
055    private boolean useSafeMode = false;
056
057
058    public static Columns create() {
059        return new Columns();
060    }
061
062
063    public static Columns create(Column column) {
064        Columns that = new Columns();
065        that.add(column);
066        return that;
067
068    }
069
070    public static Columns create(List<Column> columns) {
071        Columns that = new Columns();
072        that.cols = columns;
073        return that;
074
075    }
076
077    public static Columns create(String name, Object value) {
078        return create().eq(name, value);
079    }
080
081
082    public static Columns safeMode() {
083        return new Columns().useSafeMode();
084    }
085
086
087    public static Columns safeCreate(String name, Object value) {
088        return safeMode().eq(name, value);
089    }
090
091
092    /**
093     * add new column in Columns
094     *
095     * @param column
096     */
097    public Columns add(Column column) {
098
099        //do not add null value column
100        if (column.hasPara() && column.getValue() == null) {
101            return this;
102        }
103
104        if (this.cols == null) {
105            this.cols = new LinkedList<>();
106        }
107
108        this.cols.add(column);
109        return this;
110    }
111
112
113    /**
114     * add new column in Columns
115     *
116     * @param column
117     */
118    public Columns addToFirst(Column column) {
119
120        //do not add null value column
121        if (column.hasPara() && column.getValue() == null) {
122            return this;
123        }
124
125        if (this.cols == null) {
126            this.cols = new LinkedList<>();
127        }
128
129        this.cols.add(0, column);
130        return this;
131    }
132
133
134    /**
135     * add Columns
136     *
137     * @param columns
138     * @return
139     */
140    public Columns add(Columns columns) {
141        return append(columns);
142    }
143
144
145    /**
146     * add Columns To First
147     *
148     * @param columns
149     * @return
150     */
151    public Columns addToFirst(Columns columns) {
152        if (columns != null && !columns.isEmpty()) {
153            for (Column column : columns.getList()) {
154                addToFirst(column);
155            }
156        }
157        return this;
158    }
159
160
161    /**
162     * equals
163     *
164     * @param name
165     * @param value
166     * @return
167     */
168    public Columns eq(String name, Object value) {
169        Util.checkNullParas(this, name, value);
170        return add(Column.create(name, value));
171    }
172
173    /**
174     * not equals !=
175     *
176     * @param name
177     * @param value
178     * @return
179     */
180    public Columns ne(String name, Object value) {
181        Util.checkNullParas(this, name, value);
182        return add(Column.create(name, value, Column.LOGIC_NOT_EQUALS));
183    }
184
185
186    /**
187     * like
188     *
189     * @param name
190     * @param value
191     * @return
192     */
193    public Columns like(String name, Object value) {
194        Util.checkNullParas(this, name, value);
195        return add(Column.create(name, value, Column.LOGIC_LIKE));
196    }
197
198    /**
199     * 自动添加两边 % 的like
200     *
201     * @param name
202     * @param value
203     * @return
204     */
205    public Columns likeAppendPercent(String name, Object value) {
206        Util.checkNullParas(this, name, value);
207        if (value == null || (value instanceof String && StrUtil.isBlank((String) value))) {
208            return this;
209        }
210        return add(Column.create(name, "%" + value + "%", Column.LOGIC_LIKE));
211    }
212
213    /**
214     * 大于 great than
215     *
216     * @param name
217     * @param value
218     * @return
219     */
220    public Columns gt(String name, Object value) {
221        Util.checkNullParas(this, name, value);
222        return add(Column.create(name, value, Column.LOGIC_GT));
223    }
224
225    /**
226     * 大于等于 great or equal
227     *
228     * @param name
229     * @param value
230     * @return
231     */
232    public Columns ge(String name, Object value) {
233        Util.checkNullParas(this, name, value);
234        return add(Column.create(name, value, Column.LOGIC_GE));
235    }
236
237    /**
238     * 小于 less than
239     *
240     * @param name
241     * @param value
242     * @return
243     */
244    public Columns lt(String name, Object value) {
245        Util.checkNullParas(this, name, value);
246        return add(Column.create(name, value, Column.LOGIC_LT));
247    }
248
249    /**
250     * 小于等于 less or equal
251     *
252     * @param name
253     * @param value
254     * @return
255     */
256    public Columns le(String name, Object value) {
257        Util.checkNullParas(this, name, value);
258        return add(Column.create(name, value, Column.LOGIC_LE));
259    }
260
261
262    /**
263     * IS NULL
264     *
265     * @param name
266     * @return
267     */
268    public Columns isNull(String name) {
269        return add(Column.create(name, null, Column.LOGIC_IS_NULL));
270    }
271
272
273    /**
274     * @param name
275     * @param condition
276     * @return
277     */
278    public Columns isNullIf(String name, Boolean condition) {
279        if (condition != null && condition) {
280            add(Column.create(name, null, Column.LOGIC_IS_NULL));
281        }
282        return this;
283    }
284
285
286    /**
287     * IS NOT NULL
288     *
289     * @param name
290     * @return
291     */
292    public Columns isNotNull(String name) {
293        return add(Column.create(name, null, Column.LOGIC_IS_NOT_NULL));
294    }
295
296
297    /**
298     * IS NOT NULL
299     *
300     * @param name
301     * @param condition
302     * @return
303     */
304    public Columns isNotNullIf(String name, Boolean condition) {
305        if (condition != null && condition) {
306            add(Column.create(name, null, Column.LOGIC_IS_NOT_NULL));
307        }
308        return this;
309    }
310
311
312    /**
313     * in arrays
314     *
315     * @param name
316     * @param arrays
317     * @return
318     */
319    public Columns in(String name, Object... arrays) {
320        Util.checkNullParas(this, name, arrays);
321
322        //忽略 columns.in("name", null) 的情况
323        if (arrays != null && arrays.length == 1 && arrays[0] == null) {
324            return this;
325        }
326        return add(Column.create(name, arrays, Column.LOGIC_IN));
327    }
328
329
330    /**
331     * in Collection
332     *
333     * @param name
334     * @param collection
335     * @return
336     */
337    public Columns in(String name, Collection<?> collection) {
338        Util.checkNullParas(this, collection);
339        if (collection != null && !collection.isEmpty()) {
340            in(name, collection.toArray());
341        }
342        return this;
343    }
344
345    /**
346     * not int arrays
347     *
348     * @param name
349     * @param arrays
350     * @return
351     */
352    public Columns notIn(String name, Object... arrays) {
353        Util.checkNullParas(this, name, arrays);
354
355        //忽略 columns.notIn("name", null) 的情况
356        if (arrays != null && arrays.length == 1 && arrays[0] == null) {
357            return this;
358        }
359        return add(Column.create(name, arrays, Column.LOGIC_NOT_IN));
360    }
361
362
363    /**
364     * not in Collection
365     *
366     * @param name
367     * @param collection
368     * @return
369     */
370    public Columns notIn(String name, Collection<?> collection) {
371        Util.checkNullParas(this, collection);
372        if (collection != null && !collection.isEmpty()) {
373            notIn(name, collection.toArray());
374        }
375        return this;
376    }
377
378
379    /**
380     * between
381     *
382     * @param name
383     * @param start
384     * @param end
385     * @return
386     */
387    public Columns between(String name, Object start, Object end) {
388        Util.checkNullParas(this, name, start, end);
389        return add(Column.create(name, new Object[]{start, end}, Column.LOGIC_BETWEEN));
390    }
391
392    /**
393     * not between
394     *
395     * @param name
396     * @param start
397     * @param end
398     * @return
399     */
400    public Columns notBetween(String name, Object start, Object end) {
401        Util.checkNullParas(this, name, start, end);
402        return add(Column.create(name, new Object[]{start, end}, Column.LOGIC_NOT_BETWEEN));
403    }
404
405
406    /**
407     * group
408     *
409     * @param columns
410     * @return
411     */
412    public Columns group(Columns columns) {
413        if (columns == this) {
414            throw new IllegalArgumentException("Columns.group(...) need a new Columns");
415        }
416        if (!columns.isEmpty()) {
417            add(new Group(columns));
418        }
419        return this;
420    }
421
422
423    /**
424     * @param columns
425     * @param condition
426     * @return
427     */
428    public Columns groupIf(Columns columns, Boolean condition) {
429        if (columns == this) {
430            throw new IllegalArgumentException("Columns.group(...) need a new Columns");
431        }
432        if (condition != null && condition && !columns.isEmpty()) {
433            add(new Group(columns));
434        }
435        return this;
436    }
437
438    /**
439     * @param name
440     * @return
441     */
442    public Columns groupBy(String name) {
443        add(new GroupBy(name));
444        return this;
445    }
446
447    /**
448     * @param name
449     * @return
450     */
451    public Columns having(String name) {
452        add(new Having(name));
453        return this;
454    }
455
456
457    /**
458     * @param sql
459     * @return
460     */
461    public Columns having(String sql, Object... paras) {
462        add(new Having(sql, paras));
463        return this;
464    }
465
466
467    /**
468     * @param columns
469     * @return
470     */
471    public Columns having(Columns columns) {
472        add(new Having(columns));
473        return this;
474    }
475
476
477    /**
478     * customize string sql
479     *
480     * @param sql
481     * @return
482     */
483    public Columns sqlPart(String sql) {
484        if (StrUtil.isNotBlank(sql)) {
485            add(new SqlPart(sql));
486        }
487        return this;
488    }
489
490    /**
491     * customize string sql
492     *
493     * @param sql
494     * @param paras
495     * @return
496     */
497    public Columns sqlPart(String sql, Object... paras) {
498        Util.checkNullParas(this, paras);
499        if (StrUtil.isNotBlank(sql)) {
500            add(new SqlPart(sql, paras));
501        }
502        return this;
503    }
504
505    /**
506     * customize string sql
507     *
508     * @param sql
509     * @param condition
510     * @return
511     */
512    public Columns sqlPartIf(String sql, Boolean condition) {
513        if (condition != null && condition && StrUtil.isNotBlank(sql)) {
514            add(new SqlPart(sql));
515        }
516        return this;
517    }
518
519    /**
520     * customize string sql
521     *
522     * @param sql
523     * @param condition
524     * @param paras
525     * @return
526     */
527    public Columns sqlPartIf(String sql, Boolean condition, Object... paras) {
528        Util.checkNullParas(this, paras);
529        if (condition != null && condition && StrUtil.isNotBlank(sql)) {
530            add(new SqlPart(sql, paras));
531        }
532        return this;
533    }
534
535    /**
536     * customize string sql
537     *
538     * @param sql
539     * @return
540     */
541    public Columns sqlPartWithoutLink(String sql) {
542        if (StrUtil.isNotBlank(sql)) {
543            add(new SqlPart(sql, true));
544        }
545        return this;
546    }
547
548    /**
549     * customize string sql
550     *
551     * @param sql
552     * @param paras
553     * @return
554     */
555    public Columns sqlPartWithoutLink(String sql, Object... paras) {
556        Util.checkNullParas(this, paras);
557        if (StrUtil.isNotBlank(sql)) {
558            add(new SqlPart(sql, paras, true));
559        }
560        return this;
561    }
562
563    /**
564     * customize string sql
565     *
566     * @param sql
567     * @param condition
568     * @return
569     */
570    public Columns sqlPartWithoutLinkIf(String sql, Boolean condition) {
571        if (condition != null && condition && StrUtil.isNotBlank(sql)) {
572            add(new SqlPart(sql, true));
573        }
574        return this;
575    }
576
577    /**
578     * customize string sql
579     *
580     * @param sql
581     * @param condition
582     * @param paras
583     * @return
584     */
585    public Columns sqlPartWithoutLinkIf(String sql, Boolean condition, Object... paras) {
586        Util.checkNullParas(this, paras);
587        if (condition != null && condition && StrUtil.isNotBlank(sql)) {
588            add(new SqlPart(sql, paras, true));
589        }
590        return this;
591    }
592
593
594    public Columns or() {
595        add(new Or());
596        return this;
597    }
598
599
600    public Columns ors(String name, String logic, Object... values) {
601        Util.checkNullParas(this, name, values);
602
603        Columns columns = new Columns();
604        for (int i = 0; i < values.length; i++) {
605            Object value = values[i];
606            if (value != null) {
607                columns.add(Column.create(name, value, logic));
608                if (i != values.length - 1) {
609                    columns.add(new Or());
610                }
611            }
612        }
613
614        return group(columns);
615    }
616
617
618    public Columns orEqs(String name, Object... values) {
619        return ors(name, Column.LOGIC_EQUALS, values);
620    }
621
622
623    /**
624     * 追加 新的 columns
625     *
626     * @param columns
627     * @return
628     */
629    public Columns append(Columns columns) {
630        if (columns != null && !columns.isEmpty()) {
631            for (Column column : columns.getList()) {
632                add(column);
633            }
634        }
635        return this;
636    }
637
638
639    /**
640     * 追加 新的 columns
641     *
642     * @param columns
643     * @return
644     */
645    public Columns appendIf(Columns columns, Boolean condition) {
646        if (condition != null && condition) {
647            append(columns);
648        }
649        return this;
650    }
651
652    public boolean isUseSafeMode() {
653        return useSafeMode;
654    }
655
656    public Columns useSafeMode() {
657        this.useSafeMode = true;
658        return this;
659    }
660
661    public Columns unUseSafeMode() {
662        this.useSafeMode = false;
663        return this;
664    }
665
666    public boolean isEmpty() {
667        return cols == null || cols.isEmpty();
668    }
669
670
671    public Object[] getValueArray() {
672        return Util.getValueArray(cols);
673    }
674
675
676    public List<Column> getList() {
677        return cols;
678    }
679
680    public boolean containsName(String name) {
681        if (isEmpty()) {
682            return false;
683        }
684
685        for (Column col : cols) {
686            if (col.getName() != null && col.getName().equals(name)) {
687                return true;
688            }
689        }
690        return false;
691    }
692
693
694    public String getCacheKey() {
695        if (isEmpty()) {
696            return null;
697        }
698
699        List<Column> columns = new ArrayList<>(cols);
700        StringBuilder s = new StringBuilder();
701        buildCacheKey(s, columns);
702
703        return s.toString();
704    }
705
706    private static final char SQL_CACHE_SEPARATOR = '-';
707
708    private void buildCacheKey(StringBuilder s, List<Column> columns) {
709        for (int i = 0; i < columns.size(); i++) {
710
711            Column column = columns.get(i);
712
713            if (column instanceof Or) {
714                Column before = i > 0 ? columns.get(i - 1) : null;
715                if (before != null && !(before instanceof Or)) {
716                    s.append("or").append(SQL_CACHE_SEPARATOR);
717                }
718            } else if (column instanceof Group) {
719                s.append('(');
720                buildCacheKey(s, ((Group) column).getColumns().getList());
721                s.append(')').append(SQL_CACHE_SEPARATOR);
722            } else if (column instanceof SqlPart) {
723                String sqlpart = ((SqlPart) column).getSql();
724                Object value = column.getValue();
725                if (value != null) {
726                    if (value.getClass().isArray()) {
727                        Object[] values = (Object[]) value;
728                        for (Object v : values) {
729                            sqlpart = Util.replaceSqlPara(sqlpart, v);
730                        }
731                    } else {
732                        sqlpart = Util.replaceSqlPara(sqlpart, value);
733                    }
734                }
735                s.append(Util.deleteWhitespace(sqlpart)).append(SQL_CACHE_SEPARATOR);
736            } else {
737                s.append(column.getName())
738                        .append(SQL_CACHE_SEPARATOR)
739                        .append(getLogicString(column.getLogic()))
740                        .append(SQL_CACHE_SEPARATOR);
741                Object value = column.getValue();
742                if (value != null) {
743                    if (value.getClass().isArray()) {
744                        s.append(Util.array2String((Object[]) value));
745                    } else {
746                        s.append(column.getValue());
747                    }
748                    s.append(SQL_CACHE_SEPARATOR);
749                }
750            }
751        }
752        s.deleteCharAt(s.length() - 1);
753    }
754
755
756    /**
757     * @param logic
758     * @return
759     */
760    private static String getLogicString(String logic) {
761        switch (logic) {
762            case Column.LOGIC_LIKE:
763                return "lk";
764            case Column.LOGIC_GT:
765                return "gt";
766            case Column.LOGIC_GE:
767                return "ge";
768            case Column.LOGIC_LT:
769                return "lt";
770            case Column.LOGIC_LE:
771                return "le";
772            case Column.LOGIC_EQUALS:
773                return "eq";
774            case Column.LOGIC_NOT_EQUALS:
775                return "neq";
776            case Column.LOGIC_IS_NULL:
777                return "isn";
778            case Column.LOGIC_IS_NOT_NULL:
779                return "nn";
780            case Column.LOGIC_IN:
781                return "in";
782            case Column.LOGIC_NOT_IN:
783                return "nin";
784            case Column.LOGIC_BETWEEN:
785                return "bt";
786            case Column.LOGIC_NOT_BETWEEN:
787                return "nbt";
788            default:
789                return "";
790        }
791    }
792
793
794    /**
795     * 输出 where 后面的 sql 部分,风格是 mysql 的风格 SQL
796     *
797     * @return
798     */
799    public String toWherePartSql() {
800        return toWherePartSql('`', false);
801    }
802
803
804    /**
805     * 输出 where 后面的 sql 部分,风格是 mysql 的风格 SQL
806     *
807     * @param withWhereKeyword 是否带上 where 关键字
808     * @return
809     */
810    public String toWherePartSql(boolean withWhereKeyword) {
811        return toWherePartSql('`', withWhereKeyword);
812    }
813
814
815    /**
816     * 输出 where 部分的 sql
817     *
818     * @param separator        字段分隔符
819     * @param withWhereKeyword 是否带上 "where 关键字"
820     * @return
821     */
822    public String toWherePartSql(char separator, boolean withWhereKeyword) {
823        StringBuilder sb = new StringBuilder();
824        SqlBuilder.buildWhereSql(sb, getList(), separator, withWhereKeyword);
825        return sb.toString();
826    }
827
828
829    @Override
830    public String toString() {
831        String cacheKey = getCacheKey();
832        return StrUtil.isNotBlank(cacheKey) ? cacheKey : "{}";
833    }
834
835
836    public static void main(String[] args) {
837
838        Columns columns = Columns.create().useSafeMode().or().or().or().eq("aa", "bb").or().or().or().notIn("aaa", 123, 456, 789).like("titile", "a");
839        columns.group(Columns.create().or().or().sqlPart("aa=bb"));
840        columns.group(Columns.create("aa", "bb").eq("cc", "dd")
841                .group(Columns.create("aa", "bb").eq("cc", "dd"))
842                .group(Columns.create("aa", "bb").eq("cc", "dd").group(Columns.create("aa", "bb").eq("cc", "dd"))));
843
844        columns.ge("age", 10);
845        columns.or();
846        columns.or();
847        columns.or();
848        columns.or();
849        columns.sqlPart("user.id != ? and xxx= ?", 1, "abc2");
850        columns.sqlPart("user.id != ? and xxx= ?", 1, "abc2");
851
852        columns.or();
853        columns.or();
854        columns.or();
855        columns.group(Columns.create().likeAppendPercent("name", "null").or().or().or()
856                .eq("age", "18").eq("ddd", null));
857
858        columns.or();
859        columns.or();
860
861        columns.group(Columns.create().or().or().sqlPart("name = ?", "zhangsan"));
862        columns.or();
863        columns.or();
864        columns.or();
865
866        columns.between("name", "123", "1233");
867        columns.between("name", "123", "1233");
868        columns.or();
869
870//        columns.sqlPartWithoutLink("group by xxx");
871        columns.groupBy("aaa").having(Columns.create("aaa", "bbb").ge("ccc", 111));
872//        columns.or();
873//        columns.or();
874//        columns.or();
875
876        System.out.println(columns.getCacheKey());
877        System.out.println(Arrays.toString(columns.getValueArray()));
878        System.out.println(columns.toMysqlSql());
879        System.out.println("-----------");
880        System.out.println(columns.toWherePartSql('"', true));
881
882    }
883
884    /**
885     * 这个只是用于调试
886     *
887     * @return
888     */
889    private String toMysqlSql() {
890        JbootMysqlDialect dialect = new JbootMysqlDialect();
891        return dialect.forFindByColumns(null, null, "table", "*", getList(), null, null);
892    }
893
894
895    /**
896     * 这个只是用于调试
897     *
898     * @return
899     */
900    private String toSqlServerSql() {
901        JbootSqlServerDialect dialect = new JbootSqlServerDialect();
902        return dialect.forFindByColumns(null, null, "table", "*", getList(), null, null);
903    }
904
905}