![JAR search and dependency download from the Maven repository](/logo.png)
net.sourceforge.schemaspy.model.Table Maven / Gradle / Ivy
Show all versions of schemaspy Show documentation
/*
* This file is a part of the SchemaSpy project (http://schemaspy.sourceforge.net).
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 John Currier
*
* SchemaSpy is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* SchemaSpy is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.sourceforge.schemaspy.model;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import net.sourceforge.schemaspy.Config;
import net.sourceforge.schemaspy.model.xml.ForeignKeyMeta;
import net.sourceforge.schemaspy.model.xml.TableColumnMeta;
import net.sourceforge.schemaspy.model.xml.TableMeta;
import net.sourceforge.schemaspy.util.CaseInsensitiveMap;
/**
* A Table
is one of the basic building blocks of SchemaSpy
* that knows everything about the database table's metadata.
*
* @author John Currier
*/
public class Table implements Comparable {
private final String schema;
private final String name;
protected final CaseInsensitiveMap columns = new CaseInsensitiveMap();
private final List primaryKeys = new ArrayList();
private final CaseInsensitiveMap foreignKeys = new CaseInsensitiveMap();
private final CaseInsensitiveMap indexes = new CaseInsensitiveMap();
private Object id;
private final Map checkConstraints = new TreeMap(String.CASE_INSENSITIVE_ORDER);
private Integer numRows;
protected final Database db;
protected final Properties properties;
private String comments;
private int maxChildren;
private int maxParents;
private final static Logger logger = Logger.getLogger(Table.class.getName());
/**
* Construct a table that knows everything about the database table's metadata
*
* @param db
* @param schema
* @param name
* @param comments
* @param properties
* @param excludeIndirectColumns
* @param excludeColumns
* @throws SQLException
*/
public Table(Database db, String schema, String name, String comments, Properties properties, Pattern excludeIndirectColumns, Pattern excludeColumns) throws SQLException {
this.schema = schema;
this.name = name;
this.db = db;
this.properties = properties;
logger.fine("Creating " + getClass().getSimpleName().toLowerCase() + " " +
schema == null ? name : (schema + '.' + name));
setComments(comments);
initColumns(excludeIndirectColumns, excludeColumns);
initIndexes();
initPrimaryKeys(db.getMetaData());
}
/**
* "Connect" all of this table's foreign keys to their referenced primary keys
* (and, in some cases, do the reverse as well).
*
* @param tables
* @param excludeIndirectColumns
* @param excludeColumns
* @throws SQLException
*/
public void connectForeignKeys(Map tables, Pattern excludeIndirectColumns, Pattern excludeColumns) throws SQLException {
ResultSet rs = null;
try {
rs = db.getMetaData().getImportedKeys(null, getSchema(), getName());
while (rs.next()) {
addForeignKey(rs.getString("FK_NAME"), rs.getString("FKCOLUMN_NAME"),
rs.getString("PKTABLE_SCHEM"), rs.getString("PKTABLE_NAME"),
rs.getString("PKCOLUMN_NAME"),
rs.getInt("UPDATE_RULE"), rs.getInt("DELETE_RULE"),
tables, excludeIndirectColumns, excludeColumns);
}
} finally {
if (rs != null)
rs.close();
}
// also try to find all of the 'remote' tables in other schemas that
// point to our primary keys (not necessary in the normal case
// as we infer this from the opposite direction)
if (getSchema() != null) {
try {
rs = db.getMetaData().getExportedKeys(null, getSchema(), getName());
while (rs.next()) {
String otherSchema = rs.getString("FKTABLE_SCHEM");
if (!getSchema().equals(otherSchema))
db.addRemoteTable(otherSchema, rs.getString("FKTABLE_NAME"), getSchema(), properties, excludeIndirectColumns, excludeColumns);
}
} finally {
if (rs != null)
rs.close();
}
}
}
/**
* Get the foreign keys associated with this table
*
* @return
*/
public Collection getForeignKeys() {
return Collections.unmodifiableCollection(foreignKeys.values());
}
/**
* Add a check constraint to the table
* (no real details, just name and textual representation)
*
* @param constraintName
* @param text
*/
public void addCheckConstraint(String constraintName, String text) {
checkConstraints.put(constraintName, text);
}
/**
* @param rs ResultSet from {@link DatabaseMetaData#getImportedKeys(String, String, String)}
* rs.getString("FK_NAME");
* rs.getString("FKCOLUMN_NAME");
* rs.getString("PKTABLE_SCHEM");
* rs.getString("PKTABLE_NAME");
* rs.getString("PKCOLUMN_NAME");
* @param tables Map
* @param db
* @throws SQLException
*/
protected void addForeignKey(String fkName, String fkColName,
String pkTableSchema, String pkTableName, String pkColName,
int updateRule, int deleteRule,
Map tables,
Pattern excludeIndirectColumns, Pattern excludeColumns) throws SQLException {
if (fkName == null)
return;
ForeignKeyConstraint foreignKey = foreignKeys.get(fkName);
if (foreignKey == null) {
foreignKey = new ForeignKeyConstraint(this, fkName, updateRule, deleteRule);
foreignKeys.put(fkName, foreignKey);
}
TableColumn childColumn = getColumn(fkColName);
if (childColumn != null) {
foreignKey.addChildColumn(childColumn);
Table parentTable = tables.get(pkTableName);
String parentSchema = pkTableSchema;
String baseSchema = Config.getInstance().getSchema();
// if named table doesn't exist in this schema
// or exists here but really referencing same named table in another schema
if (parentTable == null ||
(baseSchema != null && parentSchema != null &&
!baseSchema.equals(parentSchema))) {
parentTable = db.addRemoteTable(parentSchema, pkTableName, baseSchema,
properties, excludeIndirectColumns, excludeColumns);
}
if (parentTable != null) {
TableColumn parentColumn = parentTable.getColumn(pkColName);
if (parentColumn != null) {
foreignKey.addParentColumn(parentColumn);
childColumn.addParent(parentColumn, foreignKey);
parentColumn.addChild(childColumn, foreignKey);
} else {
logger.warning("Couldn't add FK '" + foreignKey.getName() + "' to table '" + this +
"' - Column '" + pkColName + "' doesn't exist in table '" + parentTable + "'");
}
} else {
logger.warning("Couldn't add FK '" + foreignKey.getName() + "' to table '" + this +
"' - Unknown Referenced Table '" + pkTableName + "'");
}
} else {
logger.warning("Couldn't add FK '" + foreignKey.getName() + "' to table '" + this +
"' - Column '" + fkColName + "' doesn't exist");
}
}
/**
* @param meta
* @throws SQLException
*/
private void initPrimaryKeys(DatabaseMetaData meta) throws SQLException {
if (properties == null)
return;
ResultSet rs = null;
try {
rs = meta.getPrimaryKeys(null, getSchema(), getName());
while (rs.next())
setPrimaryColumn(rs);
} finally {
if (rs != null)
rs.close();
}
}
/**
* @param rs
* @throws SQLException
*/
private void setPrimaryColumn(ResultSet rs) throws SQLException {
String pkName = rs.getString("PK_NAME");
if (pkName == null)
return;
TableIndex index = getIndex(pkName);
if (index != null) {
index.setIsPrimaryKey(true);
}
String columnName = rs.getString("COLUMN_NAME");
setPrimaryColumn(getColumn(columnName));
}
/**
* @param primaryColumn
*/
void setPrimaryColumn(TableColumn primaryColumn) {
primaryKeys.add(primaryColumn);
}
/**
* @param excludeIndirectColumns
* @param excludeColumns
* @throws SQLException
*/
private void initColumns(Pattern excludeIndirectColumns, Pattern excludeColumns) throws SQLException {
ResultSet rs = null;
synchronized (Table.class) {
try {
rs = db.getMetaData().getColumns(null, getSchema(), getName(), "%");
while (rs.next())
addColumn(rs, excludeIndirectColumns, excludeColumns);
} catch (SQLException exc) {
class ColumnInitializationFailure extends SQLException {
private static final long serialVersionUID = 1L;
public ColumnInitializationFailure(SQLException failure) {
super("Failed to collect column details for " + (isView() ? "view" : "table") + " '" + getName() + "' in schema '" + getSchema() + "'");
initCause(failure);
}
}
throw new ColumnInitializationFailure(exc);
} finally {
if (rs != null)
rs.close();
}
}
if (!isView() && !isRemote())
initColumnAutoUpdate(false);
}
/**
* @param forceQuotes
* @throws SQLException
*/
private void initColumnAutoUpdate(boolean forceQuotes) throws SQLException {
ResultSet rs = null;
PreparedStatement stmt = null;
// we've got to get a result set with all the columns in it
// so we can ask if the columns are auto updated
// Ugh!!! Should have been in DatabaseMetaData instead!!!
StringBuilder sql = new StringBuilder("select * from ");
if (getSchema() != null) {
sql.append(getSchema());
sql.append('.');
}
if (forceQuotes) {
String quote = db.getMetaData().getIdentifierQuoteString().trim();
sql.append(quote + getName() + quote);
} else
sql.append(db.getQuotedIdentifier(getName()));
sql.append(" where 0 = 1");
try {
stmt = db.getMetaData().getConnection().prepareStatement(sql.toString());
rs = stmt.executeQuery();
ResultSetMetaData rsMeta = rs.getMetaData();
for (int i = rsMeta.getColumnCount(); i > 0; --i) {
TableColumn column = getColumn(rsMeta.getColumnName(i));
column.setIsAutoUpdated(rsMeta.isAutoIncrement(i));
}
} catch (SQLException exc) {
if (forceQuotes) {
// don't completely choke just because we couldn't do this....
logger.warning("Failed to determine auto increment status: " + exc);
logger.warning("SQL: " + sql.toString());
} else {
initColumnAutoUpdate(true);
}
} finally {
if (rs != null)
rs.close();
if (stmt != null)
stmt.close();
}
}
/**
* @param rs - from {@link DatabaseMetaData#getColumns(String, String, String, String)}
* @param excludeIndirectColumns
* @param excludeColumns
* @throws SQLException
*/
protected void addColumn(ResultSet rs, Pattern excludeIndirectColumns, Pattern excludeColumns) throws SQLException {
String columnName = rs.getString("COLUMN_NAME");
if (columnName == null)
return;
if (getColumn(columnName) == null) {
TableColumn column = new TableColumn(this, rs, excludeIndirectColumns, excludeColumns);
columns.put(column.getName(), column);
}
}
/**
* Add a column that's defined in xml metadata.
* Assumes that a column named colMeta.getName() doesn't already exist in columns
.
* @param colMeta
* @return
*/
protected TableColumn addColumn(TableColumnMeta colMeta) {
TableColumn column = new TableColumn(this, colMeta);
columns.put(column.getName(), column);
return column;
}
/**
* Initialize index information
*
* @throws SQLException
*/
private void initIndexes() throws SQLException {
if (isView() || isRemote())
return;
// first try to initialize using the index query spec'd in the .properties
// do this first because some DB's (e.g. Oracle) do 'bad' things with getIndexInfo()
// (they try to do a DDL analyze command that has some bad side-effects)
if (initIndexes(properties.getProperty("selectIndexesSql")))
return;
// couldn't, so try the old fashioned approach
ResultSet rs = null;
try {
rs = db.getMetaData().getIndexInfo(null, getSchema(), getName(), false, true);
while (rs.next()) {
if (rs.getShort("TYPE") != DatabaseMetaData.tableIndexStatistic)
addIndex(rs);
}
} catch (SQLException exc) {
logger.warning("Unable to extract index info for table '" + getName() + "' in schema '" + getSchema() + "': " + exc);
} finally {
if (rs != null)
rs.close();
}
}
/**
* Try to initialize index information based on the specified SQL
*
* @return boolean true
if it worked, otherwise false
*/
private boolean initIndexes(String selectIndexesSql) {
if (selectIndexesSql == null)
return false;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
stmt = db.prepareStatement(selectIndexesSql, getName());
rs = stmt.executeQuery();
while (rs.next()) {
if (rs.getShort("TYPE") != DatabaseMetaData.tableIndexStatistic)
addIndex(rs);
}
} catch (SQLException sqlException) {
logger.warning("Failed to query index information with SQL: " + selectIndexesSql);
logger.warning(sqlException.toString());
return false;
} finally {
if (rs != null) {
try {
rs.close();
} catch (Exception exc) {
exc.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (Exception exc) {
exc.printStackTrace();
}
}
}
return true;
}
/**
* @param indexName
* @return
*/
public TableIndex getIndex(String indexName) {
return indexes.get(indexName);
}
/**
* @param rs
* @throws SQLException
*/
private void addIndex(ResultSet rs) throws SQLException {
String indexName = rs.getString("INDEX_NAME");
if (indexName == null)
return;
TableIndex index = getIndex(indexName);
if (index == null) {
index = new TableIndex(rs);
indexes.put(index.getName(), index);
}
index.addColumn(getColumn(rs.getString("COLUMN_NAME")), rs.getString("ASC_OR_DESC"));
}
/**
* Returns the schema that the table belongs to
*
* @return
*/
public String getSchema() {
return schema;
}
/**
* Returns the name of the table
*
* @return
*/
public String getName() {
return name;
}
/**
* Object IDs are useful for tables such as DB/2 that many times
* give error messages based on object ID and not name
*
* @param id
*/
public void setId(Object id) {
this.id = id;
}
/**
* @see #setId(Object)
*
* @return
*/
public Object getId() {
return id;
}
/**
* Returns the check constraints associated with this table
*
* @return
*/
public Map getCheckConstraints() {
return checkConstraints;
}
/**
* Returns the indexes that are applied to this table
*
* @return
*/
public Set getIndexes() {
return new HashSet(indexes.values());
}
/**
* Returns a collection of table columns that have been identified as "primary"
*
* @return
*/
public List getPrimaryColumns() {
return primaryKeys;
}
/**
* @return Comments associated with this table, or null
if none.
*/
public String getComments() {
return comments;
}
/**
* Sets the comments that are associated with this table
*
* @param comments
*/
public void setComments(String comments) {
String cmts = (comments == null || comments.trim().length() == 0) ? null : comments.trim();
// MySQL's InnoDB engine does some insane crap of storing erroneous details in
// with table comments. Here I attempt to strip the "crap" out without impacting
// other databases. Ideally this should happen in selectColumnCommentsSql (and
// therefore isolate it to MySQL), but it's a bit too complex to do cleanly.
if (cmts != null) {
int crapIndex = cmts.indexOf("; InnoDB free: ");
if (crapIndex == -1)
crapIndex = cmts.startsWith("InnoDB free: ") ? 0 : -1;
if (crapIndex != -1) {
cmts = cmts.substring(0, crapIndex).trim();
cmts = cmts.length() == 0 ? null : cmts;
}
}
this.comments = cmts;
}
/**
* Returns the {@link TableColumn} with the given name, or null
* if it doesn't exist
*
* @param columnName
* @return
*/
public TableColumn getColumn(String columnName) {
return columns.get(columnName);
}
/**
* Returns List
of TableColumn
s in ascending column number order.
*
* @return
*/
public List getColumns() {
Set sorted = new TreeSet(new ByColumnIdComparator());
sorted.addAll(columns.values());
return new ArrayList(sorted);
}
/**
* Returns true
if this table references no other tables..
* Used in dependency analysis.
* @return
*/
public boolean isRoot() {
for (TableColumn column : columns.values()) {
if (column.isForeignKey()) {
return false;
}
}
return true;
}
/**
* Returns true
if this table is referenced by no other tables.
* Used in dependency analysis.
* @return
*/
public boolean isLeaf() {
for (TableColumn column : columns.values()) {
if (!column.getChildren().isEmpty()) {
return false;
}
}
return true;
}
/**
* Returns the maximum number of parents that this table has had before
* any had been removed during dependency analysis
*
* @return
*/
public int getMaxParents() {
return maxParents;
}
/**
* Notification that's called to indicate that a parent has been added to
* this table
*/
public void addedParent() {
maxParents++;
}
/**
* "Unlink" all of the parent tables from this table
*/
public void unlinkParents() {
for (TableColumn column : columns.values()) {
column.unlinkParents();
}
}
/**
* Returns the maximum number of children that this table has had before
* any had been removed during dependency analysis
*
* @return
*/
public int getMaxChildren() {
return maxChildren;
}
/**
* Notification that's called to indicate that a child has been added to
* this table
*/
public void addedChild() {
maxChildren++;
}
/**
* "Unlink" all of the child tables from this table
*/
public void unlinkChildren() {
for (TableColumn column : columns.values()) {
column.unlinkChildren();
}
}
/**
* Remove a single self referencing constraint if one exists.
*
* @return
*/
public ForeignKeyConstraint removeSelfReferencingConstraint() {
return remove(getSelfReferencingConstraint());
}
/**
* Remove the specified {@link ForeignKeyConstraint} from this table.
*
* This is a more drastic removal solution that was proposed by Remke Rutgers
*
* @param constraint
*/
private ForeignKeyConstraint remove(ForeignKeyConstraint constraint) {
if (constraint != null) {
for (int i = 0; i < constraint.getChildColumns().size(); i++) {
TableColumn childColumn = constraint.getChildColumns().get(i);
TableColumn parentColumn = constraint.getParentColumns().get(i);
childColumn.removeParent(parentColumn);
parentColumn.removeChild(childColumn);
}
}
return constraint;
}
/**
* Return a self referencing constraint if one exists
*
* @return
*/
private ForeignKeyConstraint getSelfReferencingConstraint() {
for (TableColumn column : columns.values()) {
for (TableColumn parentColumn : column.getParents()) {
if (compareTo(parentColumn.getTable()) == 0) {
return column.getParentConstraint(parentColumn);
}
}
}
return null;
}
/**
* Remove any non-real foreign keys
*
* @return
*/
public List removeNonRealForeignKeys() {
List nonReals = new ArrayList();
for (TableColumn column : columns.values()) {
for (TableColumn parentColumn : column.getParents()) {
ForeignKeyConstraint constraint = column.getParentConstraint(parentColumn);
if (constraint != null && !constraint.isReal()) {
nonReals.add(constraint);
}
}
}
// remove constraints outside of above loop to prevent
// concurrent modification exceptions while iterating
for (ForeignKeyConstraint constraint : nonReals) {
remove(constraint);
}
return nonReals;
}
/**
* Returns the number of tables that reference this table
*
* @return
*/
public int getNumChildren() {
int numChildren = 0;
for (TableColumn column : columns.values()) {
numChildren += column.getChildren().size();
}
return numChildren;
}
/**
* Returns the number of non-implied children
* @return
*/
public int getNumNonImpliedChildren() {
int numChildren = 0;
for (TableColumn column : columns.values()) {
for (TableColumn childColumn : column.getChildren()) {
if (!column.getChildConstraint(childColumn).isImplied())
++numChildren;
}
}
return numChildren;
}
/**
* Returns the number of tables that are referenced by this table
*
* @return
*/
public int getNumParents() {
int numParents = 0;
for (TableColumn column : columns.values()) {
numParents += column.getParents().size();
}
return numParents;
}
/**
* Returns the number of non-implied parents
*
* @return
*/
public int getNumNonImpliedParents() {
int numParents = 0;
for (TableColumn column : columns.values()) {
for (TableColumn parentColumn : column.getParents()) {
if (!column.getParentConstraint(parentColumn).isImplied())
++numParents;
}
}
return numParents;
}
/**
* Remove one foreign key constraint.
*
* Used during dependency analysis phase.
*
* @return
*/
public ForeignKeyConstraint removeAForeignKeyConstraint() {
@SuppressWarnings("hiding")
final List columns = getColumns();
int numParents = 0;
int numChildren = 0;
// remove either a child or parent, choosing which based on which has the
// least number of foreign key associations (when either gets to zero then
// the table can be pruned)
for (TableColumn column : columns) {
numParents += column.getParents().size();
numChildren += column.getChildren().size();
}
for (TableColumn column : columns) {
ForeignKeyConstraint constraint;
if (numParents <= numChildren)
constraint = column.removeAParentFKConstraint();
else
constraint = column.removeAChildFKConstraint();
if (constraint != null)
return constraint;
}
return null;
}
/**
* Returns true
if this is a view, false
otherwise
*
* @return
*/
public boolean isView() {
return false;
}
/**
* Returns true
if this table is remote (in another schema), false
otherwise
* @return
*/
public boolean isRemote() {
return false;
}
/**
* If this is a view it returns the SQL used to create the view (if it's available).
* null
if it's not a view or the SQL isn't available.
* @return
* @see #isView()
*/
public String getViewSql() {
return null;
}
/**
* Returns the number of rows contained in this table, or -1 if unable to determine
* the number of rows.
*
* @return
*/
public int getNumRows() {
if (numRows == null) {
numRows = Config.getInstance().isNumRowsEnabled() ? fetchNumRows() : -1;
}
return numRows;
}
/**
* Explicitly set the number of rows in this table
*
* @param numRows
*/
public void setNumRows(int numRows) {
this.numRows = numRows;
}
/**
* Fetch the number of rows contained in this table.
*
* returns -1 if unable to successfully fetch the row count
*
* @param db Database
* @return int
* @throws SQLException
*/
protected int fetchNumRows() {
if (properties == null) // some "meta" tables don't have associated properties
return 0;
SQLException originalFailure = null;
String sql = properties.getProperty("selectRowCountSql");
if (sql != null) {
PreparedStatement stmt = null;
ResultSet rs = null;
try {
stmt = db.prepareStatement(sql, getName());
rs = stmt.executeQuery();
while (rs.next()) {
return rs.getInt("row_count");
}
} catch (SQLException sqlException) {
// don't die just because this failed
originalFailure = sqlException;
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException exc) {}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException exc) {}
}
}
}
// if we get here then we either didn't have custom SQL or it didn't work
try {
// '*' should work best for the majority of cases
return fetchNumRows("count(*)", false);
} catch (SQLException try2Exception) {
try {
// except nested tables...try using '1' instead
return fetchNumRows("count(1)", false);
} catch (SQLException try3Exception) {
logger.warning("Unable to extract the number of rows for table " + getName() + ", using '-1'");
if (originalFailure != null)
logger.warning(originalFailure.toString());
logger.warning(try2Exception.toString());
logger.warning(try3Exception.toString());
return -1;
}
}
}
protected int fetchNumRows(String clause, boolean forceQuotes) throws SQLException {
PreparedStatement stmt = null;
ResultSet rs = null;
StringBuilder sql = new StringBuilder("select ");
sql.append(clause);
sql.append(" from ");
if (getSchema() != null) {
sql.append(getSchema());
sql.append('.');
}
if (forceQuotes) {
String quote = db.getMetaData().getIdentifierQuoteString().trim();
sql.append(quote + getName() + quote);
} else
sql.append(db.getQuotedIdentifier(getName()));
try {
stmt = db.getConnection().prepareStatement(sql.toString());
rs = stmt.executeQuery();
while (rs.next()) {
return rs.getInt(1);
}
return -1;
} catch (SQLException exc) {
if (forceQuotes) // we tried with and w/o quotes...fail this attempt
throw exc;
return fetchNumRows(clause, true);
} finally {
if (rs != null)
rs.close();
if (stmt != null)
stmt.close();
}
}
/**
* Update the table with the specified XML-derived metadata
*
* @param tableMeta
*/
public void update(TableMeta tableMeta) {
String newComments = tableMeta.getComments();
if (newComments != null) {
comments = newComments;
}
for (TableColumnMeta colMeta : tableMeta.getColumns()) {
TableColumn col = getColumn(colMeta.getName());
if (col == null) {
if (tableMeta.getRemoteSchema() == null) {
logger.warning("Unrecognized column '" + colMeta.getName() + "' for table '" + getName() + '\'');
continue;
}
col = addColumn(colMeta);
}
// update the column with the changes
col.update(colMeta);
}
}
/**
* Same as {@link #connectForeignKeys(Map, Database, Properties, Pattern, Pattern)},
* but uses XML-based metadata
*
* @param tableMeta
* @param tables
* @param remoteTables
*/
public void connect(TableMeta tableMeta, Map tables, Map remoteTables) {
for (TableColumnMeta colMeta : tableMeta.getColumns()) {
TableColumn col = getColumn(colMeta.getName());
// go thru the new foreign key defs and associate them with our columns
for (ForeignKeyMeta fk : colMeta.getForeignKeys()) {
Table parent = fk.getRemoteSchema() == null ? tables.get(fk.getTableName())
: remoteTables.get(fk.getRemoteSchema() + '.' + fk.getTableName());
if (parent != null) {
TableColumn parentColumn = parent.getColumn(fk.getColumnName());
if (parentColumn == null) {
logger.warning(parent.getName() + '.' + fk.getColumnName() + " doesn't exist");
} else {
/**
* Merely instantiating a foreign key constraint ties it
* into its parent and child columns (& therefore their tables)
*/
new ForeignKeyConstraint(parentColumn, col) {
@Override
public String getName() {
return "Defined in XML";
}
};
}
} else {
logger.warning("Undefined table '" + fk.getTableName() + "' referenced by '" + getName() + '.' + col.getName() + '\'');
}
}
}
}
@Override
public String toString() {
return getName();
}
/**
* Returns true
if this table has no relationships
*
* @param withImpliedRelationships boolean
* @return boolean
*/
public boolean isOrphan(boolean withImpliedRelationships) {
if (withImpliedRelationships)
return getMaxParents() == 0 && getMaxChildren() == 0;
for (TableColumn column : columns.values()) {
for (TableColumn parentColumn : column.getParents()) {
if (!column.getParentConstraint(parentColumn).isImplied())
return false;
}
for (TableColumn childColumn : column.getChildren()) {
if (!column.getChildConstraint(childColumn).isImplied())
return false;
}
}
return true;
}
/**
* Compare this table to another table.
* Results are based on 1: identity, 2: table name, 3: schema name
*
* This implementation was put in place to deal with analyzing multiple
* schemas that contain identically named tables.
*
* @see {@link Comparable#compareTo(Object)}
*/
public int compareTo(Table other) {
if (other == this) // fast way out
return 0;
int rc = getName().compareToIgnoreCase(other.getName());
if (rc == 0) {
// should only get here if we're dealing with cross-schema references (rare)
String ours = getSchema();
String theirs = other.getSchema();
if (ours != null && theirs != null)
rc = ours.compareToIgnoreCase(theirs);
else if (ours == null)
rc = -1;
else
rc = 1;
}
return rc;
}
/**
* Implementation of {@link Comparator} that sorts {@link TableColumn}s
* by {@link TableColumn#getId() ID} (ignored if null
)
* followed by {@link TableColumn#getName() Name}.
*/
private static class ByColumnIdComparator implements Comparator {
public int compare(TableColumn column1, TableColumn column2) {
if (column1.getId() == null || column2.getId() == null)
return column1.getName().compareToIgnoreCase(column2.getName());
if (column1.getId() instanceof Number)
return ((Number)column1.getId()).intValue() - ((Number)column2.getId()).intValue();
return column1.getId().toString().compareToIgnoreCase(column2.getId().toString());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy