All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jumpmind.symmetric.ddl.platform.JdbcModelReader Maven / Gradle / Ivy

Go to download

This library is a fork of Apache DdlUtils. DdlUtils is a small, easy-to-use component for working with Database Definition (DDL) files.

There is a newer version: 2.5.13
Show newest version
package org.jumpmind.symmetric.ddl.platform;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jumpmind.symmetric.ddl.Platform;
import org.jumpmind.symmetric.ddl.PlatformInfo;
import org.jumpmind.symmetric.ddl.model.Column;
import org.jumpmind.symmetric.ddl.model.Database;
import org.jumpmind.symmetric.ddl.model.ForeignKey;
import org.jumpmind.symmetric.ddl.model.Index;
import org.jumpmind.symmetric.ddl.model.IndexColumn;
import org.jumpmind.symmetric.ddl.model.NonUniqueIndex;
import org.jumpmind.symmetric.ddl.model.Reference;
import org.jumpmind.symmetric.ddl.model.Table;
import org.jumpmind.symmetric.ddl.model.UniqueIndex;

/*
 * An utility class to create a Database model from a live database.
 */
public class JdbcModelReader
{
    /* The Log to which logging calls will be made. */
    private final Log _log = LogFactory.getLog(JdbcModelReader.class);

    /* The descriptors for the relevant columns in the table meta data. */
    private final List _columnsForTable;
    /* The descriptors for the relevant columns in the table column meta data. */
    private final List _columnsForColumn;
    /* The descriptors for the relevant columns in the primary key meta data. */
    private final List _columnsForPK;
    /* The descriptors for the relevant columns in the foreign key meta data. */
    private final List _columnsForFK;
    /* The descriptors for the relevant columns in the index meta data. */
    private final List _columnsForIndex;

    /* The platform that this model reader belongs to. */
    private Platform _platform;
    /* Contains default column sizes (minimum sizes that a JDBC-compliant db must support). */
    private HashMap _defaultSizes = new HashMap();
    /* The default database catalog to read. */
    private String _defaultCatalogPattern = "%";
    /* The default database schema(s) to read. */
    private String _defaultSchemaPattern = "%";
    /* The default pattern for reading all tables. */
    private String _defaultTablePattern = "%";
    /* The default pattern for reading all columns. */
    private String _defaultColumnPattern;
    /* The table types to recognize per default. */
    private String[] _defaultTableTypes = { "TABLE" };

    /*
     * Creates a new model reader instance.
     * 
     * @param platform The plaftform this builder belongs to
     */
    public JdbcModelReader(Platform platform)
    {
        _platform = platform;

        _defaultSizes.put(new Integer(Types.CHAR),          "254");
        _defaultSizes.put(new Integer(Types.VARCHAR),       "254");
        _defaultSizes.put(new Integer(Types.LONGVARCHAR),   "254");
        _defaultSizes.put(new Integer(Types.BINARY),        "254");
        _defaultSizes.put(new Integer(Types.VARBINARY),     "254");
        _defaultSizes.put(new Integer(Types.LONGVARBINARY), "254");
        _defaultSizes.put(new Integer(Types.INTEGER),       "32");
        _defaultSizes.put(new Integer(Types.BIGINT),        "64");
        _defaultSizes.put(new Integer(Types.REAL),          "7,0");
        _defaultSizes.put(new Integer(Types.FLOAT),         "15,0");
        _defaultSizes.put(new Integer(Types.DOUBLE),        "15,0");
        _defaultSizes.put(new Integer(Types.DECIMAL),       "15,15");
        _defaultSizes.put(new Integer(Types.NUMERIC),       "15,15");

        _columnsForTable  = initColumnsForTable();
        _columnsForColumn = initColumnsForColumn();
        _columnsForPK     = initColumnsForPK();
        _columnsForFK     = initColumnsForFK();
        _columnsForIndex  = initColumnsForIndex();
    }

    /*
     * Returns the platform that this model reader belongs to.
     * 
     * @return The platform
     */
    public Platform getPlatform()
    {
        return _platform;
    }
    
    /*
     * Returns the platform specific settings.
     *
     * @return The platform settings
     */
    public PlatformInfo getPlatformInfo()
    {
        return _platform.getPlatformInfo();
    }

