content: /*
   * Copyright (C) 2016-2023 ActionTech.
   * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
   */
  
  package com.actiontech.dble.plan.optimizer;
  
  import com.actiontech.dble.plan.common.item.Item;
  import com.actiontech.dble.plan.common.item.ItemBoolean;
  import com.actiontech.dble.plan.common.item.function.ItemFunc;
  import com.actiontech.dble.plan.common.item.function.operator.ItemBoolFunc2;
  import com.actiontech.dble.plan.common.item.function.operator.cmpfunc.*;
  import com.actiontech.dble.plan.common.item.function.operator.logic.ItemCond;
  import com.actiontech.dble.plan.common.item.function.operator.logic.ItemCondAnd;
  import com.actiontech.dble.plan.common.item.function.operator.logic.ItemCondOr;
  import com.actiontech.dble.plan.node.JoinNode;
  import com.actiontech.dble.plan.node.PlanNode;
  import com.actiontech.dble.plan.util.FilterUtils;
  import com.actiontech.dble.plan.util.PlanUtil;
  import com.actiontech.dble.singleton.TraceManager;
  import com.google.common.collect.ImmutableMap;
  
  import java.util.*;
  
  /**
   * http://dev.mysql.com/doc/refman/5.7/en/where-optimizations.html
   *
   * @author ActionTech
   * @CreateTime Mar 16, 2016
   */
  public final class FilterPreProcessor {
      private FilterPreProcessor() {
      }
  
      public static PlanNode optimize(PlanNode qtn) {
          TraceManager.TraceObject traceObject = TraceManager.threadTrace("optimize-for-always-true");
          try {
              MergeHavingFilter.optimize(qtn);
              preProcess(qtn);
              return qtn;
          } finally {
              TraceManager.log(ImmutableMap.of("plan-node", qtn), traceObject);
              TraceManager.finishSpan(traceObject);
          }
      }
  
  
      private static void preProcess(PlanNode qtn) {
          qtn.having(processFilter(qtn.getHavingFilter()));
          qtn.query(processFilter(qtn.getWhereFilter()));
          if (qtn instanceof JoinNode) {
              JoinNode jn = (JoinNode) qtn;
              for (int i = 0; i < ((JoinNode) qtn).getJoinFilter().size(); i++) {
                  processFilter(jn.getJoinFilter().get(i));
              }
              jn.setOtherJoinOnFilter(processFilter(jn.getOtherJoinOnFilter()));
          }
          for (PlanNode child : qtn.getChildren()) {
              preProcess(child);
          }
      }
  
      private static Item processFilter(Item root) {
          if (root == null) {
              return null;
          }
  
          root = shortestFilter(root);
          root = processOneFilter(root);
          root = convertOrToIn(root);
          return root;
      }
  
      /**
       * optimizer 0=1/1=1/true
       */
      private static Item shortestFilter(Item root) {
          if (root == null)
              return null;
          if (root.canValued()) {
              boolean value = root.valBool();
              return new ItemBoolean(value);
          } else if (root.type().equals(Item.ItemType.COND_ITEM)) {
              ItemCond cond = (ItemCond) root;
              for (int index = 0; index < cond.getArgCount(); index++) {
                  Item shortedsub = shortestFilter(cond.arguments().get(index));
                  cond.arguments().set(index, shortedsub);
              }
              boolean isAnd = cond.functype().equals(ItemFunc.Functype.COND_AND_FUNC);
              List<Item> newSubFilters = new ArrayList<>();
              for (Item sub : cond.arguments()) {
                  if (sub == null)
                      continue;
                  if (sub.canValued()) {
                      boolean value = sub.valBool();
                      if (value && !isAnd)
                          return new ItemBoolean(true);
                      if (!value && isAnd)
                          return new ItemBoolean(false);
                  } else {
                      newSubFilters.add(sub);
                  }
              }
              if (isAnd)
                  return FilterUtils.and(newSubFilters);
              else
                  return FilterUtils.or(newSubFilters);
          } else {
              return root;
          }
      }
  
      /**
       * change "const op column" to "column op const"
       *
       * @param root
       * @return
       */
      private static Item processOneFilter(Item root) {
          if (root == null) {
              return null;
          }
          Item newRoot = root;
          if (root instanceof ItemBoolFunc2) {
              Item a = root.arguments().get(0);
              Item b = root.arguments().get(1);
              if (a.basicConstItem() && !b.basicConstItem() && !b.isWithSubQuery()) {
                  if (root instanceof ItemFuncGe) {
                      newRoot = new ItemFuncLe(b, a, root.getCharsetIndex());
                  } else if (root instanceof ItemFuncGt) {
                      newRoot = new ItemFuncLt(b, a, root.getCharsetIndex());
                  } else if (root instanceof ItemFuncLt) {
                      newRoot = new ItemFuncGt(b, a, root.getCharsetIndex());
                  } else if (root instanceof ItemFuncLe) {
                      newRoot = new ItemFuncGe(b, a, root.getCharsetIndex());
                  } else {
                      root.arguments().set(1, a);
                      root.arguments().set(0, b);
                      root.setItemName(null);
                  }
                  newRoot.getReferTables().addAll(root.getReferTables());
              }
          } else if (root instanceof ItemCond) {
              ItemCond condfun = (ItemCond) root;
              List<Item> newArgs = new ArrayList<>();
              for (Item arg : condfun.arguments()) {
                  Item newArg = processOneFilter(arg);
                  if (newArg != null)
                      newArgs.add(newArg);
              }
              if (condfun.functype().equals(ItemFunc.Functype.COND_AND_FUNC))
                  newRoot = FilterUtils.and(newArgs);
              else
                  newRoot = FilterUtils.or(newArgs);
          }
          return newRoot;
      }
  
      /**
       * change single Logicalfilter(or) into in
       *
       * @param filter
       */
      private static Item convertOrToIn(Item filter) {
          if (filter == null)
              return null;
          if (filter.type().equals(Item.ItemType.COND_ITEM)) {
              if (filter instanceof ItemCondAnd) {
                  ItemCondAnd andFilter = (ItemCondAnd) filter;
                  for (int index = 0; index < andFilter.getArgCount(); index++) {
                      andFilter.arguments().set(index, convertOrToIn(andFilter.arguments().get(index)));
                  }
                  andFilter.setItemName(null);
                  PlanUtil.refreshReferTables(andFilter);
                  return andFilter;
              } else {
  
                  int charsetIndex = 63;
                  // or
                  ItemCondOr orFilter = (ItemCondOr) filter;
                  HashMap<Item, Set<Item>> inMap = new HashMap<>();
                  List<Item> newSubFilterList = new ArrayList<>();
                  for (int index = 0; index < orFilter.getArgCount(); index++) {
                      Item subFilter = orFilter.arguments().get(index);
                      if (subFilter == null)
                          continue;
                      if (subFilter instanceof ItemFuncEqual) {
                          Item a = subFilter.arguments().get(0);
                          Item b = subFilter.arguments().get(1);
                          charsetIndex = subFilter.getCharsetIndex();
                          if (!a.canValued() && b.canValued()) {
                              if (!inMap.containsKey(a))
                                  inMap.put(a, new HashSet<Item>());
                              inMap.get(a).add(b);
                          } else { // stop convert
                              return filter;
                          }
                      } else {
                          Item subNew = convertOrToIn(subFilter);
                          newSubFilterList.add(subNew);
                      }
                  }
                  for (Map.Entry<Item, Set<Item>> entry : inMap.entrySet()) {
                      List<Item> args = new ArrayList<>();
                      args.add(entry.getKey());
                      args.addAll(entry.getValue());
                      ItemFuncIn inItem = new ItemFuncIn(args, false, charsetIndex);
                      PlanUtil.refreshReferTables(inItem);
                      newSubFilterList.add(inItem);
                  }
                  return FilterUtils.or(newSubFilterList);
              }
          }
          return filter;
      }
  
  }
  