/******************************************************************************* * Copyright (c) 2013 hangum. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v2.1 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * Contributors: * hangum - initial API and implementation ******************************************************************************/ package com.hangum.tadpole.rdb.erd.core.relation; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import com.hangum.tadpole.commons.libs.core.define.PublicTadpoleDefine; import com.hangum.tadpole.engine.define.DBGroupDefine; import com.hangum.tadpole.engine.manager.TadpoleSQLManager; import com.hangum.tadpole.engine.query.dao.mysql.ReferencedTableDAO; import com.hangum.tadpole.engine.query.dao.sqlite.SQLiteRefTableDAO; import com.hangum.tadpole.engine.query.dao.system.UserDBDAO; import com.hangum.tadpole.rdb.model.Column; import com.hangum.tadpole.rdb.model.DB; import com.hangum.tadpole.rdb.model.RdbFactory; import com.hangum.tadpole.rdb.model.Relation; import com.hangum.tadpole.rdb.model.RelationKind; import com.hangum.tadpole.rdb.model.Table; import com.ibatis.sqlmap.client.SqlMapClient; /** * ERD 출력시 Table relation 관련 코드 * * <pre> 컬럼 설명 constraint_name : 인덱스 이름 table_name : 소스 테이블 이름 column_name : 소스 테이블 컬럼 referenced_table_name : 타켓 테이블 이름 referenced_column_name : 타켓 테이블 컬럼 * </pre> * * @author hangum * */ public class RelationUtil { private static final Logger logger = Logger.getLogger(RelationUtil.class); /** * 특정 테이블 관계를 조회합니다. * * @param userDB * @param mapDBTables * @param db * @param refTableNames * @throws Exception */ public static void calRelation(UserDBDAO userDB, Map<String, Table> mapDBTables, DB db, String refTableNames) { try { // 현재 sqlite는 관계 정의를 못하겠는바 막습니다. if(DBGroupDefine.SQLITE_GROUP == userDB.getDBGroup()) { calRelation(userDB, mapDBTables, db, makeSQLiteRelation(userDB)); } else if(DBGroupDefine.CUBRID_GROUP == userDB.getDBGroup()) { calRelation(userDB, mapDBTables, db, CubridTableRelation.makeCubridRelation(userDB, refTableNames)); } else if(DBGroupDefine.HIVE_GROUP == userDB.getDBGroup()) { // ignore relation code } else if(DBGroupDefine.TAJO_GROUP == userDB.getDBGroup()) { // ignore relation code } else { calRelation(userDB, mapDBTables, db, getReferenceTable(userDB, refTableNames)); } } catch(Exception e) { logger.error("create relation ", e); } } /** * 모든 테이블을 조회합니다. * * @param userDB * @param mapDBTables * @param db * @throws Exception */ public static void calRelation(UserDBDAO userDB, Map<String, Table> mapDBTables, DB db) { try { // 현재 sqlite는 관계 정의를 못하겠는바 막습니다. if(DBGroupDefine.SQLITE_GROUP == userDB.getDBGroup()) { calRelation(userDB, mapDBTables, db, makeSQLiteRelation(userDB)); } else if(DBGroupDefine.CUBRID_GROUP == userDB.getDBGroup()) { calRelation(userDB, mapDBTables, db, CubridTableRelation.makeCubridRelation(userDB)); } else if(DBGroupDefine.HIVE_GROUP == userDB.getDBGroup()) { // ignore relation code } else if(DBGroupDefine.TAJO_GROUP == userDB.getDBGroup()) { // ignore relation code } else { calRelation(userDB, mapDBTables, db, getReferenceTable(userDB)); } } catch(Exception e) { logger.error("create relation ", e); } } /** * sqlite의 relation을 만든다. * * @param userDB * @return */ private static List<ReferencedTableDAO> makeSQLiteRelation(UserDBDAO userDB) { List<ReferencedTableDAO> listRealRefTableDAO = new ArrayList<ReferencedTableDAO>(); try { // 실제 레퍼런스를 생성합니다. for (SQLiteRefTableDAO sqliteRefTableDAO : getSQLiteRefTbl(userDB)) { String strFullTextSQL = sqliteRefTableDAO.getSql(); if(logger.isDebugEnabled()) logger.debug("\t full text:" + strFullTextSQL); int indexKey = StringUtils.indexOf(strFullTextSQL, "FOREIGN KEY"); if(indexKey == -1) { indexKey = StringUtils.indexOf(strFullTextSQL, "foreign key"); if(indexKey == -1) { if(logger.isDebugEnabled()) logger.debug("Not found foreign keys."); continue; } } String forKey = sqliteRefTableDAO.getSql().substring(indexKey); if(logger.isDebugEnabled()) logger.debug("\t=================>[forKeys]\n" + forKey); String[] foreignInfo = forKey.split("FOREIGN KEY"); if(foreignInfo.length == 1) foreignInfo = forKey.split("foreign key"); for(int i=1; i<foreignInfo.length; i++) { try { String strForeign = foreignInfo[i]; if(logger.isDebugEnabled()) logger.debug("\t ==========================> sub[\n" + strForeign + "]"); ReferencedTableDAO ref = new ReferencedTableDAO(); // 테이블 명 ref.setTable_name(sqliteRefTableDAO.getTbl_name()); // 컬럼명, 첫번째 ( 시작 부터 ) 끝날때까지... String colName = StringUtils.substringBetween(strForeign, "(", ")"); // 참조 테이블, REFERENCES 로 시작 하는 다음 부터 (까지 String refTbName = StringUtils.substringBetween(strForeign, "REFERENCES", "("); if("".equals(refTbName) || null == refTbName) refTbName = StringUtils.substringBetween(strForeign, "references", "("); // 참조 컬럼, refTbName의 끝나는 것부터 ) 까지... String refCol = StringUtils.substringBetween(strForeign, refTbName+"(" , ")"); ref.setColumn_name(moveSpec(colName)); ref.setReferenced_table_name(moveSpec(refTbName)); ref.setReferenced_column_name(moveSpec(refCol)); // sqlite는 인덱스 네임이 없으므로.... 생성합니다. ref.setConstraint_name(ref.toString()); listRealRefTableDAO.add(ref); } catch(Exception e) { logger.error("SQLLite Relation making", e); } } // inner if } // last for } catch(Exception e) { logger.error("SQLite Relation check 2", e); } return listRealRefTableDAO; } private static String moveSpec(String val) { return StringUtils.trimToEmpty( val.replaceAll("\\[", "").replaceAll("\\]", "") ); } /** * 테이블 관계를 구성합니다. * * @param userDB * @param mapDBTables * @param db * @throws Exception */ public static void calRelation(UserDBDAO userDB, Map<String, Table> mapDBTables, DB db, List<ReferencedTableDAO> referenceTableList) throws Exception { RdbFactory tadpoleFactory = RdbFactory.eINSTANCE; // 디비에서 관계 정보를 찾아서 넣어준다. for (ReferencedTableDAO refTabDAO : referenceTableList) { try { Table soTabMod = mapDBTables.get( refTabDAO.getTable_name() ); Table tarTabMod = mapDBTables.get( refTabDAO.getReferenced_table_name() ); // 소스테이블에 인덱스가 없고, 타겟 테이블이 있으면 추가한다. if(soTabMod != null && tarTabMod != null) { // 이미 추가된 relation인지 검사합니다. boolean isAlrealyAppend = false; for(Relation relation : soTabMod.getOutgoingLinks()) { if( relation.getConstraint_name() != null && refTabDAO.getConstraint_name() != null ) { if (relation.getConstraint_name().equalsIgnoreCase(refTabDAO.getConstraint_name())) { isAlrealyAppend = true; break; } } } for (Relation relation : soTabMod.getIncomingLinks()) { if( relation.getConstraint_name() != null && refTabDAO.getConstraint_name() != null ) { if (relation.getConstraint_name().equalsIgnoreCase(refTabDAO.getConstraint_name())) { isAlrealyAppend = true; break; } } } // TODO 현재 자신의 테이블을 키로 가자고 있는 항목은 다음과 같은 이유로 제거 합니다. // java.lang.RuntimeException: Cycle detected in graph if(refTabDAO.getTable_name().equalsIgnoreCase(refTabDAO.getReferenced_table_name())) continue; // 이미 추가 되어 있는가? if(isAlrealyAppend) continue; // 새롭게 추가될 요소 이면. Relation relation = tadpoleFactory.createRelation(); /* 저장시 아래와 같은 오류가 발생하여 추가한 코드 * 여유를 가지고 디버깅을 해봐야 하는코드 * * org.eclipse.emf.ecore.resource.Resource$IOWrappedException: * The object 'com.hangum.tadpole.model.impl.RelationImpl@5e44efa0 (source_kind: ONLY_ONE, target_kind: ONE_MANY, column_name: country_id, referenced_column_name: country_id, bendpoint: [], constraint_name: null)' * is not contained in a resource. */ relation.setDb(db); relation.setConstraint_name(refTabDAO.getConstraint_name()); relation.setColumn_name(refTabDAO.getColumn_name()); relation.setReferenced_column_name(refTabDAO.getReferenced_column_name()); /* * 위의 경우의 수를 이용하여 릴레이션을 생성합니다. */ Map<String, Column> sourceColumnsMap = new HashMap<String, Column>(); Map<String, Column> targetColumnMap = new HashMap<String, Column>(); for (Column column : soTabMod.getColumns()) sourceColumnsMap.put(column.getField(), column); for (Column column : tarTabMod.getColumns()) targetColumnMap.put(column.getField(), column); // source 컬럼 정보 Column col = null; // multi column if(StringUtils.contains(refTabDAO.getColumn_name(), ",")) { col = tadpoleFactory.createColumn(); col.setDefault(""); col.setExtra(""); col.setField(refTabDAO.getColumn_name()); col.setNull(""); col.setKey(""); col.setType(""); } else { col = sourceColumnsMap.get(refTabDAO.getColumn_name()); } // target 컬럼 정보 Column colR = null; if(StringUtils.contains(refTabDAO.getReferenced_column_name(), ",")) { colR = tadpoleFactory.createColumn(); colR.setDefault(""); colR.setExtra(""); colR.setField(refTabDAO.getReferenced_column_name()); colR.setNull(""); colR.setKey(""); colR.setType(""); } else { colR = targetColumnMap.get(refTabDAO.getReferenced_column_name().replaceAll(",", "")); } if(logger.isDebugEnabled()) { if(col == null || colR == null) { logger.debug("###[table index]###############################################################################"); logger.debug(db.getUrl() + ":" + refTabDAO.toString()); logger.debug("###[table index]###############################################################################"); } } if(col == null || colR == null) continue; // logger.debug("\t [source ]" + col.getField() + " [key]" + col.getKey()); // logger.debug("\t [target ]" + colR.getField() + " [key]" + colR.getKey()); relation.setSource_kind( calcRelationCol(col, colR) ); relation.setTarget_kind( calcRelationCol(colR, col) ); // 관계형성 soTabMod.getIncomingLinks().add(relation); tarTabMod.getOutgoingLinks().add(relation); relation.setSource(soTabMod); relation.setTarget(tarTabMod); }// if(souceModel != null && targetModel != null } catch(Exception e) { logger.error("create relation", e); } } // for } /** * 키 컬럼의의 조건을 relational type을 분석합니다. * * 상대방 컬럼이 NULL을 허용하면 자신은 없을수도 있음. * * @param soCol source table * @rapap taCol target table * @return */ public static RelationKind calcRelationCol(Column soCol, Column taCol) { if("YES".equals( taCol.getNull() ) || "YES".equals( soCol.getNull() )) { if( PublicTadpoleDefine.isPK( soCol.getKey() )) return RelationKind.ZERO_OR_ONE; else return RelationKind.ZERO_OR_MANY; } else { if( PublicTadpoleDefine.isPK( soCol.getKey() )) return RelationKind.ONLY_ONE; else return RelationKind.ONE_OR_MANY; } } /** * sqlite 참조용 테이블 * @param userDB * @return * @throws Exception */ public static List<SQLiteRefTableDAO> getSQLiteRefTbl(UserDBDAO userDB) throws Exception { SqlMapClient sqlClient = TadpoleSQLManager.getInstance(userDB); return sqlClient.queryForList("referencedTableListALL", userDB.getSchema()); } /** * sqlite 참조용 테이블 * @param userDB * @return * @throws Exception */ public static List<SQLiteRefTableDAO> getSQLiteRefTbl(UserDBDAO userDB, String tableName) throws Exception { SqlMapClient sqlClient = TadpoleSQLManager.getInstance(userDB); return sqlClient.queryForList("referencedTableList", tableName); } /** * 테이블의 참조 목록 정보를 리턴합니다. * * @return * @throws Exception */ public static List<ReferencedTableDAO> getReferenceTable(UserDBDAO userDB, String tableName) throws Exception { SqlMapClient sqlClient = TadpoleSQLManager.getInstance(userDB); Map<String, String> paramMap = new HashMap<String, String>(); paramMap.put("schema", userDB.getSchema()); paramMap.put("table", tableName); if(DBGroupDefine.ORACLE_GROUP == userDB.getDBGroup()) { return sqlClient.queryForList("referencedTableList", paramMap); }else{ return sqlClient.queryForList("referencedTableList", tableName); } } /** * 테이블의 참조 목록 정보를 리턴합니다. * * @return * @throws Exception */ public static List<ReferencedTableDAO> getReferenceTable(UserDBDAO userDB) throws Exception { SqlMapClient sqlClient = TadpoleSQLManager.getInstance(userDB); return sqlClient.queryForList("referencedTableListALL", userDB.getSchema()); } }