語法解析器續:case..when語法解析計算

  以前寫過一篇博客,是關於如何解析相似sql之類的解析器實現參考:http://www.javashuo.com/article/p-bqxebcca-nu.htmlhtml

  以前的解析器,更多的是是作語言的翻譯轉換工做,並不涉及具體的數據運算。並且拋棄了許多上下文關聯語法處理,因此相對仍是簡單的。java

  那麼,若是咱們想作一下數據運算呢?好比我給你一些值,而後給你一個表達式,你能夠給出其運算結果嗎?sql

 

1. 表達式運算難度如何?

  好比,已知表達式爲, field1 > 0 and field2 > 0, 而後已知道值 field1 = 1, field2 = 2; 那麼,此運算結果必當爲true。這很理所固然!apache

  但以上,僅爲人工處理,本身用大腦作了下運算,獲得結果。若是轉換爲代碼,又當如何?mvc

  我想,咱們至少要作這麼幾件事:app

    1. 解析出全部字段有field1, field2;
    2. 解析出比較運算符 >;
    3. 解析出右邊具體的比較值;
    4. 解析出鏈接運算符and;
    5. 作全部的比較運算;
    6. 關聯優先級獲得最終結果;ide

  怎麼樣?如今還以爲很簡單嗎?若是是,請收下個人膝蓋!單元測試

  可是,若是真要作這種泛化的場景,那就至關至關複雜了,要知道相似於HIVE之類的重量級產品,語法解析都是其中重要的組成部分。實際上,這可能涉及到至關多的語言規範須要作了。因此,必然超出咱們的簡化理解範圍。測試

  因此,我這裏僅挑一個簡單場景作解析:即如題所說,case..when..的解析。this

  因此,咱們能夠範圍縮減爲,給定表達式:case when field1 > 0 then 'f1' else 'fn' end; 的判斷解析。好比給定值 field1=1, 則應獲得結果 f1, 若是給定值 field1=0, 則應獲得結果 fn.

  在劃定範圍以後,好像更有了目標感了。可是問題真的簡單了嗎?實際上,仍是有至關多的分支須要處理的,由於case..when..中能夠嵌套其餘語法。因此,咱們只能盡力而爲了。

 

2. case..when..表達式運算的實現

  命題確立以後,咱們能夠開始着手如何實現了。如上描述,咱們有兩個已知條件:表達式和基礎值。

  基於上一篇文章的解析,咱們基本能夠快速獲得全部組成case when 的元素token信息了。這就爲咱們省去了很多事。這裏,我着重給一個如何獲取整個case..when..詞句的實現,使其可造成一個獨立的詞組。

    // 將case..when.. 歸結爲sql類關鍵詞的實現中
    public SqlKeywordAstHandler(TokenDescriptor masterToken,
                                Iterator<TokenDescriptor> candidates,
                                TokenTypeEnum tokenType) {
        super(masterToken, candidates, tokenType);
        String word = masterToken.getRawWord().toLowerCase();
        if("case".equals(word)) {
            completeCaseWhenTokens(candidates);
        }
    }

    /**
     * 實例case...when... 詞彙列表
     *
     * @param candidates 待用詞彙
     */
    private void completeCaseWhenTokens(Iterator<TokenDescriptor> candidates) {
        boolean syntaxClosed = false;
        while (candidates.hasNext()) {
            TokenDescriptor token = candidates.next();
            addExtendToken(token);
            if("end".equalsIgnoreCase(token.getRawWord())) {
                syntaxClosed = true;
                break;
            }
        }
        if(!syntaxClosed) {
            throw new SyntaxException("語法錯誤:case..when..未半閉合");
        }
    }

  以上,就是獲取case..when..詞組的方法了,主要就是從case開始,到end結束,中間的全部詞根,都被劃做其範圍。固然,還有一個重要的點,是將數據字段找出來,放到可取到的地方。

  有了一個個獨立的元素,咱們就能夠進行語義分析了。該分析能夠放在該解析器中,但也許並不會太通用,因此,此處我將其抽象爲一個單獨的值運算類。在須要的地方,再實例化該運算類,便可。核心問題如上一節中描述,具體實現代碼以下:

