語法解析器續:case..when..語法解析計算
之前寫過一篇部落格,是關於如何解析類似sql之類的解析器實現參考:https://www.cnblogs.com/yougewe/p/13774289.html
之前的解析器,更多的是是做語言的翻譯轉換工作,並不涉及具體的資料運算。而且拋棄了許多上下文關聯語法處理,所以相對還是簡單的。
那麼,如果我們想做一下資料運算呢?比如我給你一些值,然後給你一個表示式,你可以給出其運算結果嗎?
1. 表示式運算難度如何?
比如,已知表示式為, field1 > 0 and field2 > 0, 然後已知道值 field1 = 1, field2 = 2; 那麼,此運算結果必當為true。這很理所當然!
但以上,僅為人工處理,自己用大腦做了下運算,得到結果。如果轉換為程式碼,又當如何?
我想,我們至少要做這麼幾件事:
1. 解析出所有欄位有field1, field2;
2. 解析出比較運算子 >;
3. 解析出右邊具體的比較值;
4. 解析出連線運算子and;
5. 做所有的比較運算;
6. 關聯優先順序得到最終結果;
怎麼樣?現在還覺得很簡單嗎?如果是,請收下我的膝蓋!
但是,如果真要做這種泛化的場景,那就相當相當複雜了,要知道類似於HIVE之類的重量級產品,語法解析都是其中重要的組成部分。實際上,這可能涉及到相當多的語言規範需要做了。所以,必然超出我們的簡化理解範圍。
所以,我這裡僅挑一個簡單場景做解析:即如題所說,case..when..的解析。
所以,我們可以範圍縮減為,給定表示式: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
如果該語法不支援,則可以先轉換成支援的語法,再使用其引擎計算即可。
本質上,我們都是在做翻譯工作!