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