import {v4} from "uuid"; import {DB_TYPE} from "../../@types/types"; import ObjectUtil from "../../utils/ObjectUtil"; import {QueryUtils} from "./QueryUtils"; import {MybatisRegExp} from "./MybatisRegExp"; import {ArrayUtil} from "../../utils/ArrayUtil"; import {logger} from "../../logger/Logger"; export const ParamDirectionTypes = Object.freeze({ IN:0, OUT:1, INOUT:2, }) export class BindModeMybatisParser{ public convertChildren(connectionType:DB_TYPE, children:any, param:any, bindParams:any, namespace:any, myBatisMapper:any, dynamicCondition: any, isNotBindNullParam?:boolean){ let retObj:any; if (!param || param == null) { param = {}; } if (!bindParams || bindParams == null) { bindParams = {}; } 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(connectionType, children, param, bindParams, dynamicCondition, isNotBindNullParam); } else if (children.type == 'tag') { switch (children.name.toLowerCase()) { case 'if': return this.convertIf(connectionType, children, param, bindParams, namespace, myBatisMapper, dynamicCondition); case 'choose': return this.convertChoose(connectionType, children, param, bindParams, namespace, myBatisMapper, dynamicCondition); case 'trim': case 'where': return this.convertTrimWhere(connectionType, children, param, bindParams, namespace, myBatisMapper, dynamicCondition); case 'set': return this.convertSet(connectionType, children, param, bindParams, namespace, myBatisMapper, dynamicCondition); case 'foreach': return this.convertForeach(connectionType, children, param, bindParams, namespace, myBatisMapper, dynamicCondition); case 'bind': param = this.convertBind(connectionType, children, param, bindParams, dynamicCondition); return ''; case 'include': return this.convertInclude(connectionType, children, param, bindParams, namespace, myBatisMapper, dynamicCondition); default: throw new Error("XML is not well-formed character or markup. Consider using CDATA section."); } } else { return ''; } } public convertParameters(connectionType:DB_TYPE, children:any, param:any, bindParams:any, dynamicCondition: any, isNotBindNullParam?:boolean) { let convertString = children.content; try{ convertString = this.convertParametersInner(connectionType, '#', convertString, param, bindParams, isNotBindNullParam); convertString = this.convertParametersInner(connectionType, '$', convertString, param, bindParams, isNotBindNullParam); } 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) { throw new Error("Error occurred during convert CDATA section."); } return convertString; } public convertParametersInner(connectionType:DB_TYPE, change:any, convertString:any, param:any, bindParams:any, isNotBindNullParam?:boolean) { // except target comment ( /* ~~~~ */) 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); for (let i=0; i < stringTarget.length; i++) { let target = stringTarget[i]; let targetParam:string = target.replace(change + '{', '').replace('}',''); try{ let paramKeyArr = targetParam.split(',') let paramKey = paramKeyArr[0].trim(); let bindValue:any = {}; if(bindParams[paramKey]) { bindValue = bindParams[paramKey]; } else { for(let idx=0;idx < paramKeyArr.length; idx++) { let paramOptionKey = paramKeyArr[idx]; if(paramOptionKey.indexOf('=') < 0) { } else { let optionArr = paramOptionKey.split('='); let optionKey = optionArr[0].toLowerCase().trim(); let optionValue = optionArr[1].toLowerCase().trim(); bindValue[optionKey]=optionValue } } } let reg = new RegExp('\\' + target, 'g'); let bindParamConvert:string =""; // for value to convert // console.log(target) // console.log(targetParam) if(isNotBindNullParam == true) { return convertString; } if (change == '#') { if(bindParams[paramKey] != bindValue) { if(!bindValue.type){ bindValue.type = "varchar"; let bindParamValue = (param as any)[paramKey]; if(!bindParamValue) { bindValue["value"] = ""; } else { if (ObjectUtil.isObject(bindParamValue) || ArrayUtil.isArray(bindParamValue)) { bindParamValue = JSON.stringify(bindParamValue); } else { bindParamValue = bindParamValue.toString(); } bindParamValue = bindParamValue.replace(/'/g, '\\\'').replace(/"/g, '\\\"'); // bindParamValue = "'" + bindParamValue + "'" bindValue["value"] = bindParamValue } } else { bindValue["value"] = (param as any)[paramKey] } switch(bindValue.mode) { case "inout": bindValue.mode = ParamDirectionTypes.INOUT; break; case "out": bindValue.mode = ParamDirectionTypes.OUT; break; case "in": default: //null or undefined bindValue.mode = ParamDirectionTypes.IN; break; } bindParams[paramKey] = bindValue } switch(connectionType) { case "ORACLE": bindParamConvert = ":" + paramKey; break; case "MSSQL": bindParamConvert = "@" + paramKey; break; default:break; } convertString = convertString.replace(reg, bindParamConvert); } else if (change == '$') { bindParamConvert = (param as any)[paramKey] // for value to convert bindParamConvert = !bindParamConvert ? 'NULL': bindParamConvert; convertString = convertString.replace(reg, bindParamConvert); } // console.log(convertString) } catch(e) { logger.debug(e) throw new Error("Error occurred during convertParameters Inner."); } } } return convertString; } public convertIf(connectionType:DB_TYPE, children:any, param:any, bindParams:any, namespace:any, myBatisMapper:any, dynamicCondition: any) { try { if (this.checkTestCondition(children.attrs.test, param, dynamicCondition)) { let convertString = ''; for (let i=0, nextChildren; nextChildren=children['children'][i]; i++){ convertString += this.convertChildren(connectionType, nextChildren, param, bindParams, namespace, myBatisMapper, dynamicCondition); } return convertString; } else { return ''; } } catch (e) { return ''; } } public convertForeach(connectionType:DB_TYPE, children:any, param:any, bindParams:any, namespace:any, myBatisMapper:any, dynamicCondition: any) { 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 foreachTexts = []; let coll = null; for (let j=0; j 0){ foreachText += fText; } } 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 deleteBindParamKey = item+"."+colKey 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(connectionType, {content:foreachText}, foreachParam, bindParams, dynamicCondition); delete bindParams[deleteBindParamKey]; } } if (foreachText != null && foreachText.length > 0){ foreachTexts.push(foreachText); } } 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(connectionType, {content:foreachText}, foreachParam, bindParams, dynamicCondition); if (foreachText != null && foreachText.length > 0){ foreachTexts.push(foreachText); } } } delete bindParams[item]; return (open + foreachTexts.join(separator) + close); } catch (err) { logger.debug(err) throw new Error("Error occurred during convert element."); } } public convertChoose(connectionType:DB_TYPE, children:any, param:any, bindParams:any, namespace:any, myBatisMapper:any, dynamicCondition: any) { try{ for (let i=0, whenChildren; whenChildren=children.children[i];i++){ if (whenChildren.type == 'tag' && whenChildren.name.toLowerCase() == 'when'){ // Execute Evaluate string 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(connectionType, nextChildren, param, bindParams, namespace, myBatisMapper, dynamicCondition); } 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(connectionType, nextChildren, param, bindParams, namespace, myBatisMapper, dynamicCondition); } 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(connectionType:DB_TYPE, children:any, param:any, bindParams:any, namespace:any, myBatisMapper:any, dynamicCondition: any) { 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(connectionType, nextChildren, param, bindParams, namespace, myBatisMapper, dynamicCondition); } // 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(connectionType:DB_TYPE, children:any, param:any, bindParams:any, namespace:any, myBatisMapper:any, dynamicCondition: any) { let convertString = ''; try{ // Convert children first. for (let j=0, nextChildren; nextChildren=children.children[j]; j++){ convertString += this.convertChildren(connectionType, nextChildren, param, bindParams, namespace, myBatisMapper, dynamicCondition); } // 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) { throw new Error("Error occurred during convert element."); } } public convertBind(connectionType:DB_TYPE, children:any, param:any, bindParams:any, dynamicCondition: any) { // let evalString = children.attrs.value; // evalString = this.replaceEvalString(evalString, param); // param[children.attrs.name] = eval(evalString); // return param; param[children.attrs.name] = QueryUtils.parseParamsAndRunStatement(param, children.attrs.value, dynamicCondition) return param; } public convertInclude(connectionType:DB_TYPE, children:any, param:any, bindParams:any, namespace:string, myBatisMapper:any, dynamicCondition: any) { 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) { logger.debug(err) throw new Error("Error occurred during read element in element."); } let statement = ''; try{ let refid:string = this.convertParametersInner(connectionType, '#', children['attrs']['refid'], param, bindParams); refid = this.convertParametersInner(connectionType, '$', refid, param, bindParams); 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(connectionType, children, param, bindParams, childNamespace, myBatisMapper, dynamicCondition); } } 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; } }