import com.my.mvc.app.common.exception.SyntaxException;
import com.my.mvc.app.common.helper.parser.SyntaxStatement;
import com.my.mvc.app.common.helper.parser.TokenDescriptor;
import com.my.mvc.app.common.helper.parser.TokenTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 功能描述: case..when.. 真實數據運算幫助類
 *
 */
@Slf4j
public class CaseWhenElDataCalcHelper {

    /**
     * case when 完整語法
     */
    private SyntaxStatement caseWhenStmt;
    
    public CaseWhenElDataCalcHelper(SyntaxStatement caseWhenStmt) {
        this.caseWhenStmt = caseWhenStmt;
    }

    /**
     * 計算case..when的結果
     *
     * @param suppliers 原始全部值
     * @return 最終計算出的值
     */
    public String calcCaseWhenData(Map<String, String> suppliers) {
        List<TokenDescriptor> allTokens = caseWhenStmt.getAllTokens();
        TokenDescriptor masterToken = allTokens.get(0);
        if(!"case".equalsIgnoreCase(masterToken.getRawWord())) {
            throw new SyntaxException("不是case..when..表達式");
        }
        int tokenLen = allTokens.size();
        if(tokenLen < 3) {
            throw new SyntaxException("case..when..表達式語法錯誤");
        }
        TokenDescriptor closureToken = allTokens.get(tokenLen - 1);
        if(!"end".equalsIgnoreCase(closureToken.getRawWord())) {
            throw new SyntaxException("case..when..表達式未閉合");
        }
        // 暫只支持 case when xxx then xxx... end 語法
        // 不支持 case field_name when 1 then '1'... end, 即單字段斷定不支持
        List<TokenDescriptor> whenExpressionCandidates;
        for (int i = 1; i < tokenLen - 1; i++) {
            whenExpressionCandidates = new ArrayList<>();
            TokenDescriptor currentToken = allTokens.get(i);
            if("when".equalsIgnoreCase(currentToken.getRawWord())) {
                // 需走各分支邏輯
                while (i + 1 < tokenLen) {
                    TokenDescriptor nextToken = allTokens.get(i + 1);
                    if("then".equalsIgnoreCase(nextToken.getRawWord())) {
                        break;
                    }
                    whenExpressionCandidates.add(nextToken);
                    ++i;
                }
                if(judgeWhenExpression(whenExpressionCandidates, suppliers)) {
                    List<TokenDescriptor> resultCandidates
                            = scrapeCaseWhenResultCandidates(allTokens, i + 1);
                    return calcExpressionData(resultCandidates, suppliers);
                }
                // 直接進入下一輪迭代,then後面爲空迭代
            }
            if("else".equalsIgnoreCase(currentToken.getRawWord())) {
                List<TokenDescriptor> resultCandidates
                        = scrapeCaseWhenResultCandidates(allTokens, i);
                return calcExpressionData(resultCandidates, suppliers);
            }
        }
        return null;
    }

    /**
     * 撈出全部的結果運算token列表
     *
     * @param allTokens 全局token表
     * @param start 偏移量
     * @return 獲取到的全部結果運算token
     */
    private List<TokenDescriptor> scrapeCaseWhenResultCandidates(List<TokenDescriptor> allTokens,
                                                             int start) {
        List<TokenDescriptor> resultCandidates = new ArrayList<>();
        while (start + 1 < allTokens.size()) {
            TokenDescriptor nextToken = allTokens.get(start + 1);
            String word = nextToken.getRawWord();
            if("when".equalsIgnoreCase(word)
                    || "else".equalsIgnoreCase(word)
                    || "end".equalsIgnoreCase(word)) {
                break;
            }
            resultCandidates.add(nextToken);
            ++start;
        }
        return resultCandidates;
    }

