/*
 * Copyright (C) 2016-2022 ActionTech.
 * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
 */

package com.actiontech.dble.route.parser.druid;

import com.actiontech.dble.DbleServer;
import com.actiontech.dble.config.model.sharding.SchemaConfig;
import com.actiontech.dble.config.model.sharding.table.BaseTableConfig;
import com.actiontech.dble.config.model.sharding.table.ChildTableConfig;
import com.actiontech.dble.config.model.sharding.table.GlobalTableConfig;
import com.actiontech.dble.config.model.sharding.table.ShardingTableConfig;
import com.actiontech.dble.plan.common.item.function.ItemCreate;
import com.actiontech.dble.route.parser.util.Pair;
import com.actiontech.dble.route.util.ConditionUtil;
import com.actiontech.dble.route.util.RouterUtil;
import com.actiontech.dble.util.CollectionUtil;
import com.actiontech.dble.util.StringUtil;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.SQLOrderBy;
import com.alibaba.druid.sql.ast.expr.*;
import com.alibaba.druid.sql.ast.statement.*;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlDeleteStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlUpdateStatement;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
import com.alibaba.druid.sql.visitor.SQLEvalVisitorUtils;
import com.alibaba.druid.stat.TableStat.Column;
import com.alibaba.druid.stat.TableStat.Condition;
import com.alibaba.druid.stat.TableStat.Relationship;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * ServerSchemaStatVisitor
 *
 * @author wang.dw
 */
public class ServerSchemaStatVisitor extends MySqlSchemaStatVisitor {
    private String notSupportMsg = null;
    private boolean hasOrCondition = false;
    private List<WhereUnit> whereUnits = new CopyOnWriteArrayList<>();
    private boolean routeConditionIgnore = false;
    private boolean inOuterJoin = false;
    private List<SQLSelect> subQueryList = new ArrayList<>();


    private List<SQLSelect> firstClassSubQueryList = new ArrayList();
    private Map<String, String> aliasMap = new LinkedHashMap<>();
    private Map<Pair<String/* schemaName */, String/* tableName */>, String/*referenceName*/> selectSchemaTables = new HashMap<>();
    private List<String> selectTableList = new ArrayList<>();
    private List<SQLExprTableSource> motifyTableSourceList = new ArrayList<>();
    private String currentTable;
    private String currentSchema;
    private boolean firstSelectBlock = true;
    private boolean containsInnerFunction = false;

    public ServerSchemaStatVisitor() {
    }

    public ServerSchemaStatVisitor(String currentSchema) {
        this.currentSchema = currentSchema;
    }

    private void reset() {
        this.relationships.clear();
        this.conditions.clear();
        this.whereUnits.clear();
        this.hasOrCondition = false;
    }

    public List<SQLSelect> getSubQueryList() {
        return subQueryList;
    }

    public String getNotSupportMsg() {
        return notSupportMsg;
    }

    private void addSubQuery(SQLSelect subQuery) {
        if (!CollectionUtil.contaionSpecificObject(subQueryList, subQuery)) {
            subQueryList.add(subQuery);
            SQLObject ob = subQuery.getParent();
            for (; ; ob = ob.getParent()) {
                if (ob != null) {
                    if (ob instanceof SQLQueryExpr || ob instanceof SQLBinaryOpExpr || ob instanceof SQLInSubQueryExpr) {
                        continue;
                    } else if (ob instanceof MySqlUpdateStatement) {
                        firstClassSubQueryList.add(subQuery);
                    } else if (ob instanceof MySqlDeleteStatement) {
                        firstClassSubQueryList.add(subQuery);
                    } else {
                        break;
                    }
                } else {
                    break;
                }
            }
        }
    }

    @Override
    public boolean visit(SQLInSubQueryExpr x) {
        super.visit(x);
        addSubQuery(x.getSubQuery());
        return true;
    }

    @Override
    public boolean visit(SQLQueryExpr x) {
        super.visit(x);
        addSubQuery(x.getSubQuery());
        return true;
    }

    @Override
    public boolean visit(SQLUnionQuery x) {
        Map<Pair<String/* schemaName */, String/* tableName */>, String/*referenceName*/> tmpSelectSchemaTables = new HashMap<>(selectSchemaTables.size());
        tmpSelectSchemaTables.putAll(selectSchemaTables);
        selectSchemaTables.clear();
        x.getLeft().accept(this);
        tmpSelectSchemaTables.putAll(selectSchemaTables);
        selectSchemaTables.clear();
        x.getRight().accept(this);
        selectSchemaTables.putAll(tmpSelectSchemaTables);
        tmpSelectSchemaTables.clear();
        if (x.getOrderBy() != null) {
            x.getOrderBy().accept(this);
        }
        if (x.getLimit() != null) {
            x.getLimit().accept(this);
        }
        return false;
    }