    /*
     * Returns descriptors for the columns that shall be read from the result set when
     * reading the meta data for a table. Note that the columns are read in the order
     * defined by this list.
* Redefine this method if you want more columns or a different order. * * @return The descriptors for the result set columns */ protected List initColumnsForTable() { List result = new ArrayList(); result.add(new MetaDataColumnDescriptor("TABLE_NAME", Types.VARCHAR)); result.add(new MetaDataColumnDescriptor("TABLE_TYPE", Types.VARCHAR, "UNKNOWN")); result.add(new MetaDataColumnDescriptor("TABLE_CAT", Types.VARCHAR)); result.add(new MetaDataColumnDescriptor("TABLE_SCHEM", Types.VARCHAR)); result.add(new MetaDataColumnDescriptor("REMARKS", Types.VARCHAR)); return result; } /* * Returns descriptors for the columns that shall be read from the result set when * reading the meta data for table columns. Note that the columns are read in the order * defined by this list.
* Redefine this method if you want more columns or a different order. * * @return The map column name -> descriptor for the result set columns */ protected List initColumnsForColumn() { List result = new ArrayList(); // As suggested by Alexandre Borgoltz, we're reading the COLUMN_DEF first because Oracle // has problems otherwise (it seemingly requires a LONG column to be the first to be read) // See also DDLUTILS-29 result.add(new MetaDataColumnDescriptor("COLUMN_DEF", Types.VARCHAR)); // we're also reading the table name so that a model reader impl can filter manually result.add(new MetaDataColumnDescriptor("TABLE_NAME", Types.VARCHAR)); result.add(new MetaDataColumnDescriptor("COLUMN_NAME", Types.VARCHAR)); result.add(new MetaDataColumnDescriptor("TYPE_NAME", Types.VARCHAR)); result.add(new MetaDataColumnDescriptor("DATA_TYPE", Types.INTEGER, new Integer(java.sql.Types.OTHER))); result.add(new MetaDataColumnDescriptor("NUM_PREC_RADIX", Types.INTEGER, new Integer(10))); result.add(new MetaDataColumnDescriptor("DECIMAL_DIGITS", Types.INTEGER, new Integer(0))); result.add(new MetaDataColumnDescriptor("COLUMN_SIZE", Types.VARCHAR)); result.add(new MetaDataColumnDescriptor("IS_NULLABLE", Types.VARCHAR, "YES")); result.add(new MetaDataColumnDescriptor("IS_AUTOINCREMENT", Types.VARCHAR, "YES")); result.add(new MetaDataColumnDescriptor("REMARKS", Types.VARCHAR)); return result; } /* * Returns descriptors for the columns that shall be read from the result set when * reading the meta data for primary keys. Note that the columns are read in the order * defined by this list.
* Redefine this method if you want more columns or a different order. * * @return The map column name -> descriptor for the result set columns */ protected List initColumnsForPK() { List result = new ArrayList(); result.add(new MetaDataColumnDescriptor("COLUMN_NAME", Types.VARCHAR)); // we're also reading the table name so that a model reader impl can filter manually result.add(new MetaDataColumnDescriptor("TABLE_NAME", Types.VARCHAR)); // the name of the primary key is currently only interesting to the pk index name resolution result.add(new MetaDataColumnDescriptor("PK_NAME", Types.VARCHAR)); return result; } /* * Returns descriptors for the columns that shall be read from the result set when * reading the meta data for foreign keys originating from a table. Note that the * columns are read in the order defined by this list.
* Redefine this method if you want more columns or a different order. * * @return The map column name -> descriptor for the result set columns */ protected List initColumnsForFK() { List result = new ArrayList(); result.add(new MetaDataColumnDescriptor("PKTABLE_NAME", Types.VARCHAR)); // we're also reading the table name so that a model reader impl can filter manually result.add(new MetaDataColumnDescriptor("FKTABLE_NAME", Types.VARCHAR)); result.add(new MetaDataColumnDescriptor("KEY_SEQ", Types.TINYINT, new Short((short)0))); result.add(new MetaDataColumnDescriptor("FK_NAME", Types.VARCHAR)); result.add(new MetaDataColumnDescriptor("PKCOLUMN_NAME", Types.VARCHAR)); result.add(new MetaDataColumnDescriptor("FKCOLUMN_NAME", Types.VARCHAR)); return result; } /* * Returns descriptors for the columns that shall be read from the result set when * reading the meta data for indices. Note that the columns are read in the order * defined by this list.
* Redefine this method if you want more columns or a different order. * * @return The map column name -> descriptor for the result set columns */ protected List initColumnsForIndex() { List result = new ArrayList(); result.add(new MetaDataColumnDescriptor("INDEX_NAME", Types.VARCHAR)); // we're also reading the table name so that a model reader impl can filter manually result.add(new MetaDataColumnDescriptor("TABLE_NAME", Types.VARCHAR)); result.add(new MetaDataColumnDescriptor("NON_UNIQUE", Types.BIT, Boolean.TRUE)); result.add(new MetaDataColumnDescriptor("ORDINAL_POSITION", Types.TINYINT, new Short((short)0))); result.add(new MetaDataColumnDescriptor("COLUMN_NAME", Types.VARCHAR)); result.add(new MetaDataColumnDescriptor("TYPE", Types.TINYINT)); return result; } /* * Returns the catalog(s) in the database to read per default. * * @return The default catalog(s) */ public String getDefaultCatalogPattern() { return _defaultCatalogPattern; } /* * Sets the catalog(s) in the database to read per default. * * @param catalogPattern The catalog(s) */ public void setDefaultCatalogPattern(String catalogPattern) { _defaultCatalogPattern = catalogPattern; } /* * Returns the schema(s) in the database to read per default. * * @return The default schema(s) */ public String getDefaultSchemaPattern() { return _defaultSchemaPattern; } /* * Sets the schema(s) in the database to read per default. * * @param schemaPattern The schema(s) */ public void setDefaultSchemaPattern(String schemaPattern) { _defaultSchemaPattern = schemaPattern; } /* * Returns the default pattern to read the relevant tables from the database. * * @return The table pattern */ public String getDefaultTablePattern() { return _defaultTablePattern; } /* * Sets the default pattern to read the relevant tables from the database. * * @param tablePattern The table pattern */ public void setDefaultTablePattern(String tablePattern) { _defaultTablePattern = tablePattern; } /* * Returns the default pattern to read the relevant columns from the database. * * @return The column pattern */ public String getDefaultColumnPattern() { return _defaultColumnPattern; } /* * Sets the default pattern to read the relevant columns from the database. * * @param columnPattern The column pattern */ public void setDefaultColumnPattern(String columnPattern) { _defaultColumnPattern = columnPattern; } /* * Returns the table types to recognize per default. * * @return The default table types */ public String[] getDefaultTableTypes() { return _defaultTableTypes; } /* * Sets the table types to recognize per default. Typical types are "TABLE", "VIEW", * "SYSTEM TABLE", "GLOBAL TEMPORARY", "LOCAL TEMPORARY", "ALIAS", "SYNONYM". * * @param types The table types */ public void setDefaultTableTypes(String[] types) { _defaultTableTypes = types; } /* * Returns the descriptors for the columns to be read from the table meta data result set. * * @return The column descriptors */ protected List getColumnsForTable() { return _columnsForTable; } /* * Returns the descriptors for the columns to be read from the column meta data result set. * * @return The column descriptors */ protected List getColumnsForColumn() { return _columnsForColumn; } /* * Returns the descriptors for the columns to be read from the primary key meta data result set. * * @return The column descriptors */ protected List getColumnsForPK() { return _columnsForPK; } /* * Returns the descriptors for the columns to be read from the foreign key meta data result set. * * @return The column descriptors */ protected List getColumnsForFK() { return _columnsForFK; } /* * Returns the descriptors for the columns to be read from the index meta data result set. * * @return The column descriptors */ protected List getColumnsForIndex() { return _columnsForIndex; } /* * Reads the database model from the given connection. * * @param connection The connection * @param name The name of the resulting database; null when the default name (the catalog) * is desired which might be null itself though * @return The database model */ public Database getDatabase(Connection connection, String name) throws SQLException { return getDatabase(connection, name, null, null, null); } /* * Reads the database model from the given connection. * * @param connection The connection * @param name The name of the resulting database; null when the default name (the catalog) * is desired which might be null itself though * @param catalog The catalog to access in the database; use null for the default value * @param schema The schema to access in the database; use null for the default value * @param tableTypes The table types to process; use null or an empty list for the default ones * @return The database model */ public Database getDatabase(Connection connection, String name, String catalog, String schema, String[] tableTypes) throws SQLException { Database db = new Database(); if (name == null) { try { db.setName(connection.getCatalog()); if (catalog == null) { catalog = db.getName(); } } catch (Exception ex) { _log.info("Cannot determine the catalog name from connection.", ex); } } else { db.setName(name); } db.addTables(readTables(connection, catalog, schema, tableTypes)); // Note that we do this here instead of in readTable since platforms may redefine the // readTable method whereas it is highly unlikely that this method gets redefined if (getPlatform().isForeignKeysSorted()) { sortForeignKeys(db); } db.initialize(); return db; } /* * Reads the tables from the database metadata. * * @param catalog The catalog to acess in the database; use null for the default value * @param schemaPattern The schema(s) to acess in the database; use null for the default value * @param tableTypes The table types to process; use null or an empty list for the default ones * @return The tables */ protected Collection readTables(Connection connection, String catalog, String schemaPattern, String[] tableTypes) throws SQLException { ResultSet tableData = null; try { DatabaseMetaDataWrapper metaData = new DatabaseMetaDataWrapper(); metaData.setMetaData(connection.getMetaData()); metaData.setCatalog(catalog == null ? getDefaultCatalogPattern() : catalog); metaData.setSchemaPattern(schemaPattern == null ? getDefaultSchemaPattern() : schemaPattern); metaData.setTableTypes((tableTypes == null) || (tableTypes.length == 0) ? getDefaultTableTypes() : tableTypes); tableData = metaData.getTables(getDefaultTablePattern()); List
tables = new ArrayList
(); while (tableData.next()) { Map values = readColumns(tableData, getColumnsForTable()); Table table = readTable(connection, metaData, values); if (table != null) { tables.add(table); } } final Collator collator = Collator.getInstance(); Collections.sort(tables, new Comparator
() { public int compare(Table obj1, Table obj2) { return collator.compare(obj1.getName().toUpperCase(), obj2.getName().toUpperCase()); } }); return tables; } finally { if (tableData != null) { tableData.close(); } } } public Table readTable(Connection connection, String catalog, String schema, String tableName) throws SQLException { Table table = null; DatabaseMetaDataWrapper metaData = new DatabaseMetaDataWrapper(); metaData.setMetaData(connection.getMetaData()); metaData.setCatalog(catalog); metaData.setSchemaPattern(schema); metaData.setTableTypes(null); if (getPlatformInfo().isStoresUpperCaseInCatalog()) { tableName = tableName.toUpperCase(); } ResultSet tableData = null; try { tableData = metaData.getTables(getTableNamePattern(tableName)); while (tableData != null && tableData.next()) { Map values = readColumns(tableData, initColumnsForTable()); table = readTable(connection, metaData, values); } } finally { close(tableData); } return table; } protected void close(ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException ex) { } } } protected void close(Statement stmt) { if (stmt != null) { try { stmt.close(); } catch (SQLException ex) { } } } protected String getTableNamePattern(String tableName) { return tableName; } /* * Reads the next table from the meta data. * * @param metaData The database meta data * @param values The table metadata values as defined by {@link #getColumnsForTable()} * @return The table or null if the result set row did not contain a valid table */ protected Table readTable(Connection connection, DatabaseMetaDataWrapper metaData, Map values) throws SQLException { String tableName = (String)values.get("TABLE_NAME"); Table table = null; if ((tableName != null) && (tableName.length() > 0)) { table = new Table(); table.setName(tableName); table.setType((String)values.get("TABLE_TYPE")); table.setCatalog((String)values.get("TABLE_CAT")); table.setSchema((String)values.get("TABLE_SCHEM")); table.setDescription((String)values.get("REMARKS")); table.addColumns(readColumns(metaData, tableName)); table.addForeignKeys(readForeignKeys(connection, metaData, tableName)); table.addIndices(readIndices(connection, metaData, tableName)); Collection primaryKeys = readPrimaryKeyNames(metaData, tableName); for (Iterator it = primaryKeys.iterator(); it.hasNext();) { table.findColumn(it.next(), true).setPrimaryKey(true); } if (getPlatformInfo().isSystemIndicesReturned()) { removeSystemIndices(connection, metaData, table); } } return table; } /* * Removes system indices (generated by the database for primary and foreign keys) * from the table. * * @param metaData The database meta data * @param table The table */ protected void removeSystemIndices(Connection connection, DatabaseMetaDataWrapper metaData, Table table) throws SQLException { removeInternalPrimaryKeyIndex(connection, metaData, table); for (int fkIdx = 0; fkIdx < table.getForeignKeyCount(); fkIdx++) { removeInternalForeignKeyIndex(connection, metaData, table, table.getForeignKey(fkIdx)); } } /* * Tries to remove the internal index for the table's primary key. * * @param metaData The database meta data * @param table The table */ protected void removeInternalPrimaryKeyIndex(Connection connection, DatabaseMetaDataWrapper metaData, Table table) throws SQLException { Column[] pks = table.getPrimaryKeyColumns(); List columnNames = new ArrayList(); for (int columnIdx = 0; columnIdx < pks.length; columnIdx++) { columnNames.add(pks[columnIdx].getName()); } for (int indexIdx = 0; indexIdx < table.getIndexCount();) { Index index = table.getIndex(indexIdx); if (index.isUnique() && matches(index, columnNames) && isInternalPrimaryKeyIndex(connection, metaData, table, index)) { table.removeIndex(indexIdx); } else { indexIdx++; } } } /* * Tries to remove the internal index for the given foreign key. * * @param metaData The database meta data * @param table The table where the table is defined * @param fk The foreign key */ protected void removeInternalForeignKeyIndex(Connection connection, DatabaseMetaDataWrapper metaData, Table table, ForeignKey fk) throws SQLException { List columnNames = new ArrayList(); boolean mustBeUnique = !getPlatformInfo().isSystemForeignKeyIndicesAlwaysNonUnique(); for (int columnIdx = 0; columnIdx < fk.getReferenceCount(); columnIdx++) { String name = fk.getReference(columnIdx).getLocalColumnName(); Column localColumn = table.findColumn(name, getPlatform().isDelimitedIdentifierModeOn()); if (mustBeUnique && !localColumn.isPrimaryKey()) { mustBeUnique = false; } columnNames.add(name); } for (int indexIdx = 0; indexIdx < table.getIndexCount();) { Index index = table.getIndex(indexIdx); if ((mustBeUnique == index.isUnique()) && matches(index, columnNames) && isInternalForeignKeyIndex(connection, metaData, table, fk, index)) { fk.setAutoIndexPresent(true); table.removeIndex(indexIdx); } else { indexIdx++; } } } /* * Checks whether the given index matches the column list. * * @param index The index * @param columnsToSearchFor The names of the columns that the index should be for * @return true if the index matches the columns */ protected boolean matches(Index index, List columnsToSearchFor) { if (index.getColumnCount() != columnsToSearchFor.size()) { return false; } for (int columnIdx = 0; columnIdx < index.getColumnCount(); columnIdx++) { if (!columnsToSearchFor.get(columnIdx).equals(index.getColumn(columnIdx).getName())) { return false; } } return true; } /* * Tries to determine whether the index is the internal database-generated index * for the given table's primary key. * Note that only unique indices with the correct columns are fed to this method. * Redefine this method for specific platforms if there are better ways * to determine internal indices. * * @param metaData The database meta data * @param table The table owning the index * @param index The index to check * @return true if the index seems to be an internal primary key one */ protected boolean isInternalPrimaryKeyIndex(Connection connection, DatabaseMetaDataWrapper metaData, Table table, Index index) throws SQLException { return false; } /* * Tries to determine whether the index is the internal database-generated index * for the given foreign key. * Note that only non-unique indices with the correct columns are fed to this method. * Redefine this method for specific platforms if there are better ways * to determine internal indices. * * @param metaData The database meta data * @param table The table owning the index and foreign key * @param fk The foreign key * @param index The index to check * @return true if the index seems to be an internal primary key one */ protected boolean isInternalForeignKeyIndex(Connection connection, DatabaseMetaDataWrapper metaData, Table table, ForeignKey fk, Index index) throws SQLException { return false; } /* * Reads the column definitions for the indicated table. * * @param metaData The database meta data * @param tableName The name of the table * @return The columns */ protected Collection readColumns(DatabaseMetaDataWrapper metaData, String tableName) throws SQLException { ResultSet columnData = null; try { columnData = metaData.getColumns(getTableNamePattern(tableName), getDefaultColumnPattern()); List columns = new ArrayList(); while (columnData.next()) { Map values = readColumns(columnData, getColumnsForColumn()); columns.add(readColumn(metaData, values)); } return columns; } finally { close(columnData); } } protected Integer overrideJdbcTypeForColumn(Map values) { return null; } /* * Extracts a column definition from the result set. * * @param metaData The database meta data * @param values The column meta data values as defined by {@link #getColumnsForColumn()} * @return The column */ protected Column readColumn(DatabaseMetaDataWrapper metaData, Map values) throws SQLException { Column column = new Column(); column.setName((String) values.get("COLUMN_NAME")); String defaultValue = (String) values.get("COLUMN_DEF"); if (defaultValue != null) { column.setDefaultValue(defaultValue.trim()); } Integer jdbcType = overrideJdbcTypeForColumn(values); if (jdbcType != null) { column.setTypeCode(jdbcType); } else { column.setTypeCode((Integer) values.get("DATA_TYPE")); } column.setPrecisionRadix(((Integer) values.get("NUM_PREC_RADIX")).intValue()); String size = (String) values.get("COLUMN_SIZE"); int scale = ((Integer) values.get("DECIMAL_DIGITS")).intValue(); if (size == null) { size = (String) _defaultSizes.get(new Integer(column.getTypeCode())); } // we're setting the size after the precision and radix in case // the database prefers to return them in the size value column.setSize(size); if (scale != 0) { // if there is a scale value, set it after the size (which probably // did not contain // a scale specification) column.setScale(scale); } column.setRequired("NO".equalsIgnoreCase(((String) values.get("IS_NULLABLE")).trim())); column.setDescription((String) values.get("REMARKS")); //type name added to identify specific types such as geometry types column.setJdbcTypeName((String)values.get("TYPE_NAME")); return column; } /* * Retrieves the names of the columns that make up the primary key for a given table. * * @param metaData The database meta data * @param tableName The name of the table from which to retrieve PK information * @return The primary key column names */ protected Collection readPrimaryKeyNames(DatabaseMetaDataWrapper metaData, String tableName) throws SQLException { List pks = new ArrayList(); ResultSet pkData = null; try { pkData = metaData.getPrimaryKeys(getTableNamePattern(tableName)); while (pkData.next()) { Map values = readColumns(pkData, getColumnsForPK()); pks.add(readPrimaryKeyName(metaData, values)); } } finally { close(pkData); } return pks; } /* * Extracts a primary key name from the result set. * * @param metaData The database meta data * @param values The primary key meta data values as defined by {@link #getColumnsForPK()} * @return The primary key name */ protected String readPrimaryKeyName(DatabaseMetaDataWrapper metaData, Map values) throws SQLException { return (String)values.get("COLUMN_NAME"); } /* * Retrieves the foreign keys of the indicated table. * * @param metaData The database meta data * @param tableName The name of the table from which to retrieve FK information * @return The foreign keys */ protected Collection readForeignKeys(Connection connection, DatabaseMetaDataWrapper metaData, String tableName) throws SQLException { Map fks = new LinkedHashMap(); ResultSet fkData = null; try { fkData = metaData.getForeignKeys(tableName); while (fkData.next()) { Map values = readColumns(fkData, getColumnsForFK()); readForeignKey(metaData, values, fks); } } finally { close(fkData); } return fks.values(); } /* * Reads the next foreign key spec from the result set. * * @param metaData The database meta data * @param values The foreign key meta data as defined by {@link #getColumnsForFK()} * @param knownFks The already read foreign keys for the current table */ protected void readForeignKey(DatabaseMetaDataWrapper metaData, Map values, Map knownFks) throws SQLException { String fkName = (String)values.get("FK_NAME"); ForeignKey fk = (ForeignKey)knownFks.get(fkName); if (fk == null) { fk = new ForeignKey(fkName); fk.setForeignTableName((String)values.get("PKTABLE_NAME")); knownFks.put(fkName, fk); } Reference ref = new Reference(); ref.setForeignColumnName((String)values.get("PKCOLUMN_NAME")); ref.setLocalColumnName((String)values.get("FKCOLUMN_NAME")); if (values.containsKey("KEY_SEQ")) { ref.setSequenceValue(((Short)values.get("KEY_SEQ")).intValue()); } fk.addReference(ref); } /* * Determines the indices for the indicated table. * * @param metaData The database meta data * @param tableName The name of the table * @return The list of indices */ protected Collection readIndices(Connection connection, DatabaseMetaDataWrapper metaData, String tableName) throws SQLException { Map indices = new LinkedHashMap(); ResultSet indexData = null; try { indexData = metaData.getIndices(getTableNamePattern(tableName), false, false); while (indexData.next()) { Map values = readColumns(indexData, getColumnsForIndex()); readIndex(metaData, values, indices); } } finally { close(indexData); } return indices.values(); } /* * Reads the next index spec from the result set. * * @param metaData The database meta data * @param values The index meta data as defined by {@link #getColumnsForIndex()} * @param knownIndices The already read indices for the current table */ protected void readIndex(DatabaseMetaDataWrapper metaData, Map values, Map knownIndices) throws SQLException { Short indexType = (Short)values.get("TYPE"); // we're ignoring statistic indices if ((indexType != null) && (indexType.shortValue() == DatabaseMetaData.tableIndexStatistic)) { return; } String indexName = (String)values.get("INDEX_NAME"); if (indexName != null) { Index index = (Index)knownIndices.get(indexName); if (index == null) { if (((Boolean)values.get("NON_UNIQUE")).booleanValue()) { index = new NonUniqueIndex(); } else { index = new UniqueIndex(); } index.setName(indexName); knownIndices.put(indexName, index); } IndexColumn indexColumn = new IndexColumn(); indexColumn.setName((String)values.get("COLUMN_NAME")); if (values.containsKey("ORDINAL_POSITION")) { indexColumn.setOrdinalPosition(((Short)values.get("ORDINAL_POSITION")).intValue()); } index.addColumn(indexColumn); } } /* * Reads the indicated columns from the result set. * * @param resultSet The result set * @param columnDescriptors The dscriptors of the columns to read * @return The read values keyed by the column name */ protected Map readColumns(ResultSet resultSet, List columnDescriptors) throws SQLException { HashMap values = new HashMap(); for (Iterator it = columnDescriptors.iterator(); it.hasNext();) { MetaDataColumnDescriptor descriptor = it.next(); values.put(descriptor.getName(), descriptor.readColumn(resultSet)); } return values; } protected void determineAutoIncrementFromResultSetMetaData(Connection conn, Table table, final Column columnsToCheck[]) throws SQLException { determineAutoIncrementFromResultSetMetaData(conn, table, columnsToCheck, "."); } /* * Helper method that determines the auto increment status for the given columns via the * {@link ResultSetMetaData#isAutoIncrement(int)} method. * * Fix problems following problems: 1) identifiers that use keywords 2) * different catalog and schema 3) different catalog separator character * * @param table The table * @param columnsToCheck The columns to check (e.g. the primary key columns) */ public void determineAutoIncrementFromResultSetMetaData(Connection conn, Table table, final Column columnsToCheck[], String catalogSeparator) throws SQLException { StringBuilder query = new StringBuilder(); try { if (columnsToCheck == null || columnsToCheck.length == 0) { return; } query.append("SELECT "); for (int idx = 0; idx < columnsToCheck.length; idx++) { if (idx > 0) { query.append(","); } query.append("t."); appendIdentifier(query, columnsToCheck[idx].getName()); } query.append(" FROM "); if (table.getCatalog() != null && !table.getCatalog().trim().equals("")) { appendIdentifier(query, table.getCatalog()); query.append(catalogSeparator); } if (table.getSchema() != null && !table.getSchema().trim().equals("")) { appendIdentifier(query, table.getSchema()).append("."); } appendIdentifier(query, table.getName()).append(" t WHERE 1 = 0"); Statement stmt = null; try { stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(query.toString()); ResultSetMetaData rsMetaData = rs.getMetaData(); for (int idx = 0; idx < columnsToCheck.length; idx++) { if (rsMetaData.isAutoIncrement(idx + 1)) { columnsToCheck[idx].setAutoIncrement(true); } } } finally { if (stmt != null) { stmt.close(); } } } catch(SQLException ex) { StringBuilder msg = new StringBuilder("Failed to determine auto increment columns using this query: '" + query + "'. This is probably not harmful, but should be fixed. "); msg.append("\n"); msg.append(table.toString()); if (columnsToCheck != null) { for (Column col : columnsToCheck) { msg.append("\n"); msg.append(col.toString()); } } _log.warn(msg); } } public StringBuilder appendIdentifier(StringBuilder query, String identifier) { if (getPlatform().isDelimitedIdentifierModeOn()) { query.append(getPlatformInfo().getDelimiterToken()); } query.append(identifier); if (getPlatform().isDelimitedIdentifierModeOn()) { query.append(getPlatformInfo().getDelimiterToken()); } return query; } /* * Sorts the foreign keys in the tables of the model. * * @param model The model */ protected void sortForeignKeys(Database model) { for (int tableIdx = 0; tableIdx < model.getTableCount(); tableIdx++) { model.getTable(tableIdx).sortForeignKeys(getPlatform().isDelimitedIdentifierModeOn()); } } /* * Replaces a specific character sequence in the given text with the character sequence * whose escaped version it is. * * @param text The text * @param unescaped The unescaped string, e.g. "'" * @param escaped The escaped version, e.g. "''" * @return The resulting text */ protected String unescape(String text, String unescaped, String escaped) { String result = text; // we need special handling if the single quote is escaped via a double single quote if (result != null) { if (escaped.equals("''")) { if ((result.length() > 2) && result.startsWith("'") && result.endsWith("'")) { result = "'" + StringUtils.replace(result.substring(1, result.length() - 1), escaped, unescaped) + "'"; } else { result = StringUtils.replace(result, escaped, unescaped); } } else { result = StringUtils.replace(result, escaped, unescaped); } } return result; } /* * Tries to find the schema to which the given table belongs. * * @param connection The database connection * @param schemaPattern The schema pattern to limit the schemas to search in * @param table The table to search for * @return The schema name or null if the schema of the table * could not be found * @deprecated Will be removed once full schema support is in place */ public String determineSchemaOf(Connection connection, String schemaPattern, Table table) throws SQLException { ResultSet tableData = null; ResultSet columnData = null; try { DatabaseMetaDataWrapper metaData = new DatabaseMetaDataWrapper(); metaData.setMetaData(connection.getMetaData()); metaData.setCatalog(getDefaultCatalogPattern()); metaData.setSchemaPattern(schemaPattern == null ? getDefaultSchemaPattern() : schemaPattern); metaData.setTableTypes(getDefaultTableTypes()); String tablePattern = table.getName(); if (getPlatform().isDelimitedIdentifierModeOn()) { tablePattern = tablePattern.toUpperCase(); } tableData = metaData.getTables(tablePattern); boolean found = false; String schema = null; while (!found && tableData.next()) { Map values = readColumns(tableData, getColumnsForTable()); String tableName = (String)values.get("TABLE_NAME"); if ((tableName != null) && (tableName.length() > 0)) { schema = (String)values.get("TABLE_SCHEM"); columnData = metaData.getColumns(tableName, getDefaultColumnPattern()); found = true; while (found && columnData.next()) { values = readColumns(columnData, getColumnsForColumn()); if (table.findColumn((String)values.get("COLUMN_NAME"), getPlatform().isDelimitedIdentifierModeOn()) == null) { found = false; } } columnData.close(); columnData = null; } } return found ? schema : null; } finally { close(columnData); close(tableData); } } }