org.apache.phoenix.hive.query.PhoenixQueryBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.phoenix.hive.query.PhoenixQueryBuilder.java

Source

/*
 * 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.phoenix.hive.query;

import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hive.ql.plan.ExprNodeConstantDesc;
import org.apache.hadoop.hive.serde.serdeConstants;
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo;
import org.apache.hadoop.mapred.JobConf;
import org.apache.phoenix.hive.constants.PhoenixStorageHandlerConstants;
import org.apache.phoenix.hive.ql.index.IndexSearchCondition;
import org.apache.phoenix.hive.util.ColumnMappingUtils;
import org.apache.phoenix.hive.util.PhoenixStorageHandlerUtil;
import org.apache.phoenix.hive.util.PhoenixUtil;
import org.apache.phoenix.util.StringUtil;

import static org.apache.phoenix.hive.util.ColumnMappingUtils.getColumnMappingMap;

/**
 * Query builder. Produces a query depending on the colummn list and conditions
 */

public class PhoenixQueryBuilder {

    private static final Log LOG = LogFactory.getLog(PhoenixQueryBuilder.class);

    private static final String QUERY_TEMPLATE = "select $HINT$ $COLUMN_LIST$ from $TABLE_NAME$";

    private static final PhoenixQueryBuilder QUERY_BUILDER = new PhoenixQueryBuilder();

    private PhoenixQueryBuilder() {
        if (LOG.isInfoEnabled()) {
            LOG.info("PhoenixQueryBuilder created");
        }
    }

    public static PhoenixQueryBuilder getInstance() {
        return QUERY_BUILDER;
    }