    @Override
    public boolean visit(SQLListExpr x) {
        super.visit(x);
        notSupportMsg = "Row Subqueries is not supported";
        return true;
    }

    @Override
    public boolean visit(SQLExistsExpr x) {
        super.visit(x);
        addSubQuery(x.getSubQuery());
        return true;
    }

    @Override
    public boolean visit(SQLAllExpr x) {
        super.visit(x);
        addSubQuery(x.getSubQuery());
        return true;
    }

    @Override
    public boolean visit(SQLSomeExpr x) {
        super.visit(x);
        addSubQuery(x.getSubQuery());
        return true;
    }

    @Override
    public boolean visit(SQLAnyExpr x) {
        super.visit(x);
        addSubQuery(x.getSubQuery());
        return true;
    }

    @Override
    public boolean visit(SQLSelectItem x) {
        //need to protect parser SQLSelectItem, or SQLBinaryOpExpr may add to whereUnit
        // eg:id =1 will add to whereUnit
        routeConditionIgnore = true;
        SQLExpr sqlExpr = x.getExpr();
        if (sqlExpr instanceof SQLMethodInvokeExpr && ItemCreate.getInstance().isInnerFunc(sqlExpr.toString().replace("(", "").replace(")", ""))) {
            containsInnerFunction = true;
        } else if (sqlExpr instanceof SQLIdentifierExpr && ItemCreate.getInstance().isInnerFunc(sqlExpr.toString())) {
            containsInnerFunction = true;
        }
        sqlExpr.accept(this);
        //alias for select item is useless
        //        String alias = x.getAlias();
        //
        //        Map<String, String> aliasMap = this.getAliasMap();
        //        if (alias != null && (!alias.isEmpty()) && aliasMap != null) {
        //            if (x.getExpr() instanceof SQLName) {
        //                boolean isSelf = false;
        //                String itemName = x.getExpr().toString();
        //                if (DbleServer.getInstance().getSystemVariables().isLowerCaseTableNames()) {
        //                    isSelf = StringUtil.equalsIgnoreCase(alias, itemName);
        //                } else {
        //                    isSelf = StringUtil.equals(alias, itemName);
        //                }
        //                if (!isSelf) {
        //                    putAliasToMap(aliasMap, alias, x.getExpr().toString());
        //                }
        //            } else {
        //                putAliasToMap(aliasMap, alias, null);
        //            }
        //        }
        return false;
    }

    @Override
    public boolean visit(SQLJoinTableSource x) {
        switch (x.getJoinType()) {
            case LEFT_OUTER_JOIN:
            case RIGHT_OUTER_JOIN:
            case FULL_OUTER_JOIN:
                inOuterJoin = true;
                break;
            default:
                inOuterJoin = false;
                break;
        }

        SQLTableSource left = x.getLeft(), right = x.getRight();

        left.accept(this);
        right.accept(this);

        SQLExpr condition = x.getCondition();
        if (condition != null) {
            condition.accept(this);
        }

        if (x.getUsing().size() > 0 &&
                left instanceof SQLExprTableSource && right instanceof SQLExprTableSource) {
            SQLExpr leftExpr = ((SQLExprTableSource) left).getExpr();
            SQLExpr rightExpr = ((SQLExprTableSource) right).getExpr();

            for (SQLExpr expr : x.getUsing()) {
                if (expr instanceof SQLIdentifierExpr) {
                    String name = ((SQLIdentifierExpr) expr).getName();
                    /*
                    when the shard1 a join shard2 b using(id)
                    the intermediate condition should be a.id = b.id instead of shard1.id = shard2.id
                     */
                    SQLPropertyExpr leftPropExpr = new SQLPropertyExpr(leftExpr, name);
                    if (left.getAlias() != null) {
                        leftPropExpr.setOwner(left.getAlias());
                    }
                    SQLPropertyExpr rightPropExpr = new SQLPropertyExpr(rightExpr, name);
                    if (right.getAlias() != null) {
                        rightPropExpr.setOwner(right.getAlias());
                    }

                    leftPropExpr.setResolvedTableSource(left);
                    rightPropExpr.setResolvedTableSource(right);

                    SQLBinaryOpExpr usingCondition = new SQLBinaryOpExpr(leftPropExpr, SQLBinaryOperator.Equality, rightPropExpr);
                    usingCondition.accept(this);
                }
            }
        }

        inOuterJoin = false;
        return false;
    }

