001/**
002 * Copyright (c) 2022-2023, Mybatis-Flex (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 com.mybatisflex.core.query;
017
018
019import com.mybatisflex.core.dialect.IDialect;
020
021import java.io.Serializable;
022import java.lang.reflect.Array;
023import java.util.List;
024import java.util.function.Predicate;
025import java.util.function.Supplier;
026
027public class QueryCondition implements Serializable {
028
029    public static final String LOGIC_LIKE = "LIKE";
030    public static final String LOGIC_GT = ">";
031    public static final String LOGIC_GE = ">=";
032    public static final String LOGIC_LT = "<";
033    public static final String LOGIC_LE = "<=";
034    public static final String LOGIC_EQUALS = "=";
035    public static final String LOGIC_NOT_EQUALS = "!=";
036
037    public static final String LOGIC_IS_NULL = "IS NULL";
038    public static final String LOGIC_IS_NOT_NULL = "IS NOT NULL";
039
040    public static final String LOGIC_IN = "IN";
041    public static final String LOGIC_NOT_IN = "NOT IN";
042    public static final String LOGIC_BETWEEN = "BETWEEN";
043    public static final String LOGIC_NOT_BETWEEN = "NOT BETWEEN";
044
045
046    protected QueryColumn column;
047    protected String logic;
048    protected Object value;
049    protected boolean effective = true;
050
051    //当前条件的上个条件
052    protected QueryCondition before;
053    //当前条件的上个下一个
054    protected QueryCondition next;
055    //两个条件直接的连接符
056    protected SqlConnector connector;
057
058
059    public static QueryCondition createEmpty() {
060        return new QueryCondition().when(false);
061    }
062
063
064    public static QueryCondition create(String table, String column, String logic, Object value) {
065        QueryCondition condition = new QueryCondition();
066        condition.setColumn(new QueryColumn(table, column));
067        condition.setLogic(logic);
068        condition.setValue(value);
069        return condition;
070    }
071
072    public static QueryCondition create(QueryColumn queryColumn, Object value) {
073        return create(queryColumn, LOGIC_EQUALS, value);
074    }
075
076    public static QueryCondition create(QueryColumn queryColumn, String logic, Object value) {
077        QueryCondition condition = new QueryCondition();
078        condition.setColumn(queryColumn);
079        condition.setLogic(logic);
080        condition.setValue(value);
081        return condition;
082    }
083
084    public QueryCondition() {
085    }
086
087    public QueryColumn getColumn() {
088        return column;
089    }
090
091    public void setColumn(QueryColumn column) {
092        this.column = column;
093    }
094
095    public Object getValue() {
096        return checkEffective() ? value : null;
097    }
098
099    public void setValue(Object value) {
100        this.value = value;
101    }
102
103    public String getLogic() {
104        return logic;
105    }
106
107    public void setLogic(String logic) {
108        this.logic = logic;
109    }
110
111
112    public QueryCondition when(boolean effective) {
113        this.effective = effective;
114        return this;
115    }
116
117    public void when(Supplier<Boolean> fn) {
118        Boolean effective = fn.get();
119        this.effective = (effective != null && effective);
120    }
121
122    public <T> QueryCondition when(Predicate<T> fn){
123        Object val = this.value;
124        if (LOGIC_LIKE.equals(logic) && val instanceof String) {
125            String valStr = (String) val;
126            if (valStr.startsWith("%")) {
127                valStr = valStr.substring(1);
128            }
129            if (valStr.endsWith("%")) {
130                valStr = valStr.substring(0, valStr.length() - 1);
131            }
132            val = valStr;
133        }
134        this.effective = fn.test((T) val);
135        return this;
136    }
137
138    public boolean checkEffective() {
139        return effective;
140    }
141
142
143    public QueryCondition and(String sql) {
144        return and(new StringQueryCondition(sql));
145    }
146
147    public QueryCondition and(String sql, Object... params) {
148        return and(new StringQueryCondition(sql, params));
149    }
150
151    public QueryCondition and(QueryCondition nextCondition) {
152        return new Brackets(this).and(nextCondition);
153    }
154
155    public QueryCondition or(String sql) {
156        return or(new StringQueryCondition(sql));
157    }
158
159    public QueryCondition or(String sql, Object... params) {
160        return or(new StringQueryCondition(sql, params));
161    }
162
163    public QueryCondition or(QueryCondition nextCondition) {
164        return new Brackets(this).or(nextCondition);
165    }
166
167    protected void connect(QueryCondition nextCondition, SqlConnector connector) {
168        if (this.next != null) {
169            this.next.connect(nextCondition, connector);
170        } else {
171            this.next = nextCondition;
172            this.connector = connector;
173            nextCondition.before = this;
174        }
175    }
176
177    public String toSql(List<QueryTable> queryTables, IDialect dialect) {
178        StringBuilder sql = new StringBuilder();
179        //检测是否生效
180        if (checkEffective()) {
181            QueryCondition effectiveBefore = getEffectiveBefore();
182            if (effectiveBefore != null) {
183                sql.append(effectiveBefore.connector);
184            }
185            sql.append(getColumn().toConditionSql(queryTables, dialect));
186            sql.append(" ").append(logic).append(" ");
187            if (value instanceof QueryColumn) {
188                sql.append(((QueryColumn) value).toConditionSql(queryTables, dialect));
189            }
190            //子查询
191            else if (value instanceof QueryWrapper) {
192                sql.append("(").append(dialect.buildSelectSql((QueryWrapper) value)).append(")");
193            }
194            //原生sql
195            else if (value instanceof RawValue) {
196                sql.append(((RawValue) value).getContent());
197            }
198            //正常查询,构建问号
199            else {
200                appendQuestionMark(sql);
201            }
202        }
203
204        if (this.next != null) {
205            return sql + next.toSql(queryTables, dialect);
206        }
207
208        return sql.toString();
209    }
210
211
212    protected QueryCondition getEffectiveBefore() {
213        if (before != null && before.checkEffective()) {
214            return before;
215        } else if (before != null) {
216            return before.getEffectiveBefore();
217        } else {
218            return null;
219        }
220    }
221
222
223    protected void appendQuestionMark(StringBuilder sqlBuilder) {
224        if (LOGIC_IS_NULL.equals(logic)
225                || LOGIC_IS_NOT_NULL.equals(logic)
226                || value instanceof QueryColumn
227                || value instanceof QueryWrapper
228                || value instanceof RawValue) {
229            //do nothing
230        }
231
232        //between, not between
233        else if (LOGIC_BETWEEN.equals(logic) || LOGIC_NOT_BETWEEN.equals(logic)) {
234            sqlBuilder.append(" ? AND ? ");
235        }
236        //in, not in
237        else if (LOGIC_IN.equals(logic) || LOGIC_NOT_IN.equals(logic)) {
238            int paramsCount = calculateValueArrayCount();
239            sqlBuilder.append('(');
240            for (int i = 0; i < paramsCount; i++) {
241                sqlBuilder.append('?');
242                if (i != paramsCount - 1) {
243                    sqlBuilder.append(',');
244                }
245            }
246            sqlBuilder.append(')');
247        } else {
248            sqlBuilder.append(" ? ");
249        }
250    }
251
252
253    private int calculateValueArrayCount() {
254        Object[] values = (Object[]) value;
255        int paramsCount = 0;
256        for (Object object : values) {
257            if (object != null && (object.getClass().isArray()
258                    || object.getClass() == int[].class
259                    || object.getClass() == long[].class
260                    || object.getClass() == short[].class
261                    || object.getClass() == float[].class
262                    || object.getClass() == double[].class)) {
263                paramsCount += Array.getLength(object);
264            } else {
265                paramsCount++;
266            }
267        }
268        return paramsCount;
269    }
270
271    @Override
272    public String toString() {
273        return "QueryCondition{" +
274                "column=" + column +
275                ", logic='" + logic + '\'' +
276                ", value=" + value +
277                ", effective=" + effective +
278                '}';
279    }
280}