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