    @Override
    public boolean visit(SQLSubqueryTableSource x) {
        Map<Pair<String/* schemaName */, String/* tableName */>, String/*referenceName*/> tmpSelectSchemaTables = new HashMap<>(selectSchemaTables.size());
        tmpSelectSchemaTables.putAll(selectSchemaTables);
        selectSchemaTables.clear();
        putAliasToMap(x.getAlias(), "subquery");
        boolean visitResult = super.visit(x);
        selectSchemaTables.putAll(tmpSelectSchemaTables);
        tmpSelectSchemaTables.clear();
        return visitResult;
    }

    @Override
    public boolean visit(SQLSelectStatement x) {
        aliasMap.clear();
        selectTableList.clear();
        return true;
    }

    @Override
    public boolean visit(SQLSelectGroupByClause x) {
        routeConditionIgnore = true;
        return true;
    }

    @Override
    public boolean visit(SQLBetweenExpr x) {
        if (x.isNot()) {
            return true;
        }
        String begin;
        if (x.beginExpr instanceof SQLCharExpr) {
            begin = (String) ((SQLCharExpr) x.beginExpr).getValue();
        } else if (x.beginExpr instanceof SQLNullExpr) {
            begin = x.beginExpr.toString();
        } else {
            Object value = SQLEvalVisitorUtils.eval(this.getDbType(), x.beginExpr, this.getParameters(), false);
            if (value != null) {
                begin = value.toString();
            } else {
                begin = x.beginExpr.toString();
            }

        }
        String end;
        if (x.endExpr instanceof SQLCharExpr) {
            end = (String) ((SQLCharExpr) x.endExpr).getValue();
        } else if (x.endExpr instanceof SQLNullExpr) {
            end = x.endExpr.toString();
        } else {
            Object value = SQLEvalVisitorUtils.eval(this.getDbType(), x.endExpr, this.getParameters(), false);
            if (value != null) {
                end = value.toString();
            } else {
                end = x.endExpr.toString();
            }
        }
        Column column = getColumn(x);
        if (column == null) {
            return true;
        }

        Condition condition = new Condition(column, "between");
        this.conditions.add(condition);


        condition.getValues().add(begin);
        condition.getValues().add(end);


        return true;
    }

    @Override
    public boolean visit(SQLInListExpr x) {
        if (routeConditionIgnore) {
            return false;
        }
        if (x.isNot()) {
            handleCondition(x.getExpr(), "NOT IN", x.getTargetList());
        } else {
            handleCondition(x.getExpr(), "IN", x.getTargetList());
        }
        return true;
    }

    @Override
    public boolean visit(SQLUnaryExpr x) {
        switch (x.getOperator()) {
            case Not:
            // It doesn't match now it might match in the future，don't delete
            case NOT:
            case Compl:
                return false;
            default:
                break;
        }
        return true;
    }

    @Override
    public boolean visit(SQLBinaryOpExpr x) {
        x.getLeft().setParent(x);
        x.getRight().setParent(x);

        switch (x.getOperator()) {
            case Equality:
            case LessThanOrEqualOrGreaterThan:
            case Is:
            case IsNot:
                if (!routeConditionIgnore && !inOuterJoin) {
                    handleCondition(x.getLeft(), x.getOperator().name, x.getRight());
                    handleCondition(x.getRight(), x.getOperator().name, x.getLeft());
                }
                if (!routeConditionIgnore) {
                    handleRelationship(x.getLeft(), x.getOperator().name, x.getRight());
                }
                break;
            case BooleanOr:
                //remove always true
                if (!RouterUtil.isConditionAlwaysTrue(x) && !routeConditionIgnore && !inOuterJoin) {
                    hasOrCondition = true;
                    WhereUnit whereUnit;
                    whereUnit = new WhereUnit(x);
                    whereUnit.addOutConditions(getConditions());
                    whereUnit.addOutRelationships(getRelationships());
                    whereUnits.add(whereUnit);
                } else if (!RouterUtil.isConditionAlwaysTrue(x) && !routeConditionIgnore) {
                    WhereUnit whereUnit;
                    whereUnit = new WhereUnit(x);
                    whereUnit.addOutRelationships(getRelationships());
                    whereUnits.add(whereUnit);
                }
                return false;
            case Like:
            case NotLike:
            case NotEqual:
            case GreaterThan:
            case GreaterThanOrEqual:
            case LessThan:
            case LessThanOrEqual:
            default:
                break;
        }
        return true;
    }

    @Override
    public boolean visit(MySqlInsertStatement x) {
        SQLName sqlName = x.getTableName();
        if (sqlName != null) {
            currentTable = sqlName.toString();
        }
        return false;
    }

