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.constant.SqlConsts;
020import com.mybatisflex.core.dialect.IDialect;
021import com.mybatisflex.core.exception.FlexExceptions;
022import com.mybatisflex.core.util.ClassUtil;
023import com.mybatisflex.core.util.ObjectUtil;
024
025import java.lang.reflect.Array;
026import java.util.List;
027import java.util.function.Predicate;
028import java.util.function.Supplier;
029
030public class QueryCondition implements CloneSupport<QueryCondition> {
031
032
033    protected QueryColumn column;
034    protected String logic;
035    protected Object value;
036    protected boolean effective = true;
037
038    //当前条件的上一个条件
039    protected QueryCondition prev;
040    //当前条件的下一个条件
041    protected QueryCondition next;
042
043    //两个条件直接的连接符
044    protected SqlConnector connector;
045
046
047    public static QueryCondition createEmpty() {
048        return new QueryCondition().when(false);
049    }
050
051
052    public static QueryCondition create(String schema, String table, String column, String logic, Object value) {
053        QueryCondition condition = new QueryCondition();
054        condition.setColumn(new QueryColumn(schema, table, column));
055        condition.setLogic(logic);
056        condition.setValue(value);
057        return condition;
058    }
059
060    public static QueryCondition create(QueryColumn queryColumn, Object value) {
061        return create(queryColumn, SqlConsts.EQUALS, value);
062    }
063
064    public static QueryCondition create(QueryColumn queryColumn, String logic, Object value) {
065        QueryCondition condition = new QueryCondition();
066        condition.setColumn(queryColumn);
067        condition.setLogic(logic);
068        condition.setValue(value);
069        return condition;
070    }
071
072    public QueryCondition() {
073    }
074
075    public QueryColumn getColumn() {
076        return column;
077    }
078
079    public void setColumn(QueryColumn column) {
080        this.column = column;
081    }
082
083    public Object getValue() {
084        return checkEffective() ? value : null;
085    }
086
087    public void setValue(Object value) {
088        this.value = value;
089    }
090
091    public String getLogic() {
092        return logic;
093    }
094
095    public void setLogic(String logic) {
096        this.logic = logic;
097    }
098
099
100    public QueryCondition when(boolean effective) {
101        this.effective = effective;
102        return this;
103    }
104
105    public void when(Supplier<Boolean> fn) {
106        Boolean effective = fn.get();
107        this.effective = (effective != null && effective);
108    }
109
110    public <T> QueryCondition when(Predicate<T> fn) {
111        Object val = this.value;
112        if (SqlConsts.LIKE.equals(logic) && val instanceof String) {
113            String valStr = (String) val;
114            if (valStr.startsWith(SqlConsts.PERCENT_SIGN)) {
115                valStr = valStr.substring(1);
116            }
117            if (valStr.endsWith(SqlConsts.PERCENT_SIGN)) {
118                valStr = valStr.substring(0, valStr.length() - 1);
119            }
120            val = valStr;
121        }
122        this.effective = fn.test((T) val);
123        return this;
124    }
125
126    public boolean checkEffective() {
127        return effective;
128    }
129
130
131    public QueryCondition and(String sql) {
132        return and(new RawFragment(sql));
133    }
134
135    public QueryCondition and(String sql, Object... params) {
136        return and(new RawFragment(sql, params));
137    }
138
139    public QueryCondition and(QueryCondition nextCondition) {
140        return new Brackets(this).and(nextCondition);
141    }
142
143    public QueryCondition or(String sql) {
144        return or(new RawFragment(sql));
145    }
146
147    public QueryCondition or(String sql, Object... params) {
148        return or(new RawFragment(sql, params));
149    }
150
151    public QueryCondition or(QueryCondition nextCondition) {
152        return new Brackets(this).or(nextCondition);
153    }
154
155    protected void connect(QueryCondition nextCondition, SqlConnector connector) {
156        if (this.next != null) {
157            this.next.connect(nextCondition, connector);
158        } else {
159            this.next = nextCondition;
160            this.connector = connector;
161            nextCondition.prev = this;
162        }
163    }
164
165    public String toSql(List<QueryTable> queryTables, IDialect dialect) {
166        StringBuilder sql = new StringBuilder();
167        //检测是否生效
168        if (checkEffective()) {
169            QueryCondition prevEffectiveCondition = getPrevEffectiveCondition();
170            if (prevEffectiveCondition != null) {
171                sql.append(prevEffectiveCondition.connector);
172            }
173            //列
174            sql.append(getColumn().toConditionSql(queryTables, dialect));
175
176            //逻辑符号
177            sql.append(logic);
178
179            //值(或者问号)
180            if (value instanceof QueryColumn) {
181                sql.append(((QueryColumn) value).toConditionSql(queryTables, dialect));
182            }
183            //子查询
184            else if (value instanceof QueryWrapper) {
185                sql.append(SqlConsts.BRACKET_LEFT)
186                    .append(dialect.buildSelectSql((QueryWrapper) value))
187                    .append(SqlConsts.BRACKET_RIGHT);
188            }
189            //原生sql
190            else if (value instanceof RawFragment) {
191                sql.append(((RawFragment) value).getContent());
192            }
193            //正常查询,构建问号
194            else {
195                appendQuestionMark(sql);
196            }
197        }
198
199        if (this.next != null) {
200            return sql + next.toSql(queryTables, dialect);
201        }
202
203        return sql.toString();
204    }
205
206
207    protected QueryCondition getPrevEffectiveCondition() {
208        if (prev == null) {
209            return null;
210        }
211        return prev.checkEffective() ? prev : prev.getPrevEffectiveCondition();
212    }
213
214
215    protected void appendQuestionMark(StringBuilder sqlBuilder) {
216        //noinspection StatementWithEmptyBody
217        if (SqlConsts.IS_NULL.equals(logic)
218            || SqlConsts.IS_NOT_NULL.equals(logic)
219            || value instanceof QueryColumn
220            || value instanceof QueryWrapper
221            || value instanceof RawFragment) {
222            //do nothing
223        }
224
225        //between, not between
226        else if (SqlConsts.BETWEEN.equals(logic) || SqlConsts.NOT_BETWEEN.equals(logic)) {
227            sqlBuilder.append(SqlConsts.AND_PLACEHOLDER);
228        }
229        //in, not in
230        else if (SqlConsts.IN.equals(logic) || SqlConsts.NOT_IN.equals(logic)) {
231            int paramsCount = calculateValueArrayCount();
232            sqlBuilder.append(SqlConsts.BRACKET_LEFT);
233            for (int i = 0; i < paramsCount; i++) {
234                sqlBuilder.append(SqlConsts.PLACEHOLDER);
235                if (i != paramsCount - 1) {
236                    sqlBuilder.append(SqlConsts.DELIMITER);
237                }
238            }
239            sqlBuilder.append(SqlConsts.BRACKET_RIGHT);
240        } else {
241            sqlBuilder.append(SqlConsts.PLACEHOLDER);
242        }
243    }
244
245
246    private int calculateValueArrayCount() {
247        Object[] values = (Object[]) value;
248        int paramsCount = 0;
249        for (Object object : values) {
250            if (object != null && ClassUtil.isArray(object.getClass())) {
251                paramsCount += Array.getLength(object);
252            } else {
253                paramsCount++;
254            }
255        }
256        return paramsCount;
257    }
258
259
260    boolean containsTable(String... tables) {
261        if (column == null || !checkEffective()) {
262            return nextContainsTable(tables);
263        }
264        for (String table : tables) {
265            if (column.table != null && table.equals(column.table.name)) {
266                return true;
267            }
268        }
269        return nextContainsTable(tables);
270    }
271
272    boolean nextContainsTable(String... tables) {
273        if (next == null) {
274            return false;
275        }
276        return next.containsTable(tables);
277    }
278
279    @Override
280    public String toString() {
281        return "QueryCondition{" +
282            "column=" + column +
283            ", logic='" + logic + '\'' +
284            ", value=" + value +
285            ", effective=" + effective +
286            '}';
287    }
288
289    @Override
290    public QueryCondition clone() {
291        try {
292            QueryCondition clone = (QueryCondition) super.clone();
293            // deep clone ...
294            clone.column = ObjectUtil.clone(this.column);
295            clone.value = ObjectUtil.cloneObject(this.value);
296            clone.prev = clone.next = null;
297            if (this.next != null) {
298                clone.next = this.next.clone();
299                clone.next.prev = clone;
300            }
301            return clone;
302        } catch (CloneNotSupportedException e) {
303            throw FlexExceptions.wrap(e);
304        }
305    }
306
307}