    /**
     * 判斷when條件是否成立
     *
     * @param operatorCandidates 可供運算的表達式token列表
     * @param suppliers 原始字段取值來源
     * @return true:符合該斷定,false:斷定失敗
     */
    private boolean judgeWhenExpression(List<TokenDescriptor> operatorCandidates,
                                        Map<String, String> suppliers) {
        List<AndOrOperatorSupervisor> supervisors
                = partitionByPriority(operatorCandidates);
        boolean prevJudgeSuccess = false;
        for (AndOrOperatorSupervisor calc1 : supervisors) {
            Map<String, List<TokenDescriptor>> unitGroup = calc1.getUnitGroupTokens();
            String leftValue = calcExpressionData(unitGroup.get("LEFT"), suppliers);
            String op = unitGroup.get("OP").get(0).getRawWord();
            TokenTypeEnum resultType = getPriorDataTypeByTokenList(unitGroup.get("RIGHT"));
            boolean myJudgeSuccess;
            if("in".equals(op)) {
                myJudgeSuccess = checkExistsIn(leftValue, unitGroup.get("RIGHT"), resultType);
            }
            else if("notin".equals(op)) {
                myJudgeSuccess = !checkExistsIn(leftValue, unitGroup.get("RIGHT"), resultType);
            }
            else {
                String rightValue = calcExpressionData(unitGroup.get("RIGHT"), suppliers);
                myJudgeSuccess = checkCompareTrue(leftValue, op,
                                    rightValue, resultType);
            }
            TokenDescriptor prevType = calc1.getPrevType();
            TokenDescriptor nextType = calc1.getNextType();
            // 單條件斷定
            if(prevType == null && nextType == null) {
                return myJudgeSuccess;
            }
            if(nextType == null) {
                return myJudgeSuccess;
            }
            prevJudgeSuccess = myJudgeSuccess;
            if("and".equalsIgnoreCase(nextType.getRawWord())) {
                if(!myJudgeSuccess) {
                    return false;
                }
                continue;
            }
            if("or".equalsIgnoreCase(nextType.getRawWord())) {
                if(myJudgeSuccess) {
                    return true;
                }
                continue;
            }
            log.warn("解析到未知的next鏈接斷定符:{}", nextType);
            throw new SyntaxException("語法解析錯誤");
        }
        log.warn("未斷定出結果,使用默認返回,請檢查");
        return false;
    }

    /**
     * 根據值信息推斷運算數據類型
     *
     * @param tokenList 結果列表(待運算)
     * @return 計算的數據類型,數字或字符串
     */
    private TokenTypeEnum getPriorDataTypeByTokenList(List<TokenDescriptor> tokenList) {
        for (TokenDescriptor token : tokenList) {
            if(token.getTokenType() == TokenTypeEnum.WORD_STRING) {
                return TokenTypeEnum.WORD_STRING;
            }
        }
        return TokenTypeEnum.WORD_NUMBER;
    }

    /**
     * 運算返回具體的 斷定值
     *
     * @param resultCandidates 結果表達式token列表
     * @param suppliers 原始字段取值來源
     * @return true:符合該斷定,false:斷定失敗
     */
    private String calcExpressionData(List<TokenDescriptor> resultCandidates,
                                      Map<String, String> suppliers) {
        // 暫時假設結果中再也不提供運算處理
        TokenDescriptor first = resultCandidates.get(0);
        if(first.getTokenType() == TokenTypeEnum.WORD_NORMAL) {
            if("null".equalsIgnoreCase(first.getRawWord())) {
                return null;
            }
            return suppliers.get(first.getRawWord());
        }
        return unwrapStringToken(first.getRawWord());
    }

    /**
     * 判斷給定值是否在列表中
     *
     * @param aValue 要斷定的值
     * @param itemList 範圍表
     * @return true:成立, false:不在其中
     */
    private boolean checkExistsIn(String aValue,
                                  List<TokenDescriptor> itemList,
                                  TokenTypeEnum valueType) {
        if(aValue == null) {
            return false;
        }
        BigDecimal aValueNumber = null;
        for (TokenDescriptor tk1 : itemList) {
            if(valueType == TokenTypeEnum.WORD_NUMBER) {
                if(aValueNumber == null) {
                    aValueNumber = new BigDecimal(aValue);
                }
                if(aValueNumber.compareTo(
                        new BigDecimal(tk1.getRawWord())) == 0) {
                    return true;
                }
                continue;
            }
            if(aValue.equals(unwrapStringToken(tk1.getRawWord()))) {
                return true;
            }
        }
        return false;
    }