    // DUAL
    @Override
    public boolean visit(MySqlDeleteStatement x) {
        aliasMap.clear();
        accept(x.getFrom());
        accept(x.getUsing());
        x.getTableSource().accept(this);

        if (x.getTableSource() instanceof SQLExprTableSource) {
            SQLName tableName = (SQLName) ((SQLExprTableSource) x.getTableSource()).getExpr();
            currentTable = tableName.toString();
        }

        accept(x.getWhere());

        accept(x.getOrderBy());
        accept(x.getLimit());

        return false;
    }

    @Override
    public boolean visit(SQLUpdateStatement x) {
        aliasMap.clear();
        if (x.getTableSource() instanceof SQLJoinTableSource) {
            SQLJoinTableSource tableSource = (SQLJoinTableSource) x.getTableSource();
            if (tableSource.getRight() instanceof SQLExprTableSource) {
                String ident = tableSource.getRight().toString();
                putAliasToMap(ident, ident);
                String alias = tableSource.getRight().getAlias();
                if (alias != null) {
                    putAliasToMap(alias, ident);
                }
                motifyTableSourceList.add((SQLExprTableSource) tableSource.getRight());
            } else {
                tableSource.getRight().accept(this);
            }

            if (tableSource.getLeft() instanceof SQLExprTableSource) {
                String ident = tableSource.getLeft().toString();
                putAliasToMap(ident, ident);
                String alias = tableSource.getLeft().getAlias();
                if (alias != null) {
                    putAliasToMap(alias, ident);
                }
                motifyTableSourceList.add((SQLExprTableSource) tableSource.getLeft());
            } else {
                tableSource.getLeft().accept(this);
            }
        } else {
            SQLName identName = x.getTableName();
            if (identName != null) {
                String ident = identName.toString();
                currentTable = ident;
                putAliasToMap(ident, ident);
                String alias = x.getTableSource().getAlias();
                if (alias != null) {
                    putAliasToMap(alias, ident);
                }
            } else {
                x.getTableSource().accept(this);
            }
        }
        accept(x.getItems());
        accept(x.getWhere());

        return false;
    }

    @Override
    public boolean visit(SQLUpdateSetItem x) {
        return true;
    }

    @Override
    public boolean visit(SQLExprTableSource x) {
        if (this.isSimpleExprTableSource(x)) {
            String ident = x.getExpr().toString();
            currentTable = ident;
            selectTableList.add(ident);
            motifyTableSourceList.add(x);
            String alias = x.getAlias();
            if (alias != null && !aliasMap.containsKey(alias)) {
                putAliasToMap(alias, ident);
            }

            if (!aliasMap.containsKey(ident)) {
                putAliasToMap(ident, ident);
            }
        } else {
            this.accept(x.getExpr());
        }
        return false;
    }

    @Override
    public boolean visit(SQLOrderBy x) {
        routeConditionIgnore = true;
        return super.visit(x);
    }

    @Override
    public boolean visit(SQLNotExpr x) {
        routeConditionIgnore = true;
        return true;
    }

    @Override
    public boolean visit(SQLSelect x) {
        if (x.getOrderBy() != null) {
            x.getOrderBy().setParent(x);
        }
        this.accept(x.getWithSubQuery());
        Map<Pair<String/* schemaName */, String/* tableName */>, String/*referenceName*/> tmpSelectSchemaTables = new HashMap<>(selectSchemaTables.size());
        tmpSelectSchemaTables.putAll(selectSchemaTables);
        selectSchemaTables.clear();
        this.accept(x.getQuery());
        selectSchemaTables.putAll(tmpSelectSchemaTables);
        tmpSelectSchemaTables.clear();
        this.accept(x.getOrderBy());
        return false;
    }

    @Override
    public boolean visit(SQLSelectQueryBlock x) {
        if (firstSelectBlock) {
            firstSelectBlock = false;
        } else {
            whereUnits.addAll(getAllWhereUnit());
            this.relationships.clear();
            this.conditions.clear();
            this.hasOrCondition = false;
        }
        return true;
    }

    @Override
    public void endVisit(SQLOrderBy x) {
        routeConditionIgnore = false;
    }

    @Override
    public void endVisit(SQLSelectGroupByClause x) {
        routeConditionIgnore = false;
    }

    @Override
    public void endVisit(SQLSelectItem x) {
        routeConditionIgnore = false;
    }

    @Override
    public void endVisit(SQLNotExpr x) {
        routeConditionIgnore = false;
    }

    @Override
    public void endVisit(SQLSelect x) {
    }

    @Override
    public void endVisit(MySqlDeleteStatement x) {
    }

