package com.smtaiserver.smtaiserver.javaai.metrics; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.dom4j.Document; import org.dom4j.Element; import com.smtaiserver.smtaiserver.core.SMTAIServerApp; import com.smtaiserver.smtaiserver.core.SMTAIServerRequest; import com.smtaiserver.smtaiserver.database.SMTDatabase.DBQueryNotify; import com.smtaiserver.smtaiserver.database.SMTDatabase.DBRecord; import com.smtaiserver.smtaiserver.database.SMTDatabase.DBRecords; import com.smtaiserver.smtaiserver.javaai.SMTJavaAIError; import com.smtaiserver.smtaiserver.javaai.ast.ASTCubeRecs; import com.smtaiserver.smtaiserver.javaai.ast.ASTCubeRecsValue; import com.smtaiserver.smtaiserver.javaai.ast.ASTDBMap; import com.smtaiserver.smtaiserver.javaai.ast.ASTDimFilter; import com.smtaiserver.smtaiserver.javaai.ast.ASTResult; import com.smtaiserver.smtaiserver.javaai.ast.ASTCubeRecs.ASTCubeRecsType; import com.smtaiserver.smtaiserver.javaai.metrics.base.SMTMetricSqlXml; import com.smtaiserver.smtaiserver.javaai.metrics.base.SMTMetricsDefXmlDim; import com.smtaiserver.smtaiserver.javaai.metrics.base.SMTMetricSqlXml.SQLXMLQuery; import com.smtservlet.util.Json; import com.smtservlet.util.SMTStatic; public class SMTMetricsDefDevBaseInfo extends SMTMetricsDefXmlDim { private SMTMetricSqlXml _sqlxmlMeticName; private SMTMetricSqlXml _sqlxmlQuotaName; private String[] _colTitles; private int _colKeyCount; private String _colDevKeyName; private String _posXColName; private String _posYColName; @Override public boolean isTimeValues() { return false; } @Override protected void initInstanceByDoc(DBRecord rec, Document doc) throws Exception { super.initInstanceByDoc(rec, doc); Element xmlNameSQL = (Element)doc.selectSingleNode("ROOT/NAME_SQL"); String sColTitles = SMTStatic.getXmlAttr(xmlNameSQL, "col_titles", null); _colDevKeyName = SMTStatic.getXmlAttr(xmlNameSQL, "dev_key_col", null); _posXColName = SMTStatic.getXmlAttr(xmlNameSQL, "pos_x_col", null); _posYColName = SMTStatic.getXmlAttr(xmlNameSQL, "pos_y_col", null); if(!(_posXColName == null && _posYColName == null) && !(_posXColName != null && _posYColName != null)) throw new Exception("pos_x_col and pos_y_col is different"); if(!SMTStatic.isNullOrEmpty(sColTitles)) _colTitles = sColTitles.split(","); _colKeyCount = SMTStatic.toInt(SMTStatic.getXmlAttr(xmlNameSQL, "key_cols", "1")); String sNameSQLRef = SMTStatic.getXmlAttr(xmlNameSQL, "ref", null); if(!SMTStatic.isNullOrEmpty(sNameSQLRef)) xmlNameSQL = (Element) xmlNameSQL.getDocument().selectSingleNode("ROOT/" + sNameSQLRef); _sqlxmlMeticName = new SMTMetricSqlXml(xmlNameSQL); Element xmlQuotaSQL = (Element)doc.selectSingleNode("ROOT/QUOTA_SQL"); if(xmlQuotaSQL != null) { _sqlxmlQuotaName = new SMTMetricSqlXml(xmlQuotaSQL); } } private SMTJavaAIError queryNearRangeSQL(String jsonPath, ASTDBMap dbMap, Json jsonAST, Map extArg, SMTAIServerRequest tranReq, SQLXMLQuery r_queryONAME) throws Exception { SMTJavaAIError error = null; // 获取当前的范围配置 Json jsonNearRange = jsonAST.safeGetJson("near_range"); // 如果不存在范围配置,则直接忽略 if(jsonNearRange == null) return null; // 生成范围查询记录集 List listNearRange = new ArrayList<>(); if(jsonNearRange.isObject()) { listNearRange.add(jsonNearRange); } else if(jsonNearRange.isArray()) { for(Json json : jsonNearRange.asJsonList()) { listNearRange.add(json); } } else { return new SMTJavaAIError("解析范围错误"); } for(Json jsonRange : listNearRange) { if(_posXColName == null) return new SMTJavaAIError("当前指标不支持定位操作"); Double posX; Double posY; boolean isCurPos = "Y".equalsIgnoreCase(jsonRange.safeGetStr("cur_pos", "N")); if(isCurPos) { double[] curPos = tranReq.getCurQuestionPos(); if(curPos == null) return new SMTJavaAIError("当前位置未定位,无法依据当前位置回答问题"); posX = curPos[0]; posY = curPos[1]; } else { // 解析过滤条件 StringBuilder sbDIM_NAME_FILTERS = new StringBuilder(); if((error = parseSQLFilterFromJson(dbMap, jsonRange, "", tranReq, sbDIM_NAME_FILTERS, null)) != null) return error; // 设置sql参数 Map mapId2SqlArg = new HashMap<>(); mapId2SqlArg.put("__METRICS_ID__", this.getId()); mapId2SqlArg.put("__DIM_NAME_FILTERS__", sbDIM_NAME_FILTERS.toString()); mapId2SqlArg.put("__DIM_NAME_GROUP__", ""); // 生成SQL SQLXMLQuery queryRANGE = new SQLXMLQuery(); if((error = this._sqlxmlMeticName.parseSQL(dbMap, jsonAST, mapId2SqlArg, tranReq, queryRANGE)) != null) return error; // 查询结果集 tranReq.traceLLMDebug(queryRANGE.getSqlLog()); // 执行SQL,查询到坐标 DBRecords recs = queryRANGE._db.querySQL(queryRANGE._sbSQLText.toString(), queryRANGE._sqlParams.toArray(new Object[queryRANGE._sqlParams.size()])); if(recs.getRowCount() == 0) return new SMTJavaAIError("未找到指定范围的设备"); // 获取设备坐标 DBRecord rec = recs.getRecord(0); posX = rec.getDouble(_posXColName); posY = rec.getDouble(_posYColName); if(posX == null || posY == null) return new SMTJavaAIError("指定范围的设备无坐标"); } // 对主查询结果外围套壳 String rangeOp = jsonRange.getJson("operate").asString(); if("范围".equals(rangeOp)) { double rangeValue = jsonRange.getJson("value").asDouble(); r_queryONAME._sbSQLText.insert(0, "SELECT * FROM (SELECT *,sqrt(" + " pow(" + _posXColName + " - " + SMTStatic.toString(posX) + ", 2)" + "+pow(" + _posYColName + " - " + SMTStatic.toString(posY) + ", 2)" + ") as __dist__ FROM ("); r_queryONAME._sbSQLText.append(")T)T WHERE __dist__ > 0.001 AND __dist__ < " + SMTStatic.toString(rangeValue)); } else if("最近".equals(rangeOp)) { r_queryONAME._sbSQLText.insert(0, "SELECT * FROM (SELECT *,sqrt(" + " pow(" + _posXColName + " - " + SMTStatic.toString(posX) + ", 2)" + "+pow(" + _posYColName + " - " + SMTStatic.toString(posY) + ", 2)" + ") as __dist__ FROM ("); r_queryONAME._sbSQLText.append(")T)T WHERE __dist__ > 0.001 ORDER BY __dist__ LIMIT 1 "); } else if("最远".equals(rangeOp)) { r_queryONAME._sbSQLText.insert(0, "SELECT * FROM (SELECT *,sqrt(" + " pow(" + _posXColName + " - " + SMTStatic.toString(posX) + ", 2)" + "+pow(" + _posYColName + " - " + SMTStatic.toString(posY) + ", 2)" + ") as __dist__ FROM ("); r_queryONAME._sbSQLText.append(")T)T WHERE __dist__ > 0.001 ORDER BY __dist__ DESC LIMIT 1 "); } else { return new SMTJavaAIError("指定范围的设备查询条件[" + rangeOp + "]不支持"); } // NOTE: 目前只支持第一个对象的查询,所以生成第一个SQL后就退出 break; } return null; } private SMTJavaAIError queryNoGroupRecords(ASTDBMap dbMap, ASTCubeRecs astRS, String stepOpKey, Json jsonAST, SQLXMLQuery queryONAME, SMTAIServerRequest tranReq) throws Exception { // 如果存在指标名称,则查出指标名称列表 Map mapDevKey2QuotaUrl = new HashMap<>(); if(_sqlxmlQuotaName != null && _colDevKeyName != null) { SQLXMLQuery queryQuota = new SQLXMLQuery(); SMTJavaAIError error = _sqlxmlQuotaName.parseSQL(dbMap, null, null, tranReq, queryQuota); if(error != null) return error; DBRecords recsQuotaName = queryQuota._db.querySQL(queryQuota._sbSQLText.toString(), queryQuota._sqlParams.size() == 0 ? null : queryQuota._sqlParams.toArray(new Object[] {queryQuota._sqlParams.size()})); for(DBRecord recQuotaName : recsQuotaName.getRecords()) { String devKey = recQuotaName.getString("dev_key"); String queryJson = recQuotaName.getString("query_json"); StringBuilder sbJsonList = mapDevKey2QuotaUrl.get(devKey); if(sbJsonList == null) { sbJsonList = new StringBuilder(); mapDevKey2QuotaUrl.put(devKey, sbJsonList); } if(sbJsonList.length() > 0) sbJsonList.append(","); sbJsonList.append(queryJson); } } // 扫描cube记录集 StringBuilder sbDimKey = new StringBuilder(); Map mapDimKey2RsIdx = new HashMap<>(); int[] recCount = new int[] {0}; Set setDimFieldPos = new HashSet<>(); List listValues = new ArrayList<>(); // 添加排序SQL String[] orderDim = tranReq.getChatOrderDimName(); if(orderDim != null && _mapId2SqlDimDef.containsKey(orderDim[0])) { queryONAME._sbSQLText.append(" ORDER BY " + orderDim[0] + " " + orderDim[1]); } // 查询结果集 tranReq.traceLLMDebug(queryONAME.getSqlLog()); queryONAME._db.querySQLNotify(queryONAME._sbSQLText.toString(), queryONAME._sqlParams.toArray(new Object[queryONAME._sqlParams.size()]), new DBQueryNotify() { @Override public boolean onNextRecord(DBRecord recONAME) throws Exception { recCount[0] ++; if(recCount[0] > 1000) return false; int posDevKey = (_colDevKeyName != null) ? recONAME.getColIndex(_colDevKeyName) : -1; int posPosX = (_posXColName != null) ? recONAME.getColIndex(_posXColName) : -1; int posPosY = (_posYColName != null) ? recONAME.getColIndex(_posYColName) : -1; if(posPosX < 0 || posPosY < 0) { posPosX = -1; posPosY = -1; } Integer rsIdx = null; // 将当前的dim值列表加入 if(astRS._dimNames.size() == 0) { astRS._recsType = ASTCubeRecsType.RECORD; rsIdx = -1; if(!astRS._mapDimId2Recs.containsKey(rsIdx)) { ASTCubeRecsValue recsCube = new ASTCubeRecsValue(); astRS._mapDimId2Recs.put(rsIdx, recsCube); } } else { astRS._recsType = ASTCubeRecsType.RECORD; StringBuilder sbDimNames = new StringBuilder(); Object[] dimValue = new Object[astRS._dimNames.size()]; sbDimKey.setLength(0); for(int i = 0; i < astRS._dimNames.size(); i ++) { String dimName = astRS._dimNames.get(i); setDimFieldPos.add(recONAME.getColIndex(dimName)); sbDimKey.append(recONAME.getString(dimName) + "\b"); dimValue[i] = recONAME.getValue(dimName); sbDimNames.append(recONAME.getString(dimName) + "-"); } sbDimNames.deleteCharAt(sbDimNames.length() - 1); rsIdx = mapDimKey2RsIdx.get(sbDimKey.toString()); if(rsIdx == null) { rsIdx = astRS._dimValues.size(); astRS._dimValues.add(dimValue); ASTCubeRecsValue recsCube = new ASTCubeRecsValue(); astRS._mapDimId2Recs.put(rsIdx, recsCube); mapDimKey2RsIdx.put(sbDimKey.toString(), rsIdx); recsCube._title = sbDimNames.toString(); } } // 将基本名称加入 ASTCubeRecsValue recsCube = astRS._mapDimId2Recs.get(rsIdx); // 将记录加入 if(recsCube._recsValue == null) { recsCube._recsValue = new DBRecords(); List listColNames = new ArrayList<>(); for(Entry entry : recONAME.getFieldMap().entrySet()) { if(setDimFieldPos.contains(entry.getValue())) continue; listColNames.add(entry.getKey()); } List listColTitles = new ArrayList<>(); for(int i = 0; i < astRS._colTitles.length; i ++) { if(setDimFieldPos.contains(i)) continue; listColTitles.add(astRS._colTitles[i]); } astRS._colTitles = listColTitles.toArray(new String[listColTitles.size()]); recsCube._recsValue.initColumn(listColNames.toArray(new String[listColNames.size()])); } if(setDimFieldPos.size() == 0) { Object[] values = recONAME.getValues(); if(posDevKey >= 0) { StringBuilder sbQuotaJson = mapDevKey2QuotaUrl.get(values[posDevKey]); if(sbQuotaJson == null) values[posDevKey] = null; else values[posDevKey] = "[" + sbQuotaJson.toString() + "]"; } if(posPosX > 0) { Object x = values[posPosX]; Object y = values[posPosY]; if(x != null && y != null) { double[] gisPos = SMTAIServerApp.getApp().convMapToGisTransform(new double[] {SMTStatic.toDouble(x), SMTStatic.toDouble(y)}); values[posPosX] = gisPos[0]; values[posPosY] = gisPos[1]; } } recsCube._recsValue.addRecord(recONAME.getValues()); } else { listValues.clear(); Object[] recValues = recONAME.getValues(); for(int i = 0; i < recValues.length; i ++) { if(setDimFieldPos.contains(i)) continue; listValues.add(recValues[i]); } if(posDevKey >= 0) { StringBuilder sbQuotaJson = mapDevKey2QuotaUrl.get(listValues.get(posDevKey)); if(sbQuotaJson == null) listValues.set(posDevKey, null); else listValues.set(posDevKey, "[" + sbQuotaJson.toString() + "]"); } if(posPosX > 0) { Object x = listValues.get(posPosX); Object y = listValues.get(posPosY); if(x != null && y != null) { double[] gisPos = SMTAIServerApp.getApp().convMapToGisTransform(new double[] {SMTStatic.toDouble(x), SMTStatic.toDouble(y)}); listValues.set(posPosX, gisPos[0]); listValues.set(posPosY, gisPos[1]); } } recsCube._recsValue.addRecord(listValues.toArray(new Object[listValues.size()])); } return true; } }); if(recCount[0] == 0) return new SMTJavaAIError("未找到任何相关记录"); return null; } private SMTJavaAIError queryGroupRecords(ASTCubeRecs astRS, String stepOpKey, Json jsonAST, SQLXMLQuery queryONAME, SMTAIServerRequest tranReq) throws Exception { //"step_dim_name" String groupAggField = "*"; String groupOperate = "COUNT"; if(!"COUNT".equals(stepOpKey)) { // {"is_output":false,"step_op_key":"SUM","step_op_title":"累计值","step_dim_name":"pipe_length"} groupAggField = jsonAST.safeGetStr("step_dim_name", null); if(SMTStatic.isNullOrEmpty(groupAggField)) return new SMTJavaAIError("未指定需要统计的字段"); SMTSQLXMLDimDef sqlXmlDimDef = this._mapId2SqlDimDef.get(groupAggField); if(sqlXmlDimDef == null) return new SMTJavaAIError("需要统计的维度[" + groupAggField + "]未定义"); // 设置标题 astRS._title += jsonAST.safeGetStr("step_op_title", ""); astRS._colTitles = new String[] {jsonAST.safeGetStr("step_op_title", "")}; groupOperate = jsonAST.getJson("step_op_key").asString(); groupAggField = "*"; } else { astRS._title += jsonAST.safeGetStr("step_op_title", ""); astRS._colTitles = new String[] {"个数"}; } // 按照分组进行统计个数 if(astRS._dimNames.size() > 0) { String sGroupDimField = ""; for(int i = 0; i < astRS._dimNames.size(); i ++) { if(i > 0) sGroupDimField += ","; sGroupDimField += astRS._dimNames.get(i); } String sql = "SELECT " + sGroupDimField + ", " + groupOperate + "(" + groupAggField + ") AS __CNT__ FROM (\n" + queryONAME._sbSQLText.toString() + "\n)T \nGROUP BY " + sGroupDimField; // 查询结果集 StringBuilder sbLogSQL = new StringBuilder(); sbLogSQL.append(sql + "\n"); for(Object param : queryONAME._sqlParams) { sbLogSQL.append(" param : " + SMTStatic.toString(param) + "\n"); } tranReq.traceLLMDebug("\n" + sbLogSQL.toString()); Map mapDimKey2RsIdx = new HashMap<>(); astRS._recsType = ASTCubeRecsType.VALUE; queryONAME._db.querySQLNotify(sql, queryONAME._sqlParams.toArray(new Object[queryONAME._sqlParams.size()]), new DBQueryNotify() { @Override public boolean onNextRecord(DBRecord recONAME) throws Exception { astRS._recsType = ASTCubeRecsType.RECORD; StringBuilder sbDimNames = new StringBuilder(); Object[] dimValue = new Object[astRS._dimNames.size()]; StringBuilder sbDimKey = new StringBuilder(); for(int i = 0; i < astRS._dimNames.size(); i ++) { String dimName = astRS._dimNames.get(i); sbDimKey.append(recONAME.getString(dimName) + "\b"); dimValue[i] = recONAME.getValue(dimName); sbDimNames.append(recONAME.getString(dimName) + "-"); } sbDimNames.deleteCharAt(sbDimNames.length() - 1); Integer rsIdx = astRS._dimValues.size(); astRS._dimValues.add(dimValue); ASTCubeRecsValue recsCube = new ASTCubeRecsValue(); astRS._mapDimId2Recs.put(rsIdx, recsCube); mapDimKey2RsIdx.put(sbDimKey.toString(), rsIdx); recsCube._title = sbDimNames.toString(); recsCube._countField = "CNT"; recsCube._groupField = "CNT"; recsCube._groupType = 'I'; recsCube._recsValue = new DBRecords(); recsCube._recsValue.initColumn(new String[] {"CNT"}); recsCube._recsValue.addRecord(new Object[] {recONAME.getInteger("__CNT__")}); return true; } }); } // 无分组直接统计个数 else { String sql = "SELECT " + groupOperate + "(" + groupAggField + ") AS CNT FROM (\n" + queryONAME._sbSQLText.toString() + "\n)T"; // 查询结果集 StringBuilder sbLogSQL = new StringBuilder(); sbLogSQL.append(sql + "\n"); for(Object param : queryONAME._sqlParams) { sbLogSQL.append(" param : " + SMTStatic.toString(param) + "\n"); } tranReq.traceLLMDebug(sbLogSQL.toString()); astRS._recsType = ASTCubeRecsType.SUMMARY; DBRecords recs = queryONAME._db.querySQL(sql, queryONAME._sqlParams.toArray(new Object[queryONAME._sqlParams.size()])); ASTCubeRecsValue recsCube = new ASTCubeRecsValue(); astRS._mapDimId2Recs.put(-1, recsCube); recsCube._countField = "CNT"; recsCube._groupField = "CNT"; recsCube._groupType = 'I'; recsCube._recsValue = recs; recsCube._title = jsonAST.safeGetStr("step_op_title", "个数");; } return null; } // 按生产厂家统计流量计个数 // 按照付款类型对用户表信息进行统计 @Override public SMTJavaAIError queryMetrics(String jsonPath, ASTDBMap dbMap, Json jsonAST, Map extArg, SMTAIServerRequest tranReq, ASTResult r_result) throws Exception { SMTJavaAIError error = this.executeQueryMetrics(jsonPath, dbMap, jsonAST, extArg, tranReq, r_result); if(error != null) { appendSampleQuestionToResponse(jsonAST, tranReq); return error; } else { return null; } } public SMTJavaAIError executeQueryMetrics(String jsonPath, ASTDBMap dbMap, Json jsonAST, Map extArg, SMTAIServerRequest tranReq, ASTResult r_result) throws Exception { ASTCubeRecs astRS = new ASTCubeRecs(); astRS._listDimFilter = new ArrayList<>(); // 将维度过滤器加上 for(SMTSQLXMLDimDef sqlXmlDimDef : _mapId2SqlDimDef.values()) { if(SMTStatic.isNullOrEmpty(sqlXmlDimDef._filterType)) continue; ASTDimFilter dimFilter = new ASTDimFilter(); astRS._listDimFilter.add(dimFilter); dimFilter._dimPath = jsonPath + sqlXmlDimDef._dimDef.getId(); dimFilter._filterType = sqlXmlDimDef._filterType; dimFilter._dimDef = sqlXmlDimDef._dimDef; } astRS._colTitles = this._colTitles; astRS._colKeyCount = this._colKeyCount; if(this._posXColName != null) { astRS._posXName = this._posXColName; astRS._posYName = this._posYColName; } if(this._colDevKeyName != null) { astRS._devKeyName = _colDevKeyName; } r_result._listRecordset.add(astRS); SMTJavaAIError error = null; // 如果存在查询名称的sqlxml,则首先查询名称 Map mapId2SqlArg = new HashMap<>(); // 解析要分组的维度列表 Map mapIdDimDef = new LinkedHashMap<>(); if((error = parseDimListFromJson(jsonAST, tranReq, mapIdDimDef)) != null) return error; StringBuilder sbDIM_NAME_GROUP = new StringBuilder(); for(String name : mapIdDimDef.keySet()) { sbDIM_NAME_GROUP.append(","); sbDIM_NAME_GROUP.append(name); } // 解析过滤条件 StringBuilder sbDIM_NAME_FILTERS = new StringBuilder(); if((error = parseSQLFilterFromJson(dbMap, jsonAST, "", tranReq, sbDIM_NAME_FILTERS, null)) != null) return error; // 设置sql参数 mapId2SqlArg.put("__METRICS_ID__", this.getId()); mapId2SqlArg.put("__DIM_NAME_FILTERS__", sbDIM_NAME_FILTERS.toString()); mapId2SqlArg.put("__DIM_NAME_GROUP__", sbDIM_NAME_GROUP.toString()); // 生成SQL SQLXMLQuery queryONAME = new SQLXMLQuery(); if((error = this._sqlxmlMeticName.parseSQL(dbMap, jsonAST, mapId2SqlArg, tranReq, queryONAME)) != null) return error; // 利用地理范围做SQL二次包装 if((error = queryNearRangeSQL(jsonPath, dbMap, jsonAST, extArg, tranReq, queryONAME)) != null) return error; tranReq.traceLLMDebug(queryONAME.getSqlLog()); // 创建cube记录集 for(Entry entry : mapIdDimDef.entrySet()) { astRS._dimNames.add(entry.getKey()); astRS._dimTitles.add(entry.getValue()._dimDef.getName()); } String title; if(astRS._dimNames.size() == 0) { title = this._title; } else { StringBuilder sbTitle = new StringBuilder(); for(String dimName : astRS._dimNames) { if(sbTitle.length() > 0) sbTitle.append(","); sbTitle.append(this._mapId2SqlDimDef.get(dimName)._dimDef.getName()); } sbTitle.insert(0, "按照"); sbTitle.append("分类的" + this._title); title = sbTitle.toString(); } astRS._title = title; // 输出查询条件 StringBuilder sbQueryProcess = new StringBuilder(); sbQueryProcess.append("查询"); if(astRS._dimNames.size() > 0) { sbQueryProcess.append("以:"); for(String dimName : astRS._dimNames) { sbQueryProcess.append(SMTAIServerApp.getApp().getDimensionDef(dimName).getName() + " "); } sbQueryProcess.append("为分组"); } else { sbQueryProcess.append("所有"); } sbQueryProcess.append("的数据..."); tranReq.sendChunkedBlock("begin", sbQueryProcess.toString()); // 如果存在聚合操作,则直接将SQL变成聚合操作 String stepOpKey = jsonAST.safeGetStr("step_op_key", ""); if(!SMTStatic.isNullOrEmpty(stepOpKey)) { if((error = this.queryGroupRecords(astRS, stepOpKey, jsonAST, queryONAME, tranReq)) != null) return error; } // 如果不存在聚合操作则查询所有结果 else { if((error = this.queryNoGroupRecords(dbMap, astRS, stepOpKey, jsonAST, queryONAME, tranReq)) != null) return error; } return null; } }