    /**
     * 將字符串兩邊的引號去除,保持字符串屬性
     *
     * @param wrappedStr 含引號的字符串,如 'abc',"abc"
     * @return abc 無引號包裹的字符串
     */
    private String unwrapStringToken(String wrappedStr) {
        if(wrappedStr == null || wrappedStr.length() == 0) {
            return null;
        }
        char[] values = wrappedStr.toCharArray();
        int i = 0;
        while (i < values.length - 1
                && (values[i] == '"' || values[i] == '\'')) {
            i++;
        }
        int j = values.length - 1;
        while (j > 0
                && (values[j] == '"' || values[j] == '\'')) {
            j--;
        }
        return new String(values, i, j - i + 1);
    }

    /**
     * 比較兩個值ab是否基於op成立
     *
     * @param aValue 左值
     * @param op 比較運算符
     * @param bValue 右值
     * @param valueType 值類型, 主要是區分數字與字符
     * @return 是否等式成立, true:成立, false:不成立
     */
    private boolean checkCompareTrue(String aValue,
                                     String op,
                                     String bValue,
                                     TokenTypeEnum valueType) {
        // 首先進行相生性斷定
        if("null".equals(bValue)) {
            bValue = null;
        }
        switch(op) {
            case "=":
                if(bValue == null) {
                    return aValue == null;
                }
                return bValue.equals(aValue);
            case "!=":
            case "<>":
                if(bValue == null) {
                    return aValue != null;
                }
                return !bValue.equals(aValue);
        }
        if(bValue == null) {
            log.warn("非null值不能用比較符號運算");
            throw new SyntaxException("語法錯誤");
        }
        // >=,<=,>,< 斷定
        int compareResult = compareTwoData(aValue, bValue, valueType);
        switch(op) {
            case ">":
                return compareResult > 0;
            case ">=":
                return compareResult >= 0;
            case "<=":
                return compareResult <= 0;
            case "<":
                return compareResult < 0;
        }
        throw new SyntaxException("未知的運算符");
    }
    // 比較兩個值大小ab
    private int compareTwoData(String aValue,
                               String bValue,
                               TokenTypeEnum tokenType) {
        bValue = unwrapStringToken(bValue);
        if(bValue == null) {
            // 按任意值大於null 規則處理
            return aValue == null ? 0 : 1;
        }
        if(tokenType == TokenTypeEnum.WORD_NUMBER) {
            return new BigDecimal(aValue).compareTo(
                    new BigDecimal(bValue));
        }
        return aValue.compareTo(unwrapStringToken(bValue));
    }

