語法解析器續(xù):case..when表達(dá)式計算
之前寫過一篇博客,是關(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, 本次直接為falseif("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)算符");}// 比較兩個值大小abprivate 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ì)分tksMap<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個tokeni += 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個tokeni += 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ì)分tksprivate 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++;}@Overridepublic 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 {@Testpublic 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)真看完這篇文章

關(guān)注作者微信公眾號 —《JAVA爛豬皮》
了解更多java后端架構(gòu)知識以及最新面試寶典


看完本文記得給作者點(diǎn)贊+在看哦~~~大家的支持,是作者源源不斷出文的動力
作者:等你歸去來
出處:https://www.cnblogs.com/yougewe/p/14052362.html