    private void addConditionColumnToReadColumn(List<String> readColumnList, List<String> conditionColumnList) {
        if (readColumnList.isEmpty()) {
            return;
        }

        for (String conditionColumn : conditionColumnList) {
            if (!readColumnList.contains(conditionColumn)) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Condition column " + conditionColumn + " does not exist in " + "read-columns.");
                }

                readColumnList.add(conditionColumn);
            }
        }
    }

    private String makeQueryString(JobConf jobConf, String tableName, List<String> readColumnList,
            String whereClause, String queryTemplate, String hints, Map<String, TypeInfo> columnTypeMap)
            throws IOException {
        StringBuilder sql = new StringBuilder();
        List<String> conditionColumnList = buildWhereClause(jobConf, sql, whereClause, columnTypeMap);
        readColumnList = replaceColumns(jobConf, readColumnList);

        if (conditionColumnList.size() > 0) {
            addConditionColumnToReadColumn(readColumnList, conditionColumnList);
            readColumnList = ColumnMappingUtils.quoteColumns(readColumnList);
            sql.insert(0,
                    queryTemplate.replace("$HINT$", hints)
                            .replace("$COLUMN_LIST$", getSelectColumns(jobConf, tableName, readColumnList))
                            .replace("$TABLE_NAME$", tableName));
        } else {
            readColumnList = ColumnMappingUtils.quoteColumns(readColumnList);
            sql.append(queryTemplate.replace("$HINT$", hints)
                    .replace("$COLUMN_LIST$", getSelectColumns(jobConf, tableName, readColumnList))
                    .replace("$TABLE_NAME$", tableName));
        }

        if (LOG.isInfoEnabled()) {
            LOG.info("Input query : " + sql.toString());
        }

        return sql.toString();
    }

    private static String findReplacement(JobConf jobConf, String column) {
        Map<String, String> columnMappingMap = getColumnMappingMap(
                jobConf.get(PhoenixStorageHandlerConstants.PHOENIX_COLUMN_MAPPING));
        if (columnMappingMap != null && columnMappingMap.containsKey(column)) {
            return columnMappingMap.get(column);
        } else {
            return column;
        }
    }

    private static List<String> replaceColumns(JobConf jobConf, List<String> columnList) {
        Map<String, String> columnMappingMap = getColumnMappingMap(
                jobConf.get(PhoenixStorageHandlerConstants.PHOENIX_COLUMN_MAPPING));
        if (columnMappingMap != null) {
            List<String> newList = Lists.newArrayList();
            for (String column : columnList) {
                if (columnMappingMap.containsKey(column)) {
                    newList.add(columnMappingMap.get(column));
                } else {
                    newList.add(column);
                }
            }
            return newList;
        }
        return null;
    }

    private String makeQueryString(JobConf jobConf, String tableName, List<String> readColumnList,
            List<IndexSearchCondition> searchConditions, String queryTemplate, String hints) throws IOException {
        StringBuilder query = new StringBuilder();
        List<String> conditionColumnList = buildWhereClause(jobConf, query, searchConditions);

        if (conditionColumnList.size() > 0) {
            readColumnList = replaceColumns(jobConf, readColumnList);
            addConditionColumnToReadColumn(readColumnList, conditionColumnList);
            query.insert(0,
                    queryTemplate.replace("$HINT$", hints)
                            .replace("$COLUMN_LIST$", getSelectColumns(jobConf, tableName, readColumnList))
                            .replace("$TABLE_NAME$", tableName));
        } else {
            readColumnList = replaceColumns(jobConf, readColumnList);
            query.append(queryTemplate.replace("$HINT$", hints)
                    .replace("$COLUMN_LIST$", getSelectColumns(jobConf, tableName, readColumnList))
                    .replace("$TABLE_NAME$", tableName));
        }

        if (LOG.isInfoEnabled()) {
            LOG.info("Input query : " + query.toString());
        }

        return query.toString();
    }

    private String getSelectColumns(JobConf jobConf, String tableName, List<String> readColumnList)
            throws IOException {
        String selectColumns = Joiner.on(PhoenixStorageHandlerConstants.COMMA)
                .join(ColumnMappingUtils.quoteColumns(readColumnList));

        if (PhoenixStorageHandlerConstants.EMPTY_STRING.equals(selectColumns)) {
            selectColumns = "*";
        } else {
            if (PhoenixStorageHandlerUtil.isTransactionalTable(jobConf)) {
                List<String> pkColumnList = PhoenixUtil.getPrimaryKeyColumnList(jobConf, tableName);
                StringBuilder pkColumns = new StringBuilder();

                for (String pkColumn : pkColumnList) {
                    if (!readColumnList.contains(pkColumn)) {
                        pkColumns.append("\"").append(pkColumn).append("\"" + PhoenixStorageHandlerConstants.COMMA);
                    }
                }

                selectColumns = pkColumns.toString() + selectColumns;
            }
        }

        return selectColumns;
    }

    public String buildQuery(JobConf jobConf, String tableName, List<String> readColumnList, String whereClause,
            Map<String, TypeInfo> columnTypeMap) throws IOException {
        String hints = getHint(jobConf, tableName);

        if (LOG.isDebugEnabled()) {
            LOG.debug("Building query with columns : " + readColumnList + " table name : " + tableName
                    + "  with where conditions : " + whereClause + "  hints : " + hints);
        }

        return makeQueryString(jobConf, tableName, Lists.newArrayList(readColumnList), whereClause, QUERY_TEMPLATE,
                hints, columnTypeMap);
    }

    public String buildQuery(JobConf jobConf, String tableName, List<String> readColumnList,
            List<IndexSearchCondition> searchConditions) throws IOException {
        String hints = getHint(jobConf, tableName);

        if (LOG.isDebugEnabled()) {
            LOG.debug("Building query with columns : " + readColumnList + "  table name : " + tableName
                    + " search conditions : " + searchConditions + "  hints : " + hints);
        }

        return makeQueryString(jobConf, tableName, Lists.newArrayList(readColumnList), searchConditions,
                QUERY_TEMPLATE, hints);
    }

    private String getHint(JobConf jobConf, String tableName) {
        StringBuilder hints = new StringBuilder("/*+ ");
        if (!jobConf.getBoolean(PhoenixStorageHandlerConstants.HBASE_SCAN_CACHEBLOCKS, Boolean.FALSE)) {
            hints.append("NO_CACHE ");
        }

        String queryHint = jobConf.get(tableName + PhoenixStorageHandlerConstants.PHOENIX_TABLE_QUERY_HINT);
        if (queryHint != null) {
            hints.append(queryHint);
        }
        hints.append(" */");

        return hints.toString();
    }

    private List<String> buildWhereClause(JobConf jobConf, StringBuilder sql, String whereClause,
            Map<String, TypeInfo> columnTypeMap) throws IOException {
        if (whereClause == null || whereClause.isEmpty()) {
            return Collections.emptyList();
        }

        List<String> conditionColumnList = Lists.newArrayList();
        sql.append(" where ");

        whereClause = StringUtils.replaceEach(whereClause, new String[] { "UDFToString" },
                new String[] { "to_char" });

        for (String columnName : columnTypeMap.keySet()) {
            if (whereClause.contains(columnName)) {
                String column = findReplacement(jobConf, columnName);
                whereClause = StringUtils.replaceEach(whereClause, new String[] { columnName },
                        new String[] { "\"" + column + "\"" });
                conditionColumnList.add(column);

                if (PhoenixStorageHandlerConstants.DATE_TYPE.equals(columnTypeMap.get(columnName).getTypeName())) {
                    whereClause = applyDateFunctionUsingRegex(whereClause, columnName);
                } else if (PhoenixStorageHandlerConstants.TIMESTAMP_TYPE
                        .equals(columnTypeMap.get(columnName).getTypeName())) {
                    whereClause = applyTimestampFunctionUsingRegex(whereClause, columnName);
                }
            }
        }

        sql.append(whereClause);

        return conditionColumnList;
    }

    private String applyDateFunctionUsingRegex(String whereClause, String columnName) {
        whereClause = applyFunctionForCommonOperator(whereClause, columnName, true);
        whereClause = applyFunctionForBetweenOperator(whereClause, columnName, true);
        whereClause = applyFunctionForInOperator(whereClause, columnName, true);

        return whereClause;
    }

    private String applyTimestampFunctionUsingRegex(String whereClause, String columnName) {
        whereClause = applyFunctionForCommonOperator(whereClause, columnName, false);
        whereClause = applyFunctionForBetweenOperator(whereClause, columnName, false);
        whereClause = applyFunctionForInOperator(whereClause, columnName, false);

        return whereClause;
    }

    private String applyFunctionForCommonOperator(String whereClause, String columnName, boolean isDate) {
        String targetPattern = isDate ? PhoenixStorageHandlerConstants.DATE_PATTERN
                : PhoenixStorageHandlerConstants.TIMESTAMP_PATTERN;
        String pattern = StringUtils
                .replaceEach(PhoenixStorageHandlerConstants.COMMON_OPERATOR_PATTERN,
                        new String[] { PhoenixStorageHandlerConstants.COLUMNE_MARKER,
                                PhoenixStorageHandlerConstants.PATERN_MARKER },
                        new String[] { columnName, targetPattern });

        Matcher matcher = Pattern.compile(pattern).matcher(whereClause);

        while (matcher.find()) {
            String token = matcher.group(1);
            String datePart = matcher.group(3);

            String convertString = token
                    .replace(datePart,
                            applyFunction(
                                    isDate ? PhoenixStorageHandlerConstants.DATE_FUNCTION_TEMPLETE
                                            : PhoenixStorageHandlerConstants.TIMESTAMP_FUNCTION_TEMPLATE,
                                    datePart));
            whereClause = whereClause.replaceAll(
                    StringUtils.replaceEach(token, new String[] { "(", ")" }, new String[] { "\\(", "\\)" }),
                    convertString);
        }

        return whereClause;
    }

    private String applyFunctionForBetweenOperator(String whereClause, String columnName, boolean isDate) {
        String targetPattern = isDate ? PhoenixStorageHandlerConstants.DATE_PATTERN
                : PhoenixStorageHandlerConstants.TIMESTAMP_PATTERN;
        String pattern = StringUtils
                .replaceEach(PhoenixStorageHandlerConstants.BETWEEN_OPERATOR_PATTERN,
                        new String[] { PhoenixStorageHandlerConstants.COLUMNE_MARKER,
                                PhoenixStorageHandlerConstants.PATERN_MARKER },
                        new String[] { columnName, targetPattern });

        Matcher matcher = Pattern.compile(pattern).matcher(whereClause);

        while (matcher.find()) {
            String token = matcher.group(1);
            boolean isNot = matcher.group(2) == null ? false : true;
            String fromDate = matcher.group(3);
            String toDate = matcher.group(4);

            String convertString = StringUtils
                    .replaceEach(token, new String[] { fromDate, toDate },
                            new String[] {
                                    applyFunction(
                                            isDate ? PhoenixStorageHandlerConstants.DATE_FUNCTION_TEMPLETE
                                                    : PhoenixStorageHandlerConstants.TIMESTAMP_FUNCTION_TEMPLATE,
                                            fromDate),
                                    applyFunction(
                                            isDate ? PhoenixStorageHandlerConstants.DATE_FUNCTION_TEMPLETE
                                                    : PhoenixStorageHandlerConstants.TIMESTAMP_FUNCTION_TEMPLATE,
                                            toDate) });

            whereClause = whereClause.replaceAll(pattern, convertString);
        }

        return whereClause;
    }

    private String applyFunctionForInOperator(String whereClause, String columnName, boolean isDate) {
        String targetPattern = isDate ? PhoenixStorageHandlerConstants.DATE_PATTERN
                : PhoenixStorageHandlerConstants.TIMESTAMP_PATTERN;
        String pattern = StringUtils
                .replaceEach(PhoenixStorageHandlerConstants.IN_OPERATOR_PATTERN,
                        new String[] { PhoenixStorageHandlerConstants.COLUMNE_MARKER,
                                PhoenixStorageHandlerConstants.PATERN_MARKER },
                        new String[] { columnName, targetPattern });
        String itemPattern = "(" + targetPattern + ")";

        Matcher matcher = Pattern.compile(pattern).matcher(whereClause);

        while (matcher.find()) {
            String token = matcher.group(1);
            Matcher itemMatcher = Pattern.compile(itemPattern).matcher(token);
            while (itemMatcher.find()) {
                String item = itemMatcher.group(1);

                token = token
                        .replace(item,
                                applyFunction(
                                        isDate ? PhoenixStorageHandlerConstants.DATE_FUNCTION_TEMPLETE
                                                : PhoenixStorageHandlerConstants.TIMESTAMP_FUNCTION_TEMPLATE,
                                        item));
            }

            whereClause = whereClause.replaceAll(pattern, token);
        }

        return whereClause;
    }

    /**
     * replace value to specific part of pattern.
     * if pattern is to_date($value$) and value is '2016-01-15'. then return to_date('2016-01-15').
     * if pattern is cast($value$ as date) and value is '2016-01-15'. then return cast
     * ('2016-01-15' as date).
     */
    private String applyFunction(String pattern, String value) {
        if (!value.startsWith(PhoenixStorageHandlerConstants.QUOTATION_MARK)) {
            value = PhoenixStorageHandlerConstants.QUOTATION_MARK + value
                    + PhoenixStorageHandlerConstants.QUOTATION_MARK;
        }

        return pattern.replace(PhoenixStorageHandlerConstants.FUNCTION_VALUE_MARKER, value);
    }

    private String getCompareValueForDateAndTimestampFunction(String compareValue) {
        if (compareValue.startsWith(PhoenixStorageHandlerConstants.QUOTATION_MARK)) {
            return compareValue;
        } else {
            return PhoenixStorageHandlerConstants.QUOTATION_MARK + compareValue
                    + PhoenixStorageHandlerConstants.QUOTATION_MARK;
        }
    }

    private String applyDateFunction(String whereClause, String columnName) {
        StringBuilder whereCondition = new StringBuilder();
        for (Iterator<String> iterator = Splitter.on(CharMatcher.WHITESPACE).omitEmptyStrings().split(whereClause)
                .iterator(); iterator.hasNext(); whereCondition.append(PhoenixStorageHandlerConstants.SPACE)) {
            String token = iterator.next();
            if (isMyCondition(columnName, token)) {
                whereCondition.append(token);

                String comparator = iterator.next();
                whereCondition.append(PhoenixStorageHandlerConstants.SPACE);
                whereCondition.append(comparator).append(PhoenixStorageHandlerConstants.SPACE);
                if (PhoenixStorageHandlerConstants.BETWEEN_COMPARATOR.equalsIgnoreCase(comparator)) {
                    whereCondition.append("to_date(")
                            .append(getCompareValueForDateAndTimestampFunction(iterator.next())).append(") ")
                            .append(iterator.next()).append(PhoenixStorageHandlerConstants.SPACE)
                            .append("to_date(");

                    String toCompareValue = iterator.next();
                    if (toCompareValue.endsWith(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)) {
                        int rightBracketIndex = toCompareValue
                                .indexOf(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET);
                        whereCondition
                                .append(getCompareValueForDateAndTimestampFunction(
                                        toCompareValue.substring(0, rightBracketIndex)))
                                .append(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)
                                .append(toCompareValue.substring(rightBracketIndex));
                    } else {
                        whereCondition.append(getCompareValueForDateAndTimestampFunction(toCompareValue))
                                .append(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET);
                    }
                } else if (PhoenixStorageHandlerConstants.IN_COMPARATOR.equalsIgnoreCase(comparator)) {
                    while (iterator.hasNext()) {
                        String aToken = iterator.next();
                        if (aToken.equals(PhoenixStorageHandlerConstants.LEFT_ROUND_BRACKET)
                                || aToken.equals(PhoenixStorageHandlerConstants.COMMA)) {
                            whereCondition.append(aToken);
                        } else if (aToken.equals(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)) {
                            whereCondition.append(aToken);
                            break;
                        } else if (aToken.endsWith(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)) {
                            int bracketIndex = aToken.indexOf(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET);
                            whereCondition.append("to_date(")
                                    .append(getCompareValueForDateAndTimestampFunction(
                                            aToken.substring(0, bracketIndex)))
                                    .append(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)
                                    .append(aToken.substring(bracketIndex));
                            break;
                        } else if (aToken.endsWith(PhoenixStorageHandlerConstants.COMMA)) {
                            if (aToken.startsWith(PhoenixStorageHandlerConstants.LEFT_ROUND_BRACKET)) {
                                int bracketIndex = aToken
                                        .lastIndexOf(PhoenixStorageHandlerConstants.LEFT_ROUND_BRACKET);
                                whereCondition.append(aToken.substring(0, bracketIndex + 1)).append("to_date(")
                                        .append(getCompareValueForDateAndTimestampFunction(
                                                aToken.substring(bracketIndex + 1, aToken.length() - 1)))
                                        .append("),");
                            } else {
                                whereCondition.append("to_date(").append(getCompareValueForDateAndTimestampFunction(
                                        aToken.substring(0, aToken.length() - 1))).append("),");
                            }
                        }

                        whereCondition.append(PhoenixStorageHandlerConstants.SPACE);
                    }
                } else if (PhoenixStorageHandlerConstants.COMMON_COMPARATOR.contains(comparator)) {
                    String compareValue = getCompareValueForDateAndTimestampFunction(iterator.next());
                    whereCondition.append("to_date(");
                    if (compareValue.endsWith(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)) {
                        int rightBracketIndex = compareValue
                                .indexOf(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET);
                        whereCondition
                                .append(getCompareValueForDateAndTimestampFunction(
                                        compareValue.substring(0, rightBracketIndex)))
                                .append(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)
                                .append(compareValue.substring(rightBracketIndex));
                    } else {
                        whereCondition.append(getCompareValueForDateAndTimestampFunction(compareValue))
                                .append(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET);
                    }
                }
            } else {
                whereCondition.append(token);
            }
        }

        return whereCondition.toString();
    }

    // Assume timestamp value is yyyy-MM-dd HH:mm:ss.SSS
    private String applyTimestampFunction(String whereClause, String columnName) {
        StringBuilder whereCondition = new StringBuilder();
        for (Iterator<String> iterator = Splitter.on(CharMatcher.WHITESPACE).omitEmptyStrings().split(whereClause)
                .iterator(); iterator.hasNext(); whereCondition.append(PhoenixStorageHandlerConstants.SPACE)) {
            String token = iterator.next();
            if (isMyCondition(columnName, token)) {
                whereCondition.append(token);

                String comparator = iterator.next();
                whereCondition.append(PhoenixStorageHandlerConstants.SPACE);
                whereCondition.append(comparator).append(PhoenixStorageHandlerConstants.SPACE);
                if (PhoenixStorageHandlerConstants.BETWEEN_COMPARATOR.equalsIgnoreCase(comparator)) {
                    String fromCompareValue = iterator.next() + PhoenixStorageHandlerConstants.SPACE
                            + iterator.next();
                    whereCondition.append("to_timestamp(")
                            .append(getCompareValueForDateAndTimestampFunction(fromCompareValue))
                            .append(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET);
                    whereCondition.append(PhoenixStorageHandlerConstants.SPACE).append(iterator.next())
                            .append(PhoenixStorageHandlerConstants.SPACE);
                    whereCondition.append("to_timestamp(");

                    String toCompareValue = iterator.next() + PhoenixStorageHandlerConstants.SPACE
                            + iterator.next();
                    if (toCompareValue.endsWith(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)) {
                        int rightBracketIndex = toCompareValue
                                .indexOf(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET);
                        whereCondition
                                .append(getCompareValueForDateAndTimestampFunction(
                                        toCompareValue.substring(0, rightBracketIndex)))
                                .append(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)
                                .append(toCompareValue.substring(rightBracketIndex));
                    } else {
                        whereCondition.append(getCompareValueForDateAndTimestampFunction(toCompareValue))
                                .append(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET);
                    }
                } else if (PhoenixStorageHandlerConstants.IN_COMPARATOR.equalsIgnoreCase(comparator)) {
                    while (iterator.hasNext()) {
                        String aToken = iterator.next();
                        if (aToken.equals(PhoenixStorageHandlerConstants.LEFT_ROUND_BRACKET)
                                || aToken.equals(PhoenixStorageHandlerConstants.COMMA)) {
                            whereCondition.append(aToken);
                        } else if (aToken.equals(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)) {
                            whereCondition.append(aToken);
                            break;
                        } else {
                            String compareValue = aToken + PhoenixStorageHandlerConstants.SPACE + iterator.next();

                            if (compareValue.startsWith(PhoenixStorageHandlerConstants.LEFT_ROUND_BRACKET)) {
                                int leftBracketIndex = compareValue
                                        .lastIndexOf(PhoenixStorageHandlerConstants.LEFT_ROUND_BRACKET);
                                whereCondition.append(compareValue.substring(0, leftBracketIndex + 1))
                                        .append("to_timestamp(");

                                if (compareValue.endsWith(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)) {
                                    int rightBracketIndex = compareValue
                                            .indexOf(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET);
                                    whereCondition
                                            .append(getCompareValueForDateAndTimestampFunction(compareValue
                                                    .substring(leftBracketIndex + 1, rightBracketIndex)))
                                            .append(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)
                                            .append(compareValue.substring(rightBracketIndex));
                                } else if (compareValue.endsWith(PhoenixStorageHandlerConstants.COMMA)) {
                                    whereCondition
                                            .append(getCompareValueForDateAndTimestampFunction(compareValue
                                                    .substring(leftBracketIndex + 1, compareValue.length() - 1)))
                                            .append(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)
                                            .append(PhoenixStorageHandlerConstants.COMMA);
                                } else {
                                    whereCondition
                                            .append(getCompareValueForDateAndTimestampFunction(
                                                    compareValue.substring(leftBracketIndex + 1)))
                                            .append(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET);
                                }
                            } else if (compareValue.endsWith(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)) {
                                int rightBracketIndex = compareValue
                                        .indexOf(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET);
                                whereCondition.append("to_timestamp(")
                                        .append(getCompareValueForDateAndTimestampFunction(
                                                compareValue.substring(0, rightBracketIndex)))
                                        .append(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)
                                        .append(compareValue.substring(rightBracketIndex));
                                break;
                            } else if (compareValue.endsWith(PhoenixStorageHandlerConstants.COMMA)) {
                                whereCondition.append("to_timestamp(")
                                        .append(getCompareValueForDateAndTimestampFunction(
                                                compareValue.substring(0, compareValue.length() - 1)))
                                        .append("),");
                            }
                        }

                        whereCondition.append(PhoenixStorageHandlerConstants.SPACE);
                    }
                } else if (PhoenixStorageHandlerConstants.COMMON_COMPARATOR.contains(comparator)) {
                    String timestampValue = iterator.next() + PhoenixStorageHandlerConstants.SPACE
                            + iterator.next();
                    whereCondition.append("to_timestamp(");
                    if (timestampValue.endsWith(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)) {
                        int rightBracketIndex = timestampValue
                                .indexOf(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET);
                        whereCondition
                                .append(getCompareValueForDateAndTimestampFunction(
                                        timestampValue.substring(0, rightBracketIndex)))
                                .append(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET)
                                .append(timestampValue.substring(rightBracketIndex));
                    } else {
                        whereCondition.append(getCompareValueForDateAndTimestampFunction(timestampValue))
                                .append(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET);
                    }
                }
            } else {
                whereCondition.append(token);
            }
        }

        return whereCondition.toString();
    }

    private boolean isMyCondition(String columnName, String token) {
        boolean itsMine = false;

        if (columnName.equals(token)) {
            itsMine = true;
        } else if (token.startsWith(PhoenixStorageHandlerConstants.LEFT_ROUND_BRACKET)
                && token.substring(token.lastIndexOf(PhoenixStorageHandlerConstants.LEFT_ROUND_BRACKET) + 1)
                        .equals(columnName)) {
            itsMine = true;
        } else if (token.startsWith(PhoenixStorageHandlerConstants.LEFT_ROUND_BRACKET)
                && token.endsWith(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET) && token
                        .substring(token.lastIndexOf(PhoenixStorageHandlerConstants.LEFT_ROUND_BRACKET) + 1,
                                token.indexOf(PhoenixStorageHandlerConstants.RIGHT_ROUND_BRACKET))
                        .equals(columnName)) {
            itsMine = true;
        }

        return itsMine;
    }

    protected List<String> buildWhereClause(JobConf jobConf, StringBuilder sql,
            List<IndexSearchCondition> conditions) throws IOException {
        if (conditions == null || conditions.size() == 0) {
            return Collections.emptyList();
        }

        List<String> columns = Lists.newArrayList();
        sql.append(" where ");

        Iterator<IndexSearchCondition> iter = conditions.iterator();
        appendExpression(jobConf, sql, iter.next(), columns);
        while (iter.hasNext()) {
            sql.append(" and ");
            appendExpression(jobConf, sql, iter.next(), columns);
        }

        return columns;
    }

    private void appendExpression(JobConf jobConf, StringBuilder sql, IndexSearchCondition condition,
            List<String> columns) {
        Expression expr = findExpression(condition);
        if (expr != null) {
            sql.append(expr.buildExpressionStringFrom(jobConf, condition));
            String column = condition.getColumnDesc().getColumn();
            String rColumn = findReplacement(jobConf, column);
            if (rColumn != null) {
                column = rColumn;
            }

            columns.add(column);
        }
    }

    private Expression findExpression(final IndexSearchCondition condition) {
        return Iterables.tryFind(Arrays.asList(Expression.values()), new Predicate<Expression>() {
            @Override
            public boolean apply(@Nullable Expression expr) {
                return expr.isFor(condition);
            }
        }).orNull();
    }

    private static final Joiner JOINER_COMMA = Joiner.on(", ");
    private static final Joiner JOINER_AND = Joiner.on(" and ");
    private static final Joiner JOINER_SPACE = Joiner.on(" ");

    private enum Expression {
        EQUAL("UDFOPEqual", "="), GREATER_THAN_OR_EQUAL_TO("UDFOPEqualOrGreaterThan", ">="), GREATER_THAN(
                "UDFOPGreaterThan", ">"), LESS_THAN_OR_EQUAL_TO("UDFOPEqualOrLessThan",
                        "<="), LESS_THAN("UDFOPLessThan", "<"), NOT_EQUAL("UDFOPNotEqual",
                                "!="), BETWEEN("GenericUDFBetween", "between", JOINER_AND, true) {
                                    public boolean checkCondition(IndexSearchCondition condition) {
                                        return condition.getConstantDescs() != null;
                                    }
                                },
        IN("GenericUDFIn", "in", JOINER_COMMA, true) {
            public boolean checkCondition(IndexSearchCondition condition) {
                return condition.getConstantDescs() != null;
            }

            public String createConstants(final String typeName, ExprNodeConstantDesc[] desc) {
                return "(" + super.createConstants(typeName, desc) + ")";
            }
        },
        IS_NULL("GenericUDFOPNull", "is null") {
            public boolean checkCondition(IndexSearchCondition condition) {
                return true;
            }
        },
        IS_NOT_NULL("GenericUDFOPNotNull", "is not null") {
            public boolean checkCondition(IndexSearchCondition condition) {
                return true;
            }
        };

        private final String hiveCompOp;
        private final String sqlCompOp;
        private final Joiner joiner;
        private final boolean supportNotOperator;

        Expression(String hiveCompOp, String sqlCompOp) {
            this(hiveCompOp, sqlCompOp, null);
        }

        Expression(String hiveCompOp, String sqlCompOp, Joiner joiner) {
            this(hiveCompOp, sqlCompOp, joiner, false);
        }

        Expression(String hiveCompOp, String sqlCompOp, Joiner joiner, boolean supportNotOp) {
            this.hiveCompOp = hiveCompOp;
            this.sqlCompOp = sqlCompOp;
            this.joiner = joiner;
            this.supportNotOperator = supportNotOp;
        }

        public boolean checkCondition(IndexSearchCondition condition) {
            return condition.getConstantDesc().getValue() != null;
        }

        public boolean isFor(IndexSearchCondition condition) {
            return condition.getComparisonOp().endsWith(hiveCompOp) && checkCondition(condition);
        }

        public String buildExpressionStringFrom(JobConf jobConf, IndexSearchCondition condition) {
            final String type = condition.getColumnDesc().getTypeString();
            String column = condition.getColumnDesc().getColumn();
            String rColumn = findReplacement(jobConf, column);
            if (rColumn != null) {
                column = rColumn;
            }
            return JOINER_SPACE.join("\"" + column + "\"", getSqlCompOpString(condition),
                    joiner != null ? createConstants(type, condition.getConstantDescs())
                            : createConstant(type, condition.getConstantDesc()));
        }

        public String getSqlCompOpString(IndexSearchCondition condition) {
            return supportNotOperator ? (condition.isNot() ? "not " : "") + sqlCompOp : sqlCompOp;
        }

        public String createConstant(String typeName, ExprNodeConstantDesc constantDesc) {
            if (constantDesc == null) {
                return StringUtil.EMPTY_STRING;
            }

            return createConstantString(typeName, String.valueOf(constantDesc.getValue()));
        }

        public String createConstants(final String typeName, ExprNodeConstantDesc[] constantDesc) {
            if (constantDesc == null) {
                return StringUtil.EMPTY_STRING;
            }

            return joiner.join(
                    Iterables.transform(Arrays.asList(constantDesc), new Function<ExprNodeConstantDesc, String>() {
                        @Nullable
                        @Override
                        public String apply(@Nullable ExprNodeConstantDesc desc) {
                            return createConstantString(typeName, String.valueOf(desc.getValue()));
                        }
                    }));
        }

        private static class ConstantStringWrapper {
            private List<String> types;
            private String prefix;
            private String postfix;

            ConstantStringWrapper(String type, String prefix, String postfix) {
                this(Lists.newArrayList(type), prefix, postfix);
            }

            ConstantStringWrapper(List<String> types, String prefix, String postfix) {
                this.types = types;
                this.prefix = prefix;
                this.postfix = postfix;
            }

            public String apply(final String typeName, String value) {
                return Iterables.any(types, new Predicate<String>() {

                    @Override
                    public boolean apply(@Nullable String type) {
                        return typeName.startsWith(type);
                    }
                }) ? prefix + value + postfix : value;
            }
        }

        private static final String SINGLE_QUOTATION = "'";
        private static List<ConstantStringWrapper> WRAPPERS = Lists.newArrayList(
                new ConstantStringWrapper(Lists.newArrayList(serdeConstants.STRING_TYPE_NAME,
                        serdeConstants.CHAR_TYPE_NAME, serdeConstants.VARCHAR_TYPE_NAME,
                        serdeConstants.DATE_TYPE_NAME, serdeConstants.TIMESTAMP_TYPE_NAME), SINGLE_QUOTATION,
                        SINGLE_QUOTATION),
                new ConstantStringWrapper(serdeConstants.DATE_TYPE_NAME, "to_date(", ")"),
                new ConstantStringWrapper(serdeConstants.TIMESTAMP_TYPE_NAME, "to_timestamp(", ")"));

        private String createConstantString(String typeName, String value) {
            for (ConstantStringWrapper wrapper : WRAPPERS) {
                value = wrapper.apply(typeName, value);
            }

            return value;
        }
    }
}