    // 將token從新分組,以即可以作原子運算
    private List<AndOrOperatorSupervisor> partitionByPriority(List<TokenDescriptor> tokens) {
        // 1. 取左等式token列表
        // 2. 取等式表達式
        // 3. 取右等式token列表
        // 4. 構建一個表達式,作最小分組
        // 5. 檢查是否有下一運算符,若有則一定爲and|or|(
        // 6. 保存上一鏈接斷定符,新開一個分組
        // 7. 重複步驟1-6,直到取完全部token

        // 前置運算符,決定是否要運算本節點,以及結果的合併方式
        // 好比 and, 則當前點必須參與運算,若是前節點結果爲false,則直接返回false
        // 不然先計算本節點
        TokenDescriptor preType = null;
        // 當前節點計算完成後,判斷下一運算是否有必要觸發
        // 爲and時則當前爲true時必須觸發,爲or時當前爲false觸發
        TokenDescriptor nextType = null;
        // key 爲 left, op, right, 各value爲細分tks
        Map<String, List<TokenDescriptor>> unitGroup = new HashMap<>();
        String currentReadPos = "LEFT";
        List<TokenDescriptor> smallGroupTokenList = new ArrayList<>();
        // 以上爲描述單個運算的字符,使用一個list就能夠描述無括號的表達式了
        List<AndOrOperatorSupervisor> bracketGroup = new ArrayList<>();
        AndOrOperatorSupervisor supervisor
                = new AndOrOperatorSupervisor(null, unitGroup);
        bracketGroup.add(supervisor);
        for (int i = 0; i < tokens.size(); i++) {
            TokenDescriptor token = tokens.get(i);
            String word = token.getRawWord().toLowerCase();
            TokenTypeEnum tokenType = token.getTokenType();
            // 忽略分隔符,假設只有一級運算,忽略空格帶來的複雜優先級問題
            if(tokenType == TokenTypeEnum.CLAUSE_SEPARATOR) {
                continue;
            }
            // 字段直接斷定
            if(tokenType == TokenTypeEnum.COMPARE_OPERATOR
                    && !",".equals(word)) {
                unitGroup.put("OP", Collections.singletonList(token));
                currentReadPos = "RIGHT";
                continue;
            }
            // is null, is not null 解析
            if("is".equals(word)) {
                while (i + 1 < tokens.size()) {
                    TokenDescriptor nextToken = tokens.get(i + 1);
                    if("null".equalsIgnoreCase(nextToken.getRawWord())) {
                        TokenDescriptor opToken = new TokenDescriptor("=",
                                TokenTypeEnum.COMPARE_OPERATOR);
                        unitGroup.put("OP", Collections.singletonList(opToken));
                        currentReadPos = "RIGHT";
                        List<TokenDescriptor> curTokenList = unitGroup.computeIfAbsent(
                                currentReadPos, r -> new ArrayList<>());
                        curTokenList.add(nextToken);
                        // 跳過1個token
                        i += 1;
                        break;
                    }
                    if("not".equalsIgnoreCase(nextToken.getRawWord())) {
                        if(i + 2 >= tokens.size()) {
                            throw new SyntaxException("語法錯誤3: is");
                        }
                        nextToken = tokens.get(i + 2);
                        if(!"null".equalsIgnoreCase(nextToken.getRawWord())) {
                            throw new SyntaxException("語法錯誤4: is");
                        }
                        TokenDescriptor opToken = new TokenDescriptor("!=",
                                TokenTypeEnum.COMPARE_OPERATOR);
                        unitGroup.put("OP", Collections.singletonList(opToken));
                        currentReadPos = "RIGHT";
                        List<TokenDescriptor> curTokenList = unitGroup.computeIfAbsent(
                                currentReadPos, r -> new ArrayList<>());
                        curTokenList.add(nextToken);
                        // 跳過2個token
                        i += 2;
                        break;
                    }
                }
                continue;
            }
            // in (x,x,xx) 語法解析
            if("in".equals(word)) {
                TokenDescriptor opToken = new TokenDescriptor("in",
                        TokenTypeEnum.COMPARE_OPERATOR);
                unitGroup.put("OP", Collections.singletonList(opToken));
                currentReadPos = "RIGHT";
                List<TokenDescriptor> curTokenList = unitGroup.computeIfAbsent(
                        currentReadPos, r -> new ArrayList<>());
                i = parseInItems(tokens, curTokenList, i);
                continue;
            }
            // not in (x,xxx,xx) 語法解析
            if("not".equals(word)) {
                if(i + 1 > tokens.size()) {
                    throw new SyntaxException("語法錯誤:not");
                }
                TokenDescriptor nextToken = tokens.get(i + 1);
                // 暫不支持 not exists 等語法
                if(!"in".equalsIgnoreCase(nextToken.getRawWord())) {
                    throw new SyntaxException("不支持的語法:not");
                }
                TokenDescriptor opToken = new TokenDescriptor("notin",
                        TokenTypeEnum.COMPARE_OPERATOR);
                unitGroup.put("OP", Collections.singletonList(opToken));
                currentReadPos = "RIGHT";
                List<TokenDescriptor> curTokenList = unitGroup.computeIfAbsent(
                        currentReadPos, r -> new ArrayList<>());
                i = parseInItems(tokens, curTokenList, i + 1);
                continue;
            }
            // 暫只解析一級,無括號狀況
            if("and".equals(word)
                    || "or".equals(word)) {
                supervisor.setNextType(token);
                // 滾動到下一運算分支
                unitGroup = new HashMap<>();
                supervisor = new AndOrOperatorSupervisor(token, unitGroup);
                bracketGroup.add(supervisor);
                currentReadPos = "LEFT";
                continue;
            }
            List<TokenDescriptor> curTokenList = unitGroup.computeIfAbsent(
                    currentReadPos, r -> new ArrayList<>());
            curTokenList.add(token);
        }

        return bracketGroup;
    }

    /**
     * 解析in中的全部元素到結果中
     *
     * @param tokens 全部token
     * @param curTokenList 當前結果表
     * @param start in 開始的地方
     * @return in 語法結束位置
     */
    private int parseInItems(List<TokenDescriptor> tokens,
                             List<TokenDescriptor> curTokenList,
                             int start) {
        while (start + 1 < tokens.size()) {
            TokenDescriptor nextToken = tokens.get(++start);
            String nextWord = nextToken.getRawWord();
            if("(".equals(nextWord)
                    || ",".equals(nextWord)) {
                // in 開始
                continue;
            }
            if(")".equals(nextWord)) {
                break;
            }
            curTokenList.add(nextToken);
        }
        return start;
    }

    /**
     * 最小運算單元描述符
     */
    private class AndOrOperatorSupervisor {
        // 前置運算符,決定是否要運算本節點,以及結果的合併方式
        // 好比 and, 則當前點必須參與運算,若是前節點結果爲false,則直接返回false
        // 不然先計算本節點
        TokenDescriptor prevType;
        // 當前節點計算完成後,判斷下一運算是否有必要觸發
        // 爲and時則當前爲true時必須觸發,爲or時當前爲false觸發
        TokenDescriptor nextType;
        // key 爲 left, op, right, 各value爲細分tks
        Map<String, List<TokenDescriptor>> unitGroupTokens;

        public AndOrOperatorSupervisor(TokenDescriptor prevType,
                                       Map<String, List<TokenDescriptor>> unitGroupTokens) {
            this.prevType = prevType;
            this.unitGroupTokens = unitGroupTokens;
        }

        public void setNextType(TokenDescriptor nextType) {
            this.nextType = nextType;
        }

        public TokenDescriptor getPrevType() {
            return prevType;
        }

        public TokenDescriptor getNextType() {
            return nextType;
        }

        public Map<String, List<TokenDescriptor>> getUnitGroupTokens() {
            return unitGroupTokens;
        }

        @Override
        public String toString() {
            return StringUtils.join(
                    unitGroupTokens.get("LEFT").stream()
                            .map(TokenDescriptor::getRawWord)
                            .collect(Collectors.toList()), ' ')
                    + unitGroupTokens.get("OP").get(0).getRawWord()
                    +
                    StringUtils.join(
                        unitGroupTokens.get("RIGHT").stream()
                            .map(TokenDescriptor::getRawWord)
                            .collect(Collectors.toList()), ' ') +
                    ", prev=" + prevType
                    + ", next=" + nextType
                    ;
        }
    }
}

  每使用時,傳入case..when..的語句構造出一個新的計算實例,而後調用 calcCaseWhenData(rawData), 帶入已知參數信息,便可運算出最終的case..when..值。

  爲使處理簡單起見,這裏並無深刻各類邏輯嵌套處理,直接忽略掉括號的處理了。另外,對於數值類的運算也暫時被忽略,如 field1 > 1+1 這種運算,並不會計算出2來。這些東西,須要的同窗,徹底能夠稍加完善,便可支持處理這些邏輯。

  因 case when 的語法仍是比較清晰的,因此咱們只是作了順序地讀取,斷定即得出結果。另外對於 case when 的單值斷定並不支持,因此實現並不複雜。但這徹底不影響咱們理解整個語法處理的思想。相信須要的同窗定能有所啓發。

 

3. 表達式計算單元測試

  以上僅實現代碼,須要附加上各類場景測試,纔算能夠work的東西。主要就是針對種 and/or, in, is null 等的處理。以下:

import com.my.mvc.app.common.helper.CaseWhenElDataCalcHelper;
import com.my.mvc.app.common.helper.SimpleSyntaxParser;
import com.my.mvc.app.common.helper.parser.ParsedClauseAst;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