    @Override
    protected Column getColumn(SQLExpr expr) {
        if (aliasMap == null) {
            return null;
        }

        if (expr instanceof SQLPropertyExpr) {
            return getColumnByExpr((SQLPropertyExpr) expr);
        }

        if (expr instanceof SQLIdentifierExpr) {
            return getColumnByExpr((SQLIdentifierExpr) expr);
        }

        if (expr instanceof SQLBetweenExpr) {
            return getColumnByExpr((SQLBetweenExpr) expr);
        }
        return null;
    }

    @Override
    protected void handleCondition(SQLExpr expr, String operator, SQLExpr... valueExprs) {
        if (expr instanceof SQLCastExpr) {
            expr = ((SQLCastExpr) expr).getExpr();
        }

        Column column = this.getColumn(expr);
        if (column != null) {
            Condition condition = new Condition(column, operator);
            this.conditions.add(condition);

            SQLExpr[] var12 = valueExprs;
            int var13 = valueExprs.length;

            for (int var8 = 0; var8 < var13; ++var8) {
                SQLExpr item = var12[var8];
                Column valueColumn = this.getColumn(item);
                if (valueColumn == null) {
                    if (item instanceof SQLNullExpr) {
                        condition.getValues().add(item);
                    } else {
                        if (item instanceof SQLHexExpr) {
                            condition.getValues().add(item);
                        } else {
                            Object value = SQLEvalVisitorUtils.eval(this.getDbType(), item, this.getParameters(), false);
                            condition.getValues().add(value);
                        }
                    }
                }
            }

        }
    }

    private Column getColumnByExpr(SQLBetweenExpr betweenExpr) {
        if (betweenExpr.getTestExpr() != null) {
            String tableName = null;
            String column = null;
            if (betweenExpr.getTestExpr() instanceof SQLPropertyExpr) { //field has alias
                tableName = ((SQLIdentifierExpr) ((SQLPropertyExpr) betweenExpr.getTestExpr()).getOwner()).getName();
                column = ((SQLPropertyExpr) betweenExpr.getTestExpr()).getName();
            } else if (betweenExpr.getTestExpr() instanceof SQLIdentifierExpr) {
                column = ((SQLIdentifierExpr) betweenExpr.getTestExpr()).getName();
                tableName = getOwnerTableName(betweenExpr, column);
            }
            if (tableName != null && !"".equals(tableName)) {
                checkAliasInColumn(tableName, false);
                return new Column(tableName, column);
            }
        }
        return null;
    }

    private Column getColumnByExpr(SQLIdentifierExpr expr) {
        String column = expr.getName().replaceAll("`", "");
        if (currentSchema == null) {
            return currentTable == null ? null : new Column(currentTable, column);
        } else {
            int i = 0;
            String table = null;
            for (Map.Entry<Pair<String/* schemaName */, String/* tableName */>, String/*referenceName*/> k : selectSchemaTables.entrySet()) {
                String tableName = k.getKey().getValue();
                final String schemaName = k.getKey().getKey();
                if (schemaName == null || tableName.equals("subquery")) {
                    continue;
                }
                SchemaConfig schemaConfig;
                if ((schemaConfig = DbleServer.getInstance().getConfig().getSchemas().get(schemaName)) != null) {
                    BaseTableConfig tableConfig;
                    if ((tableConfig = schemaConfig.getTable(tableName)) != null && ((tableConfig instanceof ShardingTableConfig && StringUtil.equalsIgnoreCase(((ShardingTableConfig) tableConfig).getShardingColumn(), column)) ||
                            (tableConfig instanceof ChildTableConfig && StringUtil.equalsIgnoreCase(((ChildTableConfig) tableConfig).getJoinColumn(), column)))) {
                        table = k.getValue();
                        if (++i > 1) {
                            break;
                        }
                    } else if (tableConfig instanceof GlobalTableConfig) {
                        table = k.getValue();
                        if (++i > 1) {
                            break;
                        }
                    }
                }
            }
            if (i == 1) {
                return new Column(table, column);
            } else {
                return null;
            }
        }
    }

    private Column getColumnByExpr(SQLPropertyExpr expr) {
        SQLExpr owner = expr.getOwner();
        String column = expr.getName();
        boolean containSchema = false;
        if (owner instanceof SQLIdentifierExpr || owner instanceof SQLPropertyExpr) {
            String tableName;
            if (owner instanceof SQLPropertyExpr) {
                tableName = ((SQLPropertyExpr) owner).getName();
                if (((SQLPropertyExpr) owner).getOwner() instanceof SQLIdentifierExpr) {
                    containSchema = true;
                    tableName = ((SQLIdentifierExpr) ((SQLPropertyExpr) owner).getOwner()).getName() + "." + tableName;
                }
            } else {
                tableName = ((SQLIdentifierExpr) owner).getName();
            }
            checkAliasInColumn(tableName, containSchema);
            return new Column(tableName, column);
        }

        return null;
    }

