import ObjectUtil from "../../utils/ObjectUtil"; import {v4} from "uuid"; import {QueryUtils} from "./QueryUtils"; import {MybatisRegExp} from "./MybatisRegExp"; import {ArrayUtil} from "../../utils/ArrayUtil"; import {logger} from "../../logger/Logger"; import StringUtil from "../../utils/StringUtil"; export class QueryModeMybatisParser { public convertChildren(children: any, param: any, namespace: any, myBatisMapper: any, dynamicCondition: any, isNullConvert = true) { if (param == null) { param = {}; } if (!ObjectUtil.isDict(param)) { throw new Error("Parameter argument should be Key-Value type or Null."); } if (children.type == 'text') { // Convert Parameters return this.convertParameters(children, param, dynamicCondition, isNullConvert); } else if (children.type == 'tag') { switch (children.name.toLowerCase()) { case 'if': return this.convertIf(children, param, namespace, myBatisMapper, dynamicCondition, isNullConvert); case 'choose': return this.convertChoose(children, param, namespace, myBatisMapper, dynamicCondition, isNullConvert); case 'trim': case 'where': return this.convertTrimWhere(children, param, namespace, myBatisMapper, dynamicCondition, isNullConvert); case 'set': return this.convertSet(children, param, namespace, myBatisMapper, dynamicCondition, isNullConvert); case 'foreach': return this.convertForeach(children, param, namespace, myBatisMapper, dynamicCondition, isNullConvert); case 'bind': param = this.convertBind(children, param, dynamicCondition, isNullConvert); return ''; case 'include': return this.convertInclude(children, param, namespace, myBatisMapper, dynamicCondition, isNullConvert); default: throw new Error("XML is not well-formed character or markup. Consider using CDATA section."); } } else { return ''; } } public convertParameters(children: any, param: any, dynamicCondition: any, isNullConvert:boolean) { let convertString = children.content; try { convertString = this.convertParametersInner('#', convertString, param, dynamicCondition, isNullConvert); convertString = this.convertParametersInner('$', convertString, param, dynamicCondition, isNullConvert); } catch (err) { logger.debug(err) throw new Error("Error occurred during convert parameters."); } try { // convert CDATA string convertString = convertString.replace(/(\&\;)/g, '&'); convertString = convertString.replace(/(\<\;)/g, '<'); convertString = convertString.replace(/(\>\;)/g, '>'); convertString = convertString.replace(/(\"\;)/g, '"'); } catch (err) { logger.debug(err) throw new Error("Error occurred during convert CDATA section."); } return convertString; } public convertParametersInner(change: any, convertString: any, param: any, dynamicCondition: any, isNullConvert:boolean) { let commentRemovedString = convertString.replace(MybatisRegExp.commentDeleteReg, ''); let stringReg = MybatisRegExp.parameterSearchReg(change); let stringTarget = commentRemovedString.match(stringReg); if (stringTarget != null && stringTarget.length > 0) { // stringTarget = ArrayUtil.uniqueArray(stringTarget); // console.log(stringTarget) for (let i = 0; i < stringTarget.length; i++) { let target = stringTarget[i]; let targetParam: string = target.replace(change + '{', '').replace('}', ''); // let tempParamKey = eval('param.' + targetParam); let tempParamKey = QueryUtils.parseParamsAndRunStatement(param, targetParam, dynamicCondition, isNullConvert) // let reg = MybatisRegExp.parameterChangeReg(change, targetParam); if (tempParamKey) { if (tempParamKey === null) { if(isNullConvert) { tempParamKey = 'NULL'; convertString = convertString.replaceAll(target, tempParamKey); } } else { if (change == '#') { // processing JSON fields structures if (ObjectUtil.isObject(tempParamKey) || ArrayUtil.isArray(tempParamKey)) { tempParamKey = JSON.stringify(tempParamKey); } else { tempParamKey = tempParamKey.toString().replace(/"/g, '\\\"'); } tempParamKey = tempParamKey.replace(/'/g, '\\\''); convertString = convertString.replaceAll(target, "'" + tempParamKey + "'"); } else if (change == '$') { convertString = convertString.replaceAll(target, tempParamKey); } } } else { if(isNullConvert) { tempParamKey = 'NULL'; convertString = convertString.replaceAll(target, tempParamKey); } } } } return convertString; } public convertIf(children: any, param: any, namespace: any, myBatisMapper: any, dynamicCondition: any, isNullConvert:boolean) { // Execute Evaluate string try { if (this.checkTestCondition(children.attrs.test, param, dynamicCondition)) { let convertString = ''; for (let i = 0, nextChildren; nextChildren = children['children'][i]; i++) { convertString += this.convertChildren(nextChildren, param, namespace, myBatisMapper, dynamicCondition, isNullConvert); } return convertString; } else { return ''; } } catch (e) { return ''; } } public convertForeach(children: any, param: any, namespace: any, myBatisMapper: any, dynamicCondition: any, isNullConvert:boolean) { try { if(children.attrs.test && !this.checkTestCondition(children.attrs.test, param, dynamicCondition)) { return ''; } if(children.attrs.directExecute) { if(!dynamicCondition || !dynamicCondition[children.attrs.directExecute]) return ''; return dynamicCondition[children.attrs.directExecute](param) } // let collection = eval('param.' + children.attrs.collection); let collection = QueryUtils.parseParamsAndRunStatement(param, children.attrs.collection, dynamicCondition) let item = children.attrs.item; let open = (children.attrs.open == null) ? '' : children.attrs.open; let close = (children.attrs.close == null) ? '' : children.attrs.close; let separator = (children.attrs.separator == null) ? '' : children.attrs.separator; let foreachIndex = (children.attrs.separator == null) ? '' : children.attrs.index; let foreachTexts = []; let coll = null; for (let j = 0; j < collection.length; j++) { coll = collection[j]; let foreachText = ''; //convert child in foreach tag for (let k = 0, nextChildren; nextChildren = children.children[k]; k++) { let fText = this.convertChildren(nextChildren, param, namespace, myBatisMapper, dynamicCondition, false); fText = fText.replace(MybatisRegExp.flatText, ''); if (fText != null && fText.length > 0) { foreachText += fText; } } // bind foreach parameter let foreachParam: any = {}; let bindReplaceKey = `${item}_${v4().replace(/(-)/g, '_')}`; if (typeof coll == 'object') { let collKeys = Object.keys(coll) for (let collKeysIdx = 0; collKeysIdx < collKeys.length; collKeysIdx++) { let colKey = collKeys[collKeysIdx]; let bindReplaceKeyColl = bindReplaceKey + "_" + colKey //set foreach object param foreachParam[bindReplaceKeyColl] = coll[colKey]; // let paramRegexColl = new RegExp('[\\s]?[\\s]?[\\s]?[\\s]?[\\s]?(^|[^\\w]|\\b)(' + item + '.' + colKey + ')($|[^\\w]|\\b)[\\s]?[\\s]?[\\s]?[\\s]?[\\s]?', 'g'); let paramRegexColl = MybatisRegExp.findOperator(item + '.' + colKey) if (paramRegexColl.test(foreachText)) { foreachText = foreachText.replace(paramRegexColl, bindReplaceKeyColl ) //foreachText = this.convertParameters({content: foreachText}, foreachParam, dynamicCondition, isNullConvert); // foreachText = this.convertParameters({content: foreachText}, foreachParam, dynamicCondition, false); } } } else { foreachParam[bindReplaceKey] = coll // let paramRegex = new RegExp('[\\s]?[\\s]?[\\s]?[\\s]?[\\s]?(^|[^\\w]|\\b)(' + item + ')($|[^\\w]|\\b)[\\s]?[\\s]?[\\s]?[\\s]?[\\s]?', 'g'); let paramRegex = MybatisRegExp.findOperator(item); foreachText = foreachText.replace(paramRegex, bindReplaceKey) // foreachText = this.convertParameters({content: foreachText}, foreachParam, dynamicCondition, isNullConvert); // foreachText = this.convertParameters({content: foreachText}, foreachParam, dynamicCondition, false); } if(!ObjectUtil.isNull(foreachIndex)) { // bind index tag let currIdx = j; let foreachIndexParam: any = {}; let bindReplaceKeyIndex = `${item}_${v4().replace(/(-)/g, '_')}_${currIdx}`; foreachParam[bindReplaceKeyIndex] = currIdx; let paramRegex = MybatisRegExp.findOperator(foreachIndex); foreachText = foreachText.replace(paramRegex, bindReplaceKeyIndex ) // foreachText = this.convertParameters({content: foreachText}, foreachIndexParam, dynamicCondition, isNullConvert); // foreachText = this.convertParameters({content: foreachText}, foreachIndexParam, dynamicCondition, false); } if(foreachParam && Object.keys(foreachParam).length > 0 ) { foreachText = this.convertParameters({content: foreachText}, foreachParam, dynamicCondition, isNullConvert); } if (foreachText != null && foreachText.length > 0) { foreachTexts.push(foreachText); } } return (open + foreachTexts.join(separator) + close); } catch (err) { logger.debug(err) throw new Error("Error occurred during convert element."); } } public convertChoose(children: any, param: any, namespace: any, myBatisMapper: any, dynamicCondition: any, isNullConvert:boolean) { try { for (let i = 0, whenChildren; whenChildren = children.children[i]; i++) { if (whenChildren.type == 'tag' && whenChildren.name.toLowerCase() == 'when') { try { if (this.checkTestCondition(whenChildren.attrs.test, param, dynamicCondition)) { // If condition is true, do it. let convertString = ''; for (let k = 0, nextChildren; nextChildren = whenChildren.children[k]; k++) { convertString += this.convertChildren(nextChildren, param, namespace, myBatisMapper, dynamicCondition, isNullConvert); } return convertString; } else { continue; } } catch (e) { continue; } } else if (whenChildren.type == 'tag' && whenChildren.name.toLowerCase() == 'otherwise') { // If reached tag, do it. let convertString = ''; for (let k = 0, nextChildren; nextChildren = whenChildren.children[k]; k++) { convertString += this.convertChildren(nextChildren, param, namespace, myBatisMapper, dynamicCondition, isNullConvert); } return convertString; } } // If there is no suitable when and otherwise, just return null. return ''; } catch (err) { logger.debug(err) throw new Error("Error occurred during convert element."); } } public convertTrimWhere(children: any, param: any, namespace: any, myBatisMapper: any, dynamicCondition: any, isNullConvert:boolean) { let convertString = ''; let prefix = null; let prefixOverrides = null; let globalSet = null; try { switch (children.name.toLowerCase()) { case 'trim': prefix = children.attrs.prefix; prefixOverrides = children.attrs.prefixOverrides; globalSet = 'g'; break; case 'where': prefix = 'WHERE'; prefixOverrides = 'and|or'; globalSet = 'gi'; break; default: throw new Error("Error occurred during convert element."); } // Convert children first. for (let j = 0, nextChildren; nextChildren = children.children[j]; j++) { convertString += this.convertChildren(nextChildren, param, namespace, myBatisMapper, dynamicCondition, isNullConvert); } // Remove prefixOverrides // let trimRegex = new RegExp('(^)([\\s]*?)(' + prefixOverrides + ')', globalSet); let trimRegex = MybatisRegExp.removePrefixOverrides(prefixOverrides, globalSet); convertString = convertString.replace(trimRegex, ''); if (children.name.toLowerCase() != 'trim') { // trimRegex = new RegExp('(' + prefixOverrides + ')([\\s]*?)($)', globalSet); trimRegex = MybatisRegExp.removePostfixOverrides(prefixOverrides,globalSet) convertString = convertString.replace(trimRegex, ''); } if(MybatisRegExp.isStringNotEmpty.test(convertString)) { convertString = prefix + ' ' + convertString; } // Remove comma(,) before WHERE if (children.name.toLowerCase() != 'where') { // let regex = new RegExp('(,)([\\s]*?)(where)', 'gi'); convertString = convertString.replace(MybatisRegExp.getCommaBefore("where", "gi"), ' WHERE '); } return convertString; } catch (err) { logger.debug(err) throw new Error("Error occurred during convert <" + children.name.toLowerCase() + "> element."); } } public convertSet(children: any, param: any, namespace: any, myBatisMapper: any, dynamicCondition: any, isNullConvert:boolean) { let convertString = ''; try { // Convert children first. for (let j = 0, nextChildren; nextChildren = children.children[j]; j++) { convertString += this.convertChildren(nextChildren, param, namespace, myBatisMapper, dynamicCondition, isNullConvert); } // Remove comma repeated more than 2. // let regex = new RegExp('(,)(,|\\s){2,}', 'g'); convertString = convertString.replace(MybatisRegExp.removeCommaRepeated2, ',\n'); // Remove first comma if exists. // regex = new RegExp('(^)([\\s]*?)(,)', 'g'); convertString = convertString.replace(MybatisRegExp.removeCommaFirstExist, ''); // Remove last comma if exists. // regex = new RegExp('(,)([\\s]*?)($)', 'g'); convertString = convertString.replace(MybatisRegExp.removeCommaLastExist, ''); convertString = ' SET ' + convertString; return convertString; } catch (err) { logger.debug(err) throw new Error("Error occurred during convert element."); } } public convertBind(children: any, param: any, dynamicCondition: any, isNullConvert:boolean) { param[children.attrs.name] = QueryUtils.parseParamsAndRunStatement(param, children.attrs.value, dynamicCondition) // let evalString = children.attrs.value; // evalString = this.replaceEvalString(evalString, param, dynamicCondition); // param[children.attrs.name] = eval(evalString); return param; } public convertInclude(children: any, param: any, namespace: string, myBatisMapper: any, dynamicCondition: any, isNullConvert:boolean) { try { // Add Properties to param for (let j = 0, nextChildren; nextChildren = children.children[j]; j++) { if (nextChildren.type == 'tag' && nextChildren.name == 'property') { param[nextChildren.attrs['name']] = nextChildren.attrs['value']; } } } catch (err) { throw new Error("Error occurred during read element in element."); } let statement = ''; try { let refid: string = this.convertParametersInner('#', children['attrs']['refid'], param, dynamicCondition, isNullConvert); refid = this.convertParametersInner('$', refid, param, dynamicCondition, isNullConvert); let childNamespace: string = namespace; let childRefId: string = refid; if (refid.indexOf('.') > -1) // namespace included { let splitArr = refid.split('.'); childNamespace = splitArr[0]; childRefId = splitArr[1]; } for (let i = 0, children; children = myBatisMapper[childNamespace][childRefId][i]; i++) { statement += this.convertChildren(children, param, childNamespace, myBatisMapper, dynamicCondition, isNullConvert); } } catch (err) { logger.debug(err) throw new Error("Error occurred during convert 'refid' attribute in element."); } return statement; } public checkTestCondition(conditionStr: string, param: any, dynamicCondition: any) { if(!conditionStr) return false; if (QueryUtils.parseParamsAndRunStatement(param, conditionStr, dynamicCondition)) { return true; } return false; } }