<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          語法解析器續(xù):case..when表達(dá)式計算

          共 32958字,需瀏覽 66分鐘

           ·

          2021-03-26 16:07

          走過路過不要錯過

          點(diǎn)擊藍(lán)字關(guān)注我們


          之前寫過一篇博客,是關(guān)于如何解析類似sql之類的解析器實現(xiàn)參考:https://www.cnblogs.com/yougewe/p/13774289.html

          之前的解析器,更多的是是做語言的翻譯轉(zhuǎn)換工作,并不涉及具體的數(shù)據(jù)運(yùn)算。而且拋棄了許多上下文關(guān)聯(lián)語法處理,所以相對還是簡單的。

          那么,如果我們想做一下數(shù)據(jù)運(yùn)算呢?比如我給你一些值,然后給你一個表達(dá)式,你可以給出其運(yùn)算結(jié)果嗎?

          1:表達(dá)式運(yùn)算難度如何?

          比如,已知表達(dá)式為, field1 > 0 and field2 > 0, 然后已知道值 field1 = 1, field2 = 2; 那么,此運(yùn)算結(jié)果必當(dāng)為true。這很理所當(dāng)然!

          但以上,僅為人工處理,自己用大腦做了下運(yùn)算,得到結(jié)果。如果轉(zhuǎn)換為代碼,又當(dāng)如何?

          我想,我們至少要做這么幾件事:

              1. 解析出所有字段有field1, field2;
              2. 解析出比較運(yùn)算符 >;
              3. 解析出右邊具體的比較值;
              4. 解析出連接運(yùn)算符and;
              5. 做所有的比較運(yùn)算;
              6. 關(guān)聯(lián)優(yōu)先級得到最終結(jié)果;

          怎么樣?現(xiàn)在還覺得很簡單嗎?如果是,請收下我的膝蓋!

          但是,如果真要做這種泛化的場景,那就相當(dāng)相當(dāng)復(fù)雜了,要知道類似于HIVE之類的重量級產(chǎn)品,語法解析都是其中重要的組成部分。實際上,這可能涉及到相當(dāng)多的語言規(guī)范需要做了。所以,必然超出我們的簡化理解范圍。

          所以,我這里僅挑一個簡單場景做解析:即如題所說,case..when..的解析。

          所以,我們可以范圍縮減為,給定表達(dá)式:case when field1 > 0 then 'f1' else 'fn' end; 的判斷解析。比如給定值 field1=1, 則應(yīng)得到結(jié)果 f1, 如果給定值 field1=0, 則應(yīng)得到結(jié)果 fn.

          在劃定范圍之后,好像更有了目標(biāo)感了。但是問題真的簡單了嗎?實際上,還是有相當(dāng)多的分支需要處理的,因為case..when..中可以嵌套其他語法。所以,我們只能盡力而為了。

          2. case..when..表達(dá)式運(yùn)算的實現(xiàn)

          命題確立之后,我們可以開始著手如何實現(xiàn)了。如上描述,我們有兩個已知條件:表達(dá)式和基礎(chǔ)值。

          基于上一篇文章的解析,我們基本可以快速得到所有組成case when 的元素token信息了。這就為我們省去了不少事。這里,我著重給一個如何獲取整個case..when..詞句的實現(xiàn),使其可形成一個獨(dú)立的詞組。

              // 將case..when.. 歸結(jié)為sql類關(guān)鍵詞的實現(xiàn)中    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結(jié)束,中間的所有詞根,都被劃作其范圍。當(dāng)然,還有一個重要的點(diǎn),是將數(shù)據(jù)字段找出來,放到可取到的地方。

          有了一個個獨(dú)立的元素,我們就可以進(jìn)行語義分析了。該分析可以放在該解析器中,但也許并不會太通用,所以,此處我將其抽象為一個單獨(dú)的值運(yùn)算類。在需要的地方,再實例化該運(yùn)算類,即可。核心問題如上一節(jié)中描述,具體實現(xiàn)代碼如下:

          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.. 真實數(shù)據(jù)運(yùn)算幫助類 * */@Slf4jpublic class CaseWhenElDataCalcHelper {
          /** * case when 完整語法 */ private SyntaxStatement caseWhenStmt;
          public CaseWhenElDataCalcHelper(SyntaxStatement caseWhenStmt) { this.caseWhenStmt = caseWhenStmt; }
          /** * 計算case..when的結(jié)果 * * @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..表達(dá)式"); } int tokenLen = allTokens.size(); if(tokenLen < 3) { throw new SyntaxException("case..when..表達(dá)式語法錯誤"); } TokenDescriptor closureToken = allTokens.get(tokenLen - 1); if(!"end".equalsIgnoreCase(closureToken.getRawWord())) { throw new SyntaxException("case..when..表達(dá)式未閉合"); } // 暫只支持 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); } // 直接進(jìn)入下一輪迭代,then后面為空迭代 } if("else".equalsIgnoreCase(currentToken.getRawWord())) { List<TokenDescriptor> resultCandidates = scrapeCaseWhenResultCandidates(allTokens, i); return calcExpressionData(resultCandidates, suppliers); } } return null; }
          /** * 撈出所有的結(jié)果運(yùn)算token列表 * * @param allTokens 全局token表 * @param start 偏移量 * @return 獲取到的所有結(jié)果運(yùn)算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 可供運(yùn)算的表達(dá)式token列表 * @param suppliers 原始字段取值來源 * @return true:符合該判定,false:判定失敗 */ private boolean judgeWhenExpression(List<TokenDescriptor> operatorCandidates, Map<String, String> suppliers) { List<AndOrOperatorSupervisor> supervisors = partitionByPriority(operatorCandidates); Boolean prevJudgeSuccess = null; for (AndOrOperatorSupervisor calc1 : supervisors) { Map<String, List<TokenDescriptor>> unitGroup = calc1.getUnitGroupTokens(); String op = unitGroup.get("OP").get(0).getRawWord(); if(calc1.getPrevType() != null && prevJudgeSuccess != null && !prevJudgeSuccess) { // 上一個and運(yùn)算為false, 本次直接為false if("and".equalsIgnoreCase(calc1.getPrevType().getRawWord())) { continue; } } String leftValue = calcExpressionData(unitGroup.get("LEFT"), suppliers); 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 && calc1.getNeedMoreStepsAfterMyFail() <= 0) { return false; } continue; } if("or".equalsIgnoreCase(nextType.getRawWord())) { if(myJudgeSuccess) { return true; } continue; } log.warn("解析到未知的next連接判定符:{}", nextType); throw new SyntaxException("語法解析錯誤"); } log.warn("未判定出結(jié)果,使用默認(rèn)返回,請檢查"); return false; }
          /** * 根據(jù)值信息推斷運(yùn)算數(shù)據(jù)類型 * * @param tokenList 結(jié)果列表(待運(yùn)算) * @return 計算的數(shù)據(jù)類型,數(shù)字或字符串 */ private TokenTypeEnum getPriorDataTypeByTokenList(List<TokenDescriptor> tokenList) { for (TokenDescriptor token : tokenList) { if(token.getTokenType() == TokenTypeEnum.WORD_STRING) { return TokenTypeEnum.WORD_STRING; } } return TokenTypeEnum.WORD_NUMBER; }
          /** * 運(yùn)算返回具體的 判定值 * * @param resultCandidates 結(jié)果表達(dá)式token列表 * @param suppliers 原始字段取值來源 * @return true:符合該判定,false:判定失敗 */ private String calcExpressionData(List<TokenDescriptor> resultCandidates, Map<String, String> suppliers) { // 暫時假設(shè)結(jié)果中不再提供運(yùn)算處理 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 比較運(yùn)算符 * @param bValue 右值 * @param valueType 值類型, 主要是區(qū)分?jǐn)?shù)字與字符 * @return 是否等式成立, true:成立, false:不成立 */ private boolean checkCompareTrue(String aValue, String op, String bValue, TokenTypeEnum valueType) { // 首先進(jìn)行相生性判定 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值不能用比較符號運(yùn)算"); 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("未知的運(yùn)算符"); } // 比較兩個值大小ab private int compareTwoData(String aValue, String bValue, TokenTypeEnum tokenType) { bValue = unwrapStringToken(bValue); if(bValue == null) { // 按任意值大于null 規(guī)則處理 return aValue == null ? 0 : 1; } // 被比較值為null, 則按小于計算 if(aValue == null) { return -1; } if(tokenType == TokenTypeEnum.WORD_NUMBER) { return new BigDecimal(aValue).compareTo( new BigDecimal(bValue)); } return aValue.compareTo(unwrapStringToken(bValue)); }
          // 將token重新分組,以便可以做原子運(yùn)算 private List<AndOrOperatorSupervisor> partitionByPriority(List<TokenDescriptor> tokens) { // 1. 取左等式token列表 // 2. 取等式表達(dá)式 // 3. 取右等式token列表 // 4. 構(gòu)建一個表達(dá)式,做最小分組 // 5. 檢查是否有下一運(yùn)算符,如有則必定為and|or|( // 6. 保存上一連接判定符,新開一個分組 // 7. 重復(fù)步驟1-6,直到取完所有token
          // 前置運(yùn)算符,決定是否要運(yùn)算本節(jié)點(diǎn),以及結(jié)果的合并方式 // 比如 and, 則當(dāng)前點(diǎn)必須參與運(yùn)算,如果前節(jié)點(diǎn)結(jié)果為false,則直接返回false // 否則先計算本節(jié)點(diǎn) TokenDescriptor preType = null; // 當(dāng)前節(jié)點(diǎn)計算完成后,判斷下一運(yùn)算是否有必要觸發(fā) // 為and時則當(dāng)前為true時必須觸發(fā),為or時當(dāng)前為false觸發(fā) TokenDescriptor nextType = null; // key 為 left, op, right, 各value為細(xì)分tks Map<String, List<TokenDescriptor>> unitGroup = new HashMap<>(); String currentReadPos = "LEFT"; List<TokenDescriptor> smallGroupTokenList = new ArrayList<>(); // 以上為描述單個運(yùn)算的字符,使用一個list就可以描述無括號的表達(dá)式了 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(); // 忽略分隔符,假設(shè)只有一級運(yùn)算,忽略空格帶來的復(fù)雜優(yōu)先級問題 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); // 滾動到下一運(yùn)算分支 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); } sortAndSetCalcFlag(bracketGroup); return bracketGroup; }
          /** * 按優(yōu)先級重排序或其他工作 * * @param bracketGroup 按先后排序的原子運(yùn)算 */ private void sortAndSetCalcFlag(List<AndOrOperatorSupervisor> bracketGroup) { for (int i = bracketGroup.size() - 1; i > 0; i--) { AndOrOperatorSupervisor calc1 = bracketGroup.get(i); TokenDescriptor prevUnionDesc = calc1.getPrevType(); if(prevUnionDesc == null) { continue; } if(!"or".equalsIgnoreCase(prevUnionDesc.getRawWord())) { continue; } // 增加當(dāng)前節(jié)點(diǎn)失敗后的重試次數(shù) for (int j = 0; j < i; j++) { AndOrOperatorSupervisor calcTemp = bracketGroup.get(j); calcTemp.incrStepAfterMyFail(); }
          }
          }
          /** * 解析in中的所有元素到結(jié)果中 * * @param tokens 所有token * @param curTokenList 當(dāng)前結(jié)果表 * @param start in 開始的地方 * @return in 語法結(jié)束位置 */ 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; }
          /** * 最小運(yùn)算單元描述符 */ private class AndOrOperatorSupervisor { // 前置運(yùn)算符,決定是否要運(yùn)算本節(jié)點(diǎn),以及結(jié)果的合并方式 // 比如 and, 則當(dāng)前點(diǎn)必須參與運(yùn)算,如果前節(jié)點(diǎn)結(jié)果為false,則直接返回false // 否則先計算本節(jié)點(diǎn) private TokenDescriptor prevType; // 當(dāng)前節(jié)點(diǎn)計算完成后,判斷下一運(yùn)算是否有必要觸發(fā) // 為and時則當(dāng)前為true時必須觸發(fā),為or時當(dāng)前為false觸發(fā) private TokenDescriptor nextType; // key 為 left, op, right, 各value為細(xì)分tks private Map<String, List<TokenDescriptor>> unitGroupTokens;
          /** * 當(dāng)當(dāng)前節(jié)點(diǎn)運(yùn)算失敗后,還需要嘗試的更多運(yùn)算數(shù)量(如 or 運(yùn)算) */ private int needMoreStepsAfterMyFail;
          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; }
          public int getNeedMoreStepsAfterMyFail() { return needMoreStepsAfterMyFail; }
          public void incrStepAfterMyFail() { this.needMoreStepsAfterMyFail++; }
          @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..的語句構(gòu)造出一個新的計算實例,然后調(diào)用 calcCaseWhenData(rawData), 帶入已知參數(shù)信息,即可運(yùn)算出最終的case..when..值。

          為使處理簡單起見,這里并沒有深入各種邏輯嵌套處理,直接忽略掉括號的處理了。另外,對于數(shù)值類的運(yùn)算也暫時被忽略,如 field1 > 1+1 這種運(yùn)算,并不會計算出2來。這些東西,需要的同學(xué),完全可以稍加完善,即可支持處理這些邏輯。

          對于null值的處理,此處遵循null值小于任意值,任意值大于null值的約定。類型區(qū)分字符型與數(shù)值型,以右值為類型推斷依據(jù)。即不允許將數(shù)值型值寫為字符串型,除非兩者可以得到同樣的結(jié)果。非數(shù)值型字段,不得用于數(shù)學(xué)運(yùn)算。(盡管以上實現(xiàn)并未處理數(shù)值運(yùn)算)

          因 case when 的語法還是比較清晰的,所以我們只是做了順序地讀取,判定即得出結(jié)果。另外對于 case when 的單值判定并不支持,所以實現(xiàn)并不復(fù)雜。但這完全不影響我們理解整個語法處理的思想。相信需要的同學(xué)定能有所啟發(fā)。

          3. 表達(dá)式計算單元測試

          以上僅實現(xiàn)代碼,需要附加上各種場景測試,才算可以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;
          @Slf4jpublic 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..解析結(jié)果錯誤", 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..解析結(jié)果錯誤", rawData.get("kehu_phone"), helper.calcCaseWhenData(rawData));
          rawData.remove("kehu_phone"); Assert.assertEquals("case..when..解析結(jié)果錯誤", 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解析結(jié)果錯誤", "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解析結(jié)果錯誤", "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..=解析結(jié)果錯誤", "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..!=解析結(jié)果錯誤", "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", "11.1"); Assert.assertEquals("case..when..in+and+null解析結(jié)果錯誤", "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 <= 11 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解析結(jié)果錯誤", "Y2_OR", helper.calcCaseWhenData(rawData));
          rawData = new HashMap<>(); rawData.put("is_sx_emp", "N"); // rawData.put("or_emp", "Y"); rawData.put("or_emp2", "2"); rawData.put("or_tmp3", "12"); Assert.assertEquals("case..when..or>2解析結(jié)果錯誤", "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("or_tmp3", "13"); rawData.put("promotion_role_chn", "10"); Assert.assertEquals("case..when..notin解析結(jié)果錯誤", "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..()號解析結(jié)果錯誤", "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..()號解析結(jié)果錯誤2", "Y1", helper.calcCaseWhenData(rawData));

          condition = " case \n" + " \twhen (field1 != '0' and field2 != '0' or field3 != '0') then field3 \n" + " \telse 'N_ELSE' \n" + " end"; parsedClause = SimpleSyntaxParser.parse(condition); helper = new CaseWhenElDataCalcHelper(parsedClause.getAst().get(0)); rawData = new HashMap<>(); rawData.put("field1", "f1"); rawData.put("field2", "f2"); rawData.put("field3", "f3"); Assert.assertEquals("case..when..中解析字段信息不正確", 3, parsedClause.getIdMapping().size()); Assert.assertEquals("case..when..and+or優(yōu)先級解析結(jié)果錯誤", rawData.get("field3"), helper.calcCaseWhenData(rawData));
          rawData = new HashMap<>(); rawData.put("field1", "0"); rawData.put("field2", "f2"); rawData.put("field3", "f3"); Assert.assertEquals("case..when..and+or優(yōu)先級解析結(jié)果錯誤", rawData.get("field3"), helper.calcCaseWhenData(rawData));
          }}

          如果有更多場景,我們只需添加測試,然后完善相應(yīng)邏輯即可。這里所有的測試,都可以基于sql協(xié)議進(jìn)行,如有空缺則應(yīng)彌補(bǔ)相應(yīng)功能,而非要求用戶按自己的標(biāo)準(zhǔn)來,畢竟標(biāo)準(zhǔn)是個好東西。

          4. 更多表達(dá)式計算

          實際上,對表達(dá)式計算這東西,我們也許不一定非要自己去實現(xiàn)。畢竟太費(fèi)力。有開源產(chǎn)品支持的,比如:aviator: https://www.oschina.net/p/aviator?hmsr=aladdin1e1     https://www.jianshu.com/p/02403dd1f4c4

          如果該語法不支持,則可以先轉(zhuǎn)換成支持的語法,再使用其引擎計算即可。

          表達(dá)式計算,看起來像是在做了計算的工作,然而本質(zhì)上,也都是在做翻譯工作,只不過它是將表達(dá)式翻譯成了java語言執(zhí)行而已!




          往期精彩推薦



          騰訊、阿里、滴滴后臺面試題匯總總結(jié) — (含答案)

          面試:史上最全多線程面試題 !

          最新阿里內(nèi)推Java后端面試題

          JVM難學(xué)?那是因為你沒認(rèn)真看完這篇文章


          END


          關(guān)注作者微信公眾號 —《JAVA爛豬皮》


          了解更多java后端架構(gòu)知識以及最新面試寶典


          你點(diǎn)的每個好看,我都認(rèn)真當(dāng)成了


          看完本文記得給作者點(diǎn)贊+在看哦~~~大家的支持,是作者源源不斷出文的動力


          作者:等你歸去來

          出處:https://www.cnblogs.com/yougewe/p/14052362.html

          瀏覽 50
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美色综合一区二区三区 | 亚洲精品黄色电影网站 | 亚洲A V观看 | 日韩一区二区三区日屄 | 天天操2025 |