    private void checkAliasInColumn(String tableName, boolean containSchema) {
        if (DbleServer.getInstance().getSystemVariables().isLowerCaseTableNames()) {
            tableName = tableName.toLowerCase();
        }
        if (aliasMap.containsKey(tableName)) {
            return;
        }
        if (containSchema) {
            putAliasToMap(tableName, tableName);
            return;
        }
        String tempStr;
        boolean removeApostrophe = true;
        if (StringUtil.containsApostrophe(tableName)) {
            tempStr = tableName.replace("`", "");
        } else {
            tempStr = "`" + tableName + "`";
            removeApostrophe = false;
        }
        putAliasToMap(tableName, aliasMap.getOrDefault(tempStr, tempStr), removeApostrophe);
    }

    /**
     * get table name of field in between expr
     */
    private String getOwnerTableName(SQLBetweenExpr betweenExpr, String column) {
        if (selectSchemaTables.size() == 1) { //only has 1 table
            return selectSchemaTables.values().iterator().next();
        } else if (selectSchemaTables.size() == 0) { //no table
            return "";
        } else { // multi tables
            for (Column col : columns.values()) {
                if (col.getName().equals(column)) {
                    return col.getTable();
                }
            }

            //parser from parent
            SQLObject parent = betweenExpr.getParent();
            if (parent instanceof SQLBinaryOpExpr) {
                parent = parent.getParent();
            }

            if (parent instanceof MySqlSelectQueryBlock) {
                MySqlSelectQueryBlock select = (MySqlSelectQueryBlock) parent;
                if (select.getFrom() instanceof SQLJoinTableSource) {
                    SQLJoinTableSource joinTableSource = (SQLJoinTableSource) select.getFrom();
                    //FIXME :left as driven table
                    return joinTableSource.getLeft().toString();
                } else if (select.getFrom() instanceof SQLExprTableSource) {
                    return select.getFrom().toString();
                }
            } else if (parent instanceof SQLUpdateStatement) {
                SQLUpdateStatement update = (SQLUpdateStatement) parent;
                return update.getTableName().getSimpleName();
            } else if (parent instanceof SQLDeleteStatement) {
                SQLDeleteStatement delete = (SQLDeleteStatement) parent;
                return delete.getTableName().getSimpleName();
            }
        }
        return "";
    }

    /**
     * Loop all the splitedExprList and try to accept them again
     * if the Expr in splitedExprList is still  a OR-Expr just deal with it
     * <p>
     * This function only recursively all child splitedExpr and make them split again
     */
    private void loopFindSubOrCondition(List<WhereUnit> whereUnitList) {
        List<WhereUnit> subWhereUnits = new ArrayList<>();
        for (WhereUnit whereUnit : whereUnitList) {
            if (whereUnit.getSplitedExprList().size() > 0) {
                List<SQLExpr> removeSplitedList = new ArrayList<>();
                for (SQLExpr sqlExpr : whereUnit.getSplitedExprList()) {
                    reset();
                    if (isExprHasOr(sqlExpr)) {
                        removeSplitedList.add(sqlExpr);
                        for (WhereUnit subWhereUnit : whereUnits) {
                            splitUntilNoOr(subWhereUnit);
                            whereUnit.addSubWhereUnit(subWhereUnit);
                            subWhereUnits.add(subWhereUnit);
                        }
                    } else {
                        this.relationships.clear();
                        this.conditions.clear();
                    }
                }
                if (removeSplitedList.size() > 0) {
                    whereUnit.getSplitedExprList().removeAll(removeSplitedList);
                }
            }
            subWhereUnits.addAll(whereUnit.getSubWhereUnit());
        }
        if (subWhereUnits.size() > 0) {
            loopFindSubOrCondition(subWhereUnits);
        }
    }

    /**
     * reBuild WhereUnits
     */
    private void reBuildWhereUnits() {
        relationMerge(getRelationships());
        if (conditions.size() > 0) {
            for (int i = 0; i < whereUnits.size(); i++) {
                WhereUnit orgUnit = whereUnits.get(i);
                WhereUnit whereUnit = new WhereUnit();
                whereUnit.setFinishedParse(true);
                whereUnit.addOutConditions(getConditions());
                whereUnit.addOutRelationships(getRelationships());
                WhereUnit innerWhereUnit = new WhereUnit(orgUnit.getCanSplitExpr());
                whereUnit.addSubWhereUnit(innerWhereUnit);
                whereUnits.set(i, whereUnit);
            }
        }
    }