@Slf4j
public class CaseWhenElDataCalcHelperTest {
    @Test
    public void testCaseWhenSimple1() {
        String condition;
        ParsedClauseAst parsedClause;
        CaseWhenElDataCalcHelper helper;
        Map<String, String> rawData;
        condition = "case \n" +
                "\twhen (kehu_phone is null or field1 != 'c') then m_phone \n" +
                "\telse kehu_phone\n" +
                "end";
        parsedClause = SimpleSyntaxParser.parse(condition);
        helper = new CaseWhenElDataCalcHelper(parsedClause.getAst().get(0));
        rawData = new HashMap<>();
        rawData.put("kehu_phone", "kehu_phone_v1");
        rawData.put("field1", "field1_v");
        rawData.put("m_phone", "m_phone_v");
        Assert.assertEquals("case..when..中解析字段信息不正確",
                    3, parsedClause.getIdMapping().size());
        Assert.assertEquals("case..when..解析結果錯誤",
                            rawData.get("m_phone"),
                            helper.calcCaseWhenData(rawData));

        condition = "case \n" +
                "\twhen (kehu_phone is null) then m_phone \n" +
                "\telse kehu_phone\n" +
                "end";
        parsedClause = SimpleSyntaxParser.parse(condition);
        helper = new CaseWhenElDataCalcHelper(parsedClause.getAst().get(0));
        rawData = new HashMap<>();
        rawData.put("kehu_phone", "kehu_phone_v1");
        rawData.put("field1", "field1_v");
        rawData.put("m_phone", "m_phone_v");
        Assert.assertEquals("case..when..中解析字段信息不正確",
                2, parsedClause.getIdMapping().size());
        Assert.assertEquals("case..when..解析結果錯誤",
                    rawData.get("kehu_phone"),
                    helper.calcCaseWhenData(rawData));

        rawData.remove("kehu_phone");
        Assert.assertEquals("case..when..解析結果錯誤",
                    rawData.get("m_phone"),
                    helper.calcCaseWhenData(rawData));

        condition = " case \n" +
                " \twhen is_sx_emp='Y' then 'Y1' \n" +
                " \twhen is_sx_new_custom!='Y' then 'Y2' \n" +
                " \twhen is_sx_fort_promot_custom='Y' then 'Y3' \n" +
                " \twhen promotion_role_chn in ('10','11') and first_tenthousand_dt is not null then 'Y4' \n" +
                " \telse 'N' \n" +
                " end";
        parsedClause = SimpleSyntaxParser.parse(condition);
        helper = new CaseWhenElDataCalcHelper(parsedClause.getAst().get(0));
        rawData = new HashMap<>();
        rawData.put("is_sx_emp", "N");
        rawData.put("is_sx_new_custom", "Y");
        rawData.put("is_sx_fortune_promot_custom", "N");
        rawData.put("promotion_role_chn", "10");
        rawData.put("first_tenthousand_dt", "10");
        Assert.assertEquals("case..when..中解析字段信息不正確",
                5, parsedClause.getIdMapping().size());
        Assert.assertEquals("case..when..in解析結果錯誤",
                "Y4",
                helper.calcCaseWhenData(rawData));

        rawData = new HashMap<>();
        rawData.put("is_sx_emp", "N");
        rawData.put("is_sx_new_custom", "Y");
        rawData.put("is_sx_fortune_promot_custom", "N");
        rawData.put("first_tenthousand_dt", "10");
        rawData.put("promotion_role_chn", "9");
        Assert.assertEquals("case..when..else解析結果錯誤",
                "N",
                helper.calcCaseWhenData(rawData));

        rawData = new HashMap<>();
        rawData.put("is_sx_new_custom", "Y");
        rawData.put("is_sx_fortune_promot_custom", "N");
        rawData.put("first_tenthousand_dt", "10");
        rawData.put("promotion_role_chn", "9");
        rawData.put("is_sx_emp", "Y");
        Assert.assertEquals("case..when..=解析結果錯誤",
                "Y1",
                helper.calcCaseWhenData(rawData));

        rawData = new HashMap<>();
        rawData.put("is_sx_emp", "N");
        rawData.put("is_sx_new_custom", "N");
        rawData.put("is_sx_fortune_promot_custom", "N");
        rawData.put("first_tenthousand_dt", "10");
        rawData.put("promotion_role_chn", "9");
        Assert.assertEquals("case..when..!=解析結果錯誤",
                "Y2",
                helper.calcCaseWhenData(rawData));

        rawData = new HashMap<>();
        rawData.put("is_sx_emp", "N");
        rawData.put("is_sx_new_custom", "Y");
        rawData.put("is_sx_fortune_promot_custom", "N");
        // rawData.put("first_tenthousand_dt", "10");
        rawData.put("promotion_role_chn", "9");
        Assert.assertEquals("case..when..in+and+null解析結果錯誤",
                "N",
                helper.calcCaseWhenData(rawData));


        condition = " case \n" +
                " \twhen is_sx_emp='Y' then 'Y1' \n" +
                " \twhen or_emp != null or or_emp2 > 3 then 'Y2_OR' \n" +
                " \twhen and_emp != null and and_emp2 > 3 or or_tmp3 <= 10 then 'Y3_OR' \n" +
                " \twhen promotion_role_chn not in ('10','11') and first_tenthousand_dt is not null then 'Y4' \n" +
                " \twhen promotion_role_chn not in ('10') then 'Y5_NOTIN' \n" +
                " \telse 'N_ELSE' \n" +
                " end";
        parsedClause = SimpleSyntaxParser.parse(condition);
        helper = new CaseWhenElDataCalcHelper(parsedClause.getAst().get(0));
        rawData = new HashMap<>();
        rawData.put("is_sx_emp", "N");
        rawData.put("or_emp", "Y");
        Assert.assertEquals("case..when..中解析字段信息不正確",
                8, parsedClause.getIdMapping().size());
        Assert.assertEquals("case..when..in解析結果錯誤",
                "Y2_OR",
                helper.calcCaseWhenData(rawData));

        rawData = new HashMap<>();
        rawData.put("is_sx_emp", "N");
        // rawData.put("or_emp", "Y");
        rawData.put("or_emp2", "2");
        Assert.assertEquals("case..when..or>2解析結果錯誤",
                "Y5_NOTIN",
                helper.calcCaseWhenData(rawData));

        rawData = new HashMap<>();
        rawData.put("is_sx_emp", "N");
        // rawData.put("or_emp", "Y");
        rawData.put("or_emp2", "2");
        rawData.put("promotion_role_chn", "10");
        Assert.assertEquals("case..when..notin解析結果錯誤",
                "N_ELSE",
                helper.calcCaseWhenData(rawData));

        condition = " case \n" +
                " \twhen (is_sx_emp='Y' or a_field=3) then 'Y1' \n" +
                " \telse 'N_ELSE' \n" +
                " end";
        parsedClause = SimpleSyntaxParser.parse(condition);
        helper = new CaseWhenElDataCalcHelper(parsedClause.getAst().get(0));
        rawData = new HashMap<>();
        rawData.put("is_sx_emp", "N");
        rawData.put("or_emp", "Y");
        Assert.assertEquals("case..when..中解析字段信息不正確",
                2, parsedClause.getIdMapping().size());
        Assert.assertEquals("case..when..()號解析結果錯誤",
                "N_ELSE",
                helper.calcCaseWhenData(rawData));

        rawData = new HashMap<>();
        rawData.put("is_sx_emp", "Y");
        rawData.put("or_emp", "Y");
        Assert.assertEquals("case..when..中解析字段信息不正確",
                2, parsedClause.getIdMapping().size());
        Assert.assertEquals("case..when..()號解析結果錯誤2",
                "Y1",
                helper.calcCaseWhenData(rawData));

    }
}

  若是有更多場景,咱們只需添加測試,而後完善相應邏輯便可。這裏全部的測試,均可以基於sql協議進行,若有空缺則應彌補相應功能,而非要求用戶按本身的標準來,畢竟標準是個好東西。

 

4. 更多表達式計算

  實際上,對錶達式計算這東西,咱們也許不必定非要本身去實現。畢竟太費力。有開源產品支持的,好比:aviator: https://www.oschina.net/p/aviator?hmsr=aladdin1e1     https://www.jianshu.com/p/02403dd1f4c4

  若是該語法不支持,則能夠先轉換成支持的語法,再使用其引擎計算便可。

  本質上,咱們都是在作翻譯工做!

相關文章
相關標籤/搜索