FrontendConjunctsUtils.java

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package org.apache.doris.nereids.util;

import org.apache.doris.analysis.Expr;
import org.apache.doris.nereids.analyzer.UnboundSlot;
import org.apache.doris.nereids.parser.NereidsParser;
import org.apache.doris.nereids.rules.expression.rules.FoldConstantRuleOnFE;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
import org.apache.doris.nereids.trees.expressions.literal.Literal;
import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
import org.apache.doris.persist.gson.GsonUtils;

import com.google.common.collect.Lists;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.collections.CollectionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.List;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * FrontendConjunctsUtils
 */
public class FrontendConjunctsUtils {
    private static final Logger LOG = LogManager.getLogger(FrontendConjunctsUtils.class);
    private static List<String> nameParts;

    public static List<Expression> convertToExpression(String conjuncts) {
        List<Expr> exprs = GsonUtils.GSON.fromJson(conjuncts, new TypeToken<List<Expr>>() {
        }.getType());
        return exprs.stream()
                .map(expr -> exprToExpression(expr))
                .collect(Collectors.toList());
    }

    public static Expression exprToExpression(Expr expr) {
        NereidsParser nereidsParser = new NereidsParser();
        return nereidsParser.parseExpression(expr.toSql());
    }

    /**
     * Filter out the conjuncts that contain the current slotName.
     *
     * @param expressions expressions
     * @param slotName slotName
     * @return filterBySlotName
     */
    public static List<Expression> filterBySlotName(List<Expression> expressions, String slotName) {
        List<Expression> res = Lists.newArrayList();
        for (Expression expression : expressions) {
            if (containSlotName(expression, slotName)) {
                res.add(expression);
            }
        }
        return res;
    }

    private static boolean containSlotName(Expression expression, String slotName) {
        return expression.anyMatch(c -> {
            if (c instanceof UnboundSlot) {
                List<String> nameParts = ((UnboundSlot) c).getNameParts();
                if (!CollectionUtils.isEmpty(nameParts)) {
                    String name = nameParts.get(nameParts.size() - 1).toLowerCase();
                    return name.equalsIgnoreCase(slotName);
                }
            }
            return false;
        });
    }

    public static boolean isFiltered(List<Expression> expressions, String columnName, Object value) {
        TreeMap<String, Object> values = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
        values.put(columnName, value);
        return isFiltered(expressions, values);
    }

    /**
     * isFiltered
     *
     * @param expressions expressions
     * @param values case insensitive map
     * @return isFiltered
     */
    public static boolean isFiltered(List<Expression> expressions, TreeMap<String, Object> values) {
        for (Expression expression : expressions) {
            if (isFiltered(expression, values)) {
                return true;
            }
        }
        return false;
    }

    /**
     * isFiltered
     *
     * @param expression expression
     * @param values case insensitive map
     * @return isFiltered
     */
    public static boolean isFiltered(Expression expression, TreeMap<String, Object> values) {
        try {
            AtomicBoolean containsAllColumn = new AtomicBoolean(true);
            Expression rewrittenExpr = expression.rewriteUp(expr -> {
                if (expr instanceof UnboundSlot) {
                    List<String> nameParts = ((UnboundSlot) expr).getNameParts();
                    if (!CollectionUtils.isEmpty(nameParts)) {
                        String name = nameParts.get(nameParts.size() - 1).toLowerCase();
                        Object value = values.get(name);
                        if (value != null) {
                            return Literal.of(value);
                        } else {
                            containsAllColumn.set(false);
                        }
                    }
                }
                return expr;
            });
            // expression is: c1=v1 or c2=v2,
            // if values is {c1=v3}
            // we should not return true, because c2 may equals v2
            if (!containsAllColumn.get()) {
                return false;
            }
            Expression evaluate = FoldConstantRuleOnFE.evaluate(rewrittenExpr, null);
            if (evaluate instanceof BooleanLiteral && !((BooleanLiteral) evaluate).getValue()) {
                return true;
            }
            if (evaluate instanceof NullLiteral) {
                return true;
            }
            return false;
        } catch (Exception e) {
            LOG.warn("frontend conjuncts deal fail: expression: {}, values: {}", expression, values, e);
            return false;
        }

    }
}