/* * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Created on Jul 13, 2004 */ package org.jkiss.dbeaver.ext.erd.model; import org.jkiss.code.NotNull; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.ext.erd.editor.ERDAttributeVisibility; import org.jkiss.dbeaver.model.DBUtils; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.struct.*; import org.jkiss.dbeaver.model.struct.rdb.DBSTable; import org.jkiss.dbeaver.model.struct.rdb.DBSTableIndex; import org.jkiss.utils.CommonUtils; import java.util.*; /** * Model object representing a relational database Table * Also includes the bounds of the table so that the diagram can be * restored following a save, although ideally this should be * in a separate diagram specific model hierarchy * @author Serge Rider */ public class ERDEntity extends ERDObject<DBSEntity> { private static final Log log = Log.getLog(ERDEntity.class); private List<ERDEntityAttribute> columns; private List<ERDAssociation> primaryKeyRelationships; private List<ERDAssociation> foreignKeyRelationships; private List<DBSEntityAssociation> unresolvedKeys; private boolean primary = false; public ERDEntity(DBSEntity dbsTable) { super(dbsTable); } public void addColumn(ERDEntityAttribute column, boolean reflect) { if (columns == null) { columns = new ArrayList<>(); } if (columns.contains(column)) { throw new IllegalArgumentException("Column already present"); } columns.add(column); if (reflect) { firePropertyChange(CHILD, null, column); } } public void removeColumn(ERDEntityAttribute column, boolean reflect) { columns.remove(column); if (reflect) { firePropertyChange(CHILD, column, null); } } public void switchColumn(ERDEntityAttribute column, int index, boolean reflect) { columns.remove(column); columns.add(index, column); if (reflect) { firePropertyChange(REORDER, this, column); } } /** * Sets name without firing off any event notifications * * @param name * The name to set. * public void setName(String name) { this.name = name; }*/ /** * Adds relationship where the current object is the foreign key table in a relationship * * @param rel * the primary key relationship */ public void addForeignKeyRelationship(ERDAssociation rel, boolean reflect) { if (foreignKeyRelationships == null) { foreignKeyRelationships = new ArrayList<>(); } foreignKeyRelationships.add(rel); if (reflect) { firePropertyChange(OUTPUT, null, rel); } } /** * Adds relationship where the current object is the primary key table in a relationship * * @param table * the foreign key relationship */ public void addPrimaryKeyRelationship(ERDAssociation table, boolean reflect) { if (primaryKeyRelationships == null) { primaryKeyRelationships = new ArrayList<>(); } primaryKeyRelationships.add(table); if (reflect) { firePropertyChange(INPUT, null, table); } } /** * Removes relationship where the current object is the foreign key table in a relationship * * @param table * the primary key relationship */ public void removeForeignKeyRelationship(ERDAssociation table, boolean reflect) { foreignKeyRelationships.remove(table); if (reflect) { firePropertyChange(OUTPUT, table, null); } } /** * Removes relationship where the current object is the primary key table in a relationship * * @param table * the foreign key relationship */ public void removePrimaryKeyRelationship(ERDAssociation table, boolean reflect) { primaryKeyRelationships.remove(table); if (reflect) { firePropertyChange(INPUT, table, null); } } public List<ERDEntityAttribute> getColumns() { return CommonUtils.safeList(columns); } /** * @return Returns the foreignKeyRelationships. */ public List<ERDAssociation> getForeignKeyRelationships() { return CommonUtils.safeList(foreignKeyRelationships); } /** * @return Returns the primaryKeyRelationships. */ public List<ERDAssociation> getPrimaryKeyRelationships() { return CommonUtils.safeList(primaryKeyRelationships); } public boolean isPrimary() { return primary; } public void setPrimary(boolean primary) { this.primary = primary; } public boolean hasSelfLinks() { if (foreignKeyRelationships != null) { for (ERDAssociation association : foreignKeyRelationships) { if (association.getPrimaryKeyEntity() == this) { return true; } } } return false; } public String toString() { return object.getName(); } /** * hashcode implementation for use as key in Map */ public int hashCode() { return object.hashCode(); } /** * equals implementation for use as key in Map */ public boolean equals(Object o) { if (o == null || !(o instanceof ERDEntity)) return false; return object.equals(((ERDEntity)o).object); } public static ERDEntity fromObject(DBRProgressMonitor monitor, EntityDiagram diagram, DBSEntity entity) { ERDEntity erdEntity = new ERDEntity(entity); ERDAttributeVisibility attributeVisibility = diagram.getAttributeVisibility(); if (attributeVisibility != ERDAttributeVisibility.NONE) { Set<DBSEntityAttribute> keyColumns = null; if (attributeVisibility == ERDAttributeVisibility.KEYS) { keyColumns = new HashSet<>(); try { for (DBSEntityAssociation assoc : CommonUtils.safeCollection(entity.getAssociations(monitor))) { if (assoc instanceof DBSEntityReferrer) { keyColumns.addAll(DBUtils.getEntityAttributes(monitor, (DBSEntityReferrer) assoc)); } } for (DBSEntityConstraint constraint : CommonUtils.safeCollection(entity.getConstraints(monitor))) { if (constraint instanceof DBSEntityReferrer) { keyColumns.addAll(DBUtils.getEntityAttributes(monitor, (DBSEntityReferrer) constraint)); } } } catch (DBException e) { log.warn(e); } } Collection<? extends DBSEntityAttribute> idColumns = null; try { idColumns = getBestTableIdentifier(monitor, entity); if (keyColumns != null) { keyColumns.addAll(idColumns); } } catch (DBException e) { log.error("Error reading table identifier", e); } try { Collection<? extends DBSEntityAttribute> attributes = entity.getAttributes(monitor); if (!CommonUtils.isEmpty(attributes)) { for (DBSEntityAttribute attribute : attributes) { if (attribute instanceof DBSEntityAssociation) { // skip attributes which are associations // usual thing in some systems like WMI/CIM model continue; } if (DBUtils.isHiddenObject(attribute)) { // Skip hidden attributes continue; } switch (attributeVisibility) { case PRIMARY: if (idColumns == null || !idColumns.contains(attribute)) { continue; } break; case KEYS: if (!keyColumns.contains(attribute)) { continue; } break; default: break; } ERDEntityAttribute c1 = new ERDEntityAttribute(diagram, attribute, idColumns != null && idColumns.contains(attribute)); erdEntity.addColumn(c1, false); } } } catch (DBException e) { // just skip this problematic columns log.debug("Can't load table '" + entity.getName() + "'columns", e); } } return erdEntity; } @NotNull public static Collection<? extends DBSEntityAttribute> getBestTableIdentifier(@NotNull DBRProgressMonitor monitor, @NotNull DBSEntity entity) throws DBException { if (entity instanceof DBSTable && ((DBSTable) entity).isView()) { return Collections.emptyList(); } if (CommonUtils.isEmpty(entity.getAttributes(monitor))) { return Collections.emptyList(); } // Find PK or unique key DBSEntityConstraint uniqueId = null; //DBSEntityConstraint uniqueIndex = null; for (DBSEntityConstraint id : CommonUtils.safeCollection(entity.getConstraints(monitor))) { if (id instanceof DBSEntityReferrer && id.getConstraintType() == DBSEntityConstraintType.PRIMARY_KEY) { return DBUtils.getEntityAttributes(monitor, (DBSEntityReferrer) id); } else if (id.getConstraintType().isUnique()) { uniqueId = id; } else if (id instanceof DBSTableIndex && ((DBSTableIndex) id).isUnique()) { uniqueId = id; } } if (uniqueId instanceof DBSEntityReferrer) { return DBUtils.getEntityAttributes(monitor, (DBSEntityReferrer) uniqueId); } // Check indexes if (entity instanceof DBSTable) { try { Collection<? extends DBSTableIndex> indexes = ((DBSTable)entity).getIndexes(monitor); if (!CommonUtils.isEmpty(indexes)) { for (DBSTableIndex index : indexes) { if (DBUtils.isIdentifierIndex(monitor, index)) { return DBUtils.getEntityAttributes(monitor, index); } } } } catch (DBException e) { log.debug(e); } } return Collections.emptyList(); } public void addRelations(DBRProgressMonitor monitor, Map<DBSEntity, ERDEntity> tableMap, boolean reflect) { try { Set<DBSEntityAttribute> fkColumns = new HashSet<>(); // Make associations Collection<? extends DBSEntityAssociation> fks = getObject().getAssociations(monitor); if (fks != null) { for (DBSEntityAssociation fk : fks) { if (fk instanceof DBSEntityReferrer) { fkColumns.addAll(DBUtils.getEntityAttributes(monitor, (DBSEntityReferrer) fk)); } ERDEntity entity2 = tableMap.get(fk.getAssociatedEntity()); if (entity2 == null) { //log.debug("Table '" + fk.getReferencedKey().getTable().getFullyQualifiedName() + "' not found in ERD"); if (unresolvedKeys == null) { unresolvedKeys = new ArrayList<>(); } unresolvedKeys.add(fk); } else { //if (table1 != entity2) { new ERDAssociation(fk, entity2, this, reflect); //} } } } // Mark column's fk flag for (ERDEntityAttribute column : this.getColumns()) { if (fkColumns.contains(column.getObject())) { column.setInForeignKey(true); } } } catch (DBException e) { log.warn("Can't load table '" + getObject().getName() + "' foreign keys", e); } } public void resolveRelations(Map<DBSEntity, ERDEntity> tableMap, boolean reflect) { if (CommonUtils.isEmpty(unresolvedKeys)) { return; } for (Iterator<DBSEntityAssociation> iter = unresolvedKeys.iterator(); iter.hasNext(); ) { final DBSEntityAssociation fk = iter.next(); if (fk.getReferencedConstraint() != null) { ERDEntity refEntity = tableMap.get(fk.getReferencedConstraint().getParentObject()); if (refEntity != null) { new ERDAssociation(fk, refEntity, this, reflect); iter.remove(); } } } } @NotNull @Override public String getName() { return getObject().getName(); } }