    /**
     * Equal or not equal to relationship transfer
     * E.g.  ntest.id = mtest.id and xtest.id = ntest.id
     * we try to add a derivative relationship "xtest.id = mtest.id"
     * so when there has limit in mtest.id the xtest.id can also get the limit
     * P.S.:Only order insensitive operator can be optimize,like = or <=>
     */
    private void relationMerge(Set<Relationship> relationships) {
        HashSet<Relationship> loopReSet = new HashSet<>();
        loopReSet.addAll(relationships);
        for (Relationship re : loopReSet) {
            for (Relationship inv : loopReSet) {
                if (inv.getOperator().equals(re.getOperator()) && (inv.getOperator().equals("=") || inv.getOperator().equals("<=>"))) {
                    List<Column> tempSet = new ArrayList<>();
                    addAndCheckDuplicate(tempSet, re.getLeft());
                    addAndCheckDuplicate(tempSet, re.getRight());
                    addAndCheckDuplicate(tempSet, inv.getLeft());
                    addAndCheckDuplicate(tempSet, inv.getRight());
                    if (tempSet.size() == 2) {
                        Relationship rs1 = new Relationship(tempSet.get(0), tempSet.get(1), inv.getOperator());
                        Relationship rs2 = new Relationship(tempSet.get(1), tempSet.get(0), inv.getOperator());
                        if (!relationships.contains(rs1) && !relationships.contains(rs2)) {
                            relationships.add(rs1);
                        }
                    }
                }
            }
        }
    }

    /**
     * find how mach non-repeating column in 2 relationships
     * E.g. A = B and B = C the result size is 2
     * A = B and C = D the result size is 4
     * A = B and B = A the result size is 0
     * when the result size is 2,we can know that there is 3 columns in  2 relationships
     * and the derivative relationship may be needed
     */
    private void addAndCheckDuplicate(List<Column> tempSet, Column tmp) {
        if (tempSet.contains(tmp)) {
            tempSet.remove(tmp);
        } else {
            tempSet.add(tmp);
        }
    }

    private boolean isExprHasOr(SQLExpr expr) {
        expr.accept(this);
        reBuildWhereUnits();
        return hasOrCondition;
    }

    public List<String> getSelectTableList() {
        return selectTableList;
    }


    public boolean isContainsInnerFunction() {
        return containsInnerFunction;
    }

    /**
     * turn all the condition in or into conditionList
     * exp (conditionA OR conditionB) into conditionList{conditionA,conditionB}
     * so the conditionA,conditionB can be group with outer conditions
     */
    private void resetConditionsFromWhereUnit(WhereUnit whereUnit) {
        if (!whereUnit.isFinishedExtend()) {
            List<List<Condition>> retList = new ArrayList<>();
            List<Condition> outSideCondition = new ArrayList<>();
            outSideCondition.addAll(conditions);
            List<Relationship> outSideRelationship = new ArrayList<>();
            outSideRelationship.addAll(relationships);
            this.conditions.clear();
            this.relationships.clear();
            for (SQLExpr sqlExpr : whereUnit.getSplitedExprList()) {
                sqlExpr.accept(this);
                List<Condition> conds = new ArrayList<>();
                conds.addAll(getConditions());
                conds.addAll(outSideCondition);
                Set<Relationship> relations = new HashSet<>();
                relations.addAll(getRelationships());
                relations.addAll(outSideRelationship);
                ConditionUtil.extendConditionsFromRelations(conds, relations);
                retList.add(conds);
                this.conditions.clear();
                this.relationships.clear();
            }
            whereUnit.setOrConditionList(retList);

            for (WhereUnit subWhere : whereUnit.getSubWhereUnit()) {
                resetConditionsFromWhereUnit(subWhere);
            }
            whereUnit.setFinishedExtend(true);
        }
    }

    /**
     * split on conditions into whereUnit..splitedExprList
     */
    private void splitUntilNoOr(WhereUnit whereUnit) {
        if (whereUnit.isFinishedParse()) {
            if (whereUnit.getSubWhereUnit().size() > 0) {
                for (int i = 0; i < whereUnit.getSubWhereUnit().size(); i++) {
                    splitUntilNoOr(whereUnit.getSubWhereUnit().get(i));
                }
            }
        } else {
            SQLBinaryOpExpr expr = whereUnit.getCanSplitExpr();
            if (expr == null) {
                whereUnit.setFinishedParse(true);
            } else if (expr.getOperator() == SQLBinaryOperator.BooleanOr) {
                addExprNotFalse(whereUnit, expr.getRight());
                if (expr.getLeft() instanceof SQLBinaryOpExpr) {
                    whereUnit.setCanSplitExpr((SQLBinaryOpExpr) expr.getLeft());
                    splitUntilNoOr(whereUnit);
                } else {
                    addExprNotFalse(whereUnit, expr.getLeft());
                }
            } else {
                addExprNotFalse(whereUnit, expr);
                whereUnit.setFinishedParse(true);
            }
        }
    }

    private void addExprNotFalse(WhereUnit whereUnit, SQLExpr expr) {
        if (!RouterUtil.isConditionAlwaysFalse(expr)) {
            whereUnit.addSplitedExpr(expr);
        }
    }

    private void putAliasToMap(String name, String value) {
        putAliasToMap(name, value, true);
    }

    private void putAliasToMap(String name, String value, boolean removeApostrophe) {
        if (removeApostrophe) {
            value = value.replace("`", "");
        }
        if (name != null) {
            if (DbleServer.getInstance().getSystemVariables().isLowerCaseTableNames()) {
                name = name.toLowerCase();
                value = value.toLowerCase();
            }
            // remove alias
            String space = " ";
            if (name.contains(space)) {
                name = name.substring(0, name.indexOf(space));
            }

            if (value.contains(space)) {
                value = value.substring(0, value.indexOf(space));
            }
            aliasMap.put(name, value);

            String[] tmpArray = value.split("\\.");
            String tmpSchema = tmpArray.length == 2 ? tmpArray[0] : currentSchema;
            String tmpTable = tmpArray.length == 2 ? tmpArray[1] : tmpArray[0];
            selectSchemaTables.put(new Pair<>(tmpSchema, tmpTable), name);
        }
    }

    public Map<String, String> getAliasMap() {
        return aliasMap;
    }

    public String getCurrentTable() {
        return currentTable;
    }

    public void setCurrentTable(String currentTable) {
        this.currentTable = currentTable;
    }

    private static void mergeOuterRelations(WhereUnit whereUnit) {
        if (whereUnit.getSubWhereUnit().size() > 0) {
            for (WhereUnit sub : whereUnit.getSubWhereUnit()) {
                mergeOuterRelations(sub);
                if (whereUnit.getOutRelationships().size() > 0) {
                    for (List<Condition> subConditionList : sub.getOrConditionList()) {
                        ConditionUtil.extendConditionsFromRelations(subConditionList, whereUnit.getOutRelationships());
                    }
                }
            }
        }
    }

    public List<WhereUnit> getAllWhereUnit() {
        List<WhereUnit> storedWhereUnits = new ArrayList<>();
        if (this.hasOrCondition) {
            //pre deal with condition and whereUnits
            reBuildWhereUnits();
            storedWhereUnits.addAll(whereUnits);

            //split according to or expr
            for (WhereUnit whereUnit : whereUnits) {
                splitUntilNoOr(whereUnit);
            }
            whereUnits.clear();
            loopFindSubOrCondition(storedWhereUnits);

            for (WhereUnit whereUnit : storedWhereUnits) {
                this.resetConditionsFromWhereUnit(whereUnit);
            }
        } else {
            storedWhereUnits.addAll(whereUnits);
            whereUnits.clear();
            WhereUnit whereUnit = generateWhereUnit();
            if (whereUnit != null) {
                storedWhereUnits.add(whereUnit);
            }
        }

        for (WhereUnit whereUnit : storedWhereUnits) {
            mergeOuterRelations(whereUnit);
        }

        return storedWhereUnits;
    }

    private WhereUnit generateWhereUnit() {
        List<Condition> conditionList = new ArrayList<>();
        conditionList.addAll(this.getConditions());
        ConditionUtil.extendConditionsFromRelations(conditionList, this.relationships);
        if (conditionList.size() == 0 && this.relationships.size() == 0) {
            return null;
        }
        WhereUnit whereUnit = new WhereUnit();
        whereUnit.setFinishedParse(true);
        List<List<Condition>> retList = new ArrayList<>();
        retList.add(conditionList);
        whereUnit.setOrConditionList(retList);
        whereUnit.addOutRelationships(this.relationships);
        return whereUnit;
    }

    public List<SQLSelect> getFirstClassSubQueryList() {
        return firstClassSubQueryList;
    }


    public List<WhereUnit> getWhereUnits() {
        return whereUnits;
    }


    public List<SQLExprTableSource> getMotifyTableSourceList() {
        return motifyTableSourceList;
    }
}
