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        return create(new QueryColumn(schema, table, column), logic, value);
070    }
071
072    public static QueryCondition create(QueryColumn queryColumn, Object value) {
073        return create(queryColumn, SqlConsts.EQUALS, value);
074    }
075
076    public static QueryCondition create(QueryColumn queryColumn, SqlOperator logic, Object value) {
077        return create(queryColumn, logic.getValue(), 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 QueryColumn getColumn() {
089        return column;
090    }
091
092    public void setColumn(QueryColumn column) {
093        this.column = column;
094    }
095
096    public Object getValue() {
097        return checkEffective() ? value : null;
098    }
099
100    public void setValue(Object value) {
101        this.value = value;
102    }
103
104    public String getLogic() {
105        return logic;
106    }
107
108    public void setLogic(String logic) {
109        this.logic = logic;
110    }
111
112    /**
113     * 动态条件构造。
114     *
115     * @param effective 是否启用该条件
116     * @return {@link QueryCondition}
117     */
118    public QueryCondition when(boolean effective) {
119        if (notEmpty()) {
120            this.effective = effective;
121        }
122        return this;
123    }
124
125    /**
126     * 动态条件构造。
127     *
128     * @param fn 是否启用该条件
129     * @return {@link QueryCondition}
130     */
131    public QueryCondition when(BooleanSupplier fn) {
132        if (notEmpty()) {
133            this.effective = fn.getAsBoolean();
134        }
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 RawQueryCondition(sql));
145    }
146
147    public QueryCondition and(String sql, Object... params) {
148        return and(new RawQueryCondition(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 RawQueryCondition(sql));
157    }
158
159    public QueryCondition or(String sql, Object... params) {
160        return or(new RawQueryCondition(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
169        if (this.next != null) {
170            this.next.connect(nextCondition, connector);
171        } else {
172            nextCondition.connector = connector;
173            this.next = nextCondition;
174            nextCondition.prev = this;
175        }
176    }
177
178    public String toSql(List<QueryTable> queryTables, IDialect dialect) {
179        StringBuilder sql = new StringBuilder();
180        //检测是否生效
181        if (checkEffective()) {
182            QueryCondition prevEffectiveCondition = getPrevEffectiveCondition();
183            if (prevEffectiveCondition != null && this.connector != null) {
184                sql.append(this.connector);
185            }
186            //列
187            sql.append(getColumn().toConditionSql(queryTables, dialect));
188
189            //逻辑符号
190            sql.append(logic);
191
192            //值(或者问号)
193            if (value instanceof QueryColumn) {
194                sql.append(((QueryColumn) value).toConditionSql(queryTables, dialect));
195            }
196            //子查询
197            else if (value instanceof QueryWrapper) {
198                sql.append(SqlConsts.BRACKET_LEFT)
199                    .append(dialect.buildSelectSql((QueryWrapper) value))
200                    .append(SqlConsts.BRACKET_RIGHT);
201            }
202            //原生sql
203            else if (value instanceof RawQueryCondition) {
204                sql.append(((RawQueryCondition) value).getContent());
205            }
206            //正常查询,构建问号
207            else {
208                appendQuestionMark(sql);
209            }
210        }
211
212        if (this.next != null) {
213            return sql + next.toSql(queryTables, dialect);
214        }
215
216        return sql.toString();
217    }
218
219
220    /**
221     * 获取上一个 “有效” 的条件
222     *
223     * @return QueryCondition
224     */
225    protected QueryCondition getPrevEffectiveCondition() {
226        if (prev == null) {
227            return null;
228        }
229        return prev.checkEffective() ? prev : prev.getPrevEffectiveCondition();
230    }
231
232    protected QueryCondition getNextEffectiveCondition() {
233        if (next == null) {
234            return null;
235        }
236        return next.checkEffective() ? next : next.getNextEffectiveCondition();
237    }
238
239
240    protected void appendQuestionMark(StringBuilder sqlBuilder) {
241        //noinspection StatementWithEmptyBody
242        if (SqlConsts.IS_NULL.equals(logic)
243            || SqlConsts.IS_NOT_NULL.equals(logic)
244            || value instanceof QueryColumn
245            || value instanceof QueryWrapper
246            || value instanceof RawQueryCondition) {
247            //do nothing
248        }
249
250        //between, not between
251        else if (SqlConsts.BETWEEN.equals(logic) || SqlConsts.NOT_BETWEEN.equals(logic)) {
252            sqlBuilder.append(SqlConsts.AND_PLACEHOLDER);
253        }
254        //in, not in
255        else if (SqlConsts.IN.equals(logic) || SqlConsts.NOT_IN.equals(logic)) {
256            int paramsCount = calculateValueArrayCount();
257            sqlBuilder.append(SqlConsts.BRACKET_LEFT);
258            for (int i = 0; i < paramsCount; i++) {
259                sqlBuilder.append(SqlConsts.PLACEHOLDER);
260                if (i != paramsCount - 1) {
261                    sqlBuilder.append(SqlConsts.DELIMITER);
262                }
263            }
264            sqlBuilder.append(SqlConsts.BRACKET_RIGHT);
265        } else {
266            sqlBuilder.append(SqlConsts.PLACEHOLDER);
267        }
268    }
269
270
271    private int calculateValueArrayCount() {
272        Object[] values = (Object[]) value;
273        int paramsCount = 0;
274        for (Object object : values) {
275            if (object != null && ClassUtil.isArray(object.getClass())) {
276                paramsCount += Array.getLength(object);
277            } else {
278                paramsCount++;
279            }
280        }
281        return paramsCount;
282    }
283
284
285    boolean containsTable(String... tables) {
286        if (column == null || !checkEffective()) {
287            return nextContainsTable(tables);
288        }
289        for (String table : tables) {
290            String tableName = StringUtil.getTableNameWithAlias(table)[0];
291            if (column.table != null && tableName.equals(column.table.name)) {
292                return true;
293            }
294        }
295        return nextContainsTable(tables);
296    }
297
298    boolean nextContainsTable(String... tables) {
299        if (next == null) {
300            return false;
301        }
302        return next.containsTable(tables);
303    }
304
305    @Override
306    public String toString() {
307        return "QueryCondition{" +
308            "column=" + column +
309            ", logic='" + logic + '\'' +
310            ", value=" + value +
311            ", effective=" + effective +
312            '}';
313    }
314
315    @Override
316    public QueryCondition clone() {
317        try {
318            QueryCondition clone = (QueryCondition) super.clone();
319            // deep clone ...
320            clone.column = ObjectUtil.clone(this.column);
321            clone.value = ObjectUtil.cloneObject(this.value);
322            clone.prev = clone.next = null;
323            if (this.next != null) {
324                clone.next = this.next.clone();
325                clone.next.prev = clone;
326            }
327            return clone;
328        } catch (CloneNotSupportedException e) {
329            throw FlexExceptions.wrap(e);
330        }
331    }
332
333}