### Eclipse Workspace Patch 1.0 #P hibernateext.tools Index: src/java/org/hibernate/cfg/reveng/dialect/MetaDataDialect.java =================================================================== --- src/java/org/hibernate/cfg/reveng/dialect/MetaDataDialect.java (revision 16604) +++ src/java/org/hibernate/cfg/reveng/dialect/MetaDataDialect.java (working copy) @@ -84,6 +84,16 @@ Iterator getExportedKeys(String catalog, String schema, String table); /** + * Return iterator over the imported foreign keys that mathces catalog, schema and table + * + * @param catalog name or null + * @param schema name or null + * @param table name or null + * @return iterator with map elements that has "TABLE_NAME", "TABLE_SCHEMA", "TABLE_CAT", "FKTABLE_CAT", "FKTABLE_SCHEM", "FKTABLE_NAME", "FK_NAME", "KEY_SEQ" + */ + Iterator getImportedKeys(String catalog, String schema, String table); + + /** * Does this name need quoting * * @param name Index: src/java/org/hibernate/cfg/reveng/dialect/CachedMetaDataDialect.java =================================================================== --- src/java/org/hibernate/cfg/reveng/dialect/CachedMetaDataDialect.java (revision 16604) +++ src/java/org/hibernate/cfg/reveng/dialect/CachedMetaDataDialect.java (working copy) @@ -67,6 +67,12 @@ } } + public Iterator getImportedKeys(final String catalog, final String schema, + final String table) { + // for CachedMetaDataDialect -> get keys via imported or exported - use the same way + return getExportedKeys(catalog, schema, table); + } + public Iterator getIndexInfo(String catalog, String schema, String table) { StringKey sk = new StringKey(new String[] { catalog, schema, table }); List cached = (List) cachedIndexInfo.get( sk ); Index: src/java/org/hibernate/cfg/reveng/JDBCReader.java =================================================================== --- src/java/org/hibernate/cfg/reveng/JDBCReader.java (revision 16604) +++ src/java/org/hibernate/cfg/reveng/JDBCReader.java (working copy) @@ -89,8 +89,7 @@ } } - tables = foundTables.iterator(); //dbs.iterateTables(); - Map oneToManyCandidates = resolveForeignKeys( dbs, tables, progress ); + Map oneToManyCandidates = resolveForeignKeys( dbs, foundTables, progress ); dbs.setOneToManyCandidates(oneToManyCandidates); @@ -100,30 +99,50 @@ revengStrategy.close(); } } - + /** * Iterates the tables and find all the foreignkeys that refers to something that is available inside the DatabaseCollector. * @param dbs - * @param progress + * @param foundTables * @param tables * @return */ - private Map resolveForeignKeys(DatabaseCollector dbs, Iterator tables, ProgressListener progress) { - List fks = new ArrayList(); - while ( tables.hasNext() ) { - Table table = (Table) tables.next(); - // Done here after the basic process of collections as we might not have touched + private Map resolveForeignKeys(DatabaseCollector dbs, List foundTables, ProgressListener progress) { + // Possible 2 different strategies to get foreign keys: + // 1 - old: resolveForeignKeys -> processForeignKeys - get foreign keys via getMetaDataDialect().getExportedKeys(...) + // 2 - new: resolveForeignKeys -> processForeignKeysStage1 - get foreign keys via getMetaDataDialect().getImportedKeys(...) + // It is possible to check that for MySQL getImportedKeys performance O(N) steps, whereas getExportedKeys O(N^2) steps, + // the main reason - internal MySQL structure. So here this is a way to get real performance growth. + // For other RDMS (PostgreeSQL & HSQL where tested) performance time for resolveForeignKeys version 1 & resolveForeignKeys version 2 + // differ inessential, resolveForeignKeys version 2 is a little bit quicker. + HashSet processTables = new HashSet(); + Iterator it = dbs.iterateTables(); + while (it.hasNext()) { + processTables.add(it.next()); + } + processTables.addAll(foundTables); + // + Map fksMap = new HashMap(); + Iterator tables = processTables.iterator(); + while (tables.hasNext()) { + Table table = (Table)tables.next(); + // Done here after the basic process of collections as we might not have touched // all referenced tables (this ensure the columns are the same instances througout the basic JDBC derived model. - // after this stage it should be "ok" to divert from keeping columns in sync as it can be required if the same - //column is used with different aliases in the ORM mapping. - ForeignKeysInfo foreignKeys = processForeignKeys(dbs, table, progress); - fks.add( foreignKeys ); + // after this stage it should be "ok" to divert from keeping columns in sync as it can be required if the same + // column is used with different aliases in the ORM mapping. + processForeignKeysStage1(fksMap, dbs, table, progress); } - - Map oneToManyCandidates = new HashMap(); - for (Iterator iter = fks.iterator(); iter.hasNext();) { + tables = processTables.iterator(); + while (tables.hasNext()) { + Table table = (Table) tables.next(); + ForeignKeysInfo foreignKeys = (ForeignKeysInfo) fksMap.get(table); + foreignKeys = processForeignKeysStage2(foreignKeys, dbs, table, progress); + fksMap.put(table, foreignKeys); + } + Map oneToManyCandidates = new HashMap(); + for (Iterator iter = fksMap.values().iterator(); iter.hasNext();) { ForeignKeysInfo element = (ForeignKeysInfo) iter.next(); - Map map = element.process( revengStrategy ); // the actual foreignkey is created here. + Map map = element.process(revengStrategy); // the actual foreignkey is created here. mergeMultiMap( oneToManyCandidates, map ); } return oneToManyCandidates; @@ -164,169 +183,171 @@ return oneToManyCandidates; } } - - protected ForeignKeysInfo processForeignKeys(DatabaseCollector dbs, Table referencedTable, ProgressListener progress) throws JDBCBinderException { - // foreign key name to list of columns - Map dependentColumns = new HashMap(); - // foreign key name to Table - Map dependentTables = new HashMap(); - Map referencedColumns = new HashMap(); - + + protected void processForeignKeysStage1(Map fksMap, DatabaseCollector dbs, Table referencedTable, ProgressListener progress) throws JDBCBinderException { + + Map dependentColumns = null; + Map dependentTables = null; + Map referencedColumns = null; short bogusFkName = 0; - // first get all the relationships dictated by the database schema - - Iterator exportedKeyIterator = null; - - log.debug("Calling getExportedKeys on " + referencedTable); - progress.startSubTask("Finding exported foreignkeys on " + referencedTable.getName()); + Iterator importedKeyIterator = null; + log.debug("Calling getImportedKeys on " + referencedTable); + progress.startSubTask("Finding imported foreignkeys on " + referencedTable.getName()); try { - Map exportedKeyRs = null; - exportedKeyIterator = getMetaDataDialect().getExportedKeys(getCatalogForDBLookup(referencedTable.getCatalog()), getSchemaForDBLookup(referencedTable.getSchema()), referencedTable.getName() ); + Map importedKeyRs = null; + importedKeyIterator = getMetaDataDialect().getImportedKeys(getCatalogForDBLookup(referencedTable.getCatalog()), getSchemaForDBLookup(referencedTable.getSchema()), referencedTable.getName() ); try { - while (exportedKeyIterator.hasNext() ) { - exportedKeyRs = (Map) exportedKeyIterator.next(); - String fkCatalog = getCatalogForModel((String) exportedKeyRs.get("FKTABLE_CAT")); - String fkSchema = getSchemaForModel((String) exportedKeyRs.get("FKTABLE_SCHEM")); - String fkTableName = (String) exportedKeyRs.get("FKTABLE_NAME"); - String fkColumnName = (String) exportedKeyRs.get("FKCOLUMN_NAME"); - String pkColumnName = (String) exportedKeyRs.get("PKCOLUMN_NAME"); - String fkName = (String) exportedKeyRs.get("FK_NAME"); - short keySeq = ((Short)exportedKeyRs.get("KEY_SEQ")).shortValue(); - - Table fkTable = dbs.getTable(fkSchema, fkCatalog, fkTableName); - if(fkTable==null) { + while (importedKeyIterator.hasNext()) { + importedKeyRs = (Map)importedKeyIterator.next(); + String fkCatalog = getCatalogForModel((String) importedKeyRs.get("FKTABLE_CAT")); + String fkSchema = getSchemaForModel((String) importedKeyRs.get("FKTABLE_SCHEM")); + String fkTableName = (String) importedKeyRs.get("FKTABLE_NAME"); + String fkColumnName = (String) importedKeyRs.get("FKCOLUMN_NAME"); + String fkName = (String) importedKeyRs.get("FK_NAME"); + String pkCatalog = getCatalogForModel((String) importedKeyRs.get("PKTABLE_CAT")); + String pkSchema = getSchemaForModel((String) importedKeyRs.get("PKTABLE_SCHEM")); + String pkTableName = (String) importedKeyRs.get("PKTABLE_NAME"); + String pkColumnName = (String) importedKeyRs.get("PKCOLUMN_NAME"); + short keySeq = ((Short)importedKeyRs.get("KEY_SEQ")).shortValue(); + Table fkTable = dbs.getTable(quote(fkSchema), quote(fkCatalog), quote(fkTableName)); + if (fkTable == null) { // filter out stuff we don't have tables for! log.debug("Foreign key " + fkName + " references unknown or filtered table " + Table.qualify(fkCatalog, fkSchema, fkTableName) ); continue; - } else { + } + else { log.debug("Foreign key " + fkName); } - + Table pkTable = dbs.getTable(quote(pkSchema), quote(pkCatalog), quote(pkTableName)); + if (pkTable == null) { + // filter out stuff we don't have tables for! + log.debug("Foreign key " + fkName + " references unknown or filtered table " + Table.qualify(pkCatalog, pkSchema, pkTableName) ); + continue; + } + else { + log.debug("Foreign key " + fkName); + } + ForeignKeysInfo foreignKeys = (ForeignKeysInfo)fksMap.get(pkTable); + dependentColumns = foreignKeys == null ? new HashMap() : foreignKeys.dependentColumns; + dependentTables = foreignKeys == null ? new HashMap() : foreignKeys.dependentTables; + referencedColumns = foreignKeys == null ? new HashMap() : foreignKeys.referencedColumns; + if (foreignKeys == null) { + foreignKeys = new ForeignKeysInfo(pkTable, dependentTables, dependentColumns, referencedColumns); + fksMap.put(pkTable, foreignKeys); + } // TODO: if there is a relation to a column which is not a pk // then handle it as a property-ref - if (keySeq == 0) { bogusFkName++; } - if (fkName == null) { // somehow reuse hibernates name generator ? fkName = Short.toString(bogusFkName); } //Table fkTable = mappings.addTable(fkSchema, fkCatalog, fkTableName, null, false); - - List depColumns = (List) dependentColumns.get(fkName); if (depColumns == null) { depColumns = new ArrayList(); dependentColumns.put(fkName,depColumns); dependentTables.put(fkName, fkTable); - } + } else { Object previousTable = dependentTables.get(fkName); if(fkTable != previousTable) { throw new JDBCBinderException("Foreign key name (" + fkName + ") mapped to different tables! previous: " + previousTable + " current:" + fkTable); } } - Column column = new Column(fkColumnName); Column existingColumn = fkTable.getColumn(column); column = existingColumn==null ? column : existingColumn; - depColumns.add(column); - List primColumns = (List) referencedColumns.get(fkName); if (primColumns == null) { primColumns = new ArrayList(); - referencedColumns.put(fkName,primColumns); - } - + referencedColumns.put(fkName,primColumns); + } Column refColumn = new Column(pkColumnName); existingColumn = referencedTable.getColumn(refColumn); refColumn = existingColumn==null?refColumn:existingColumn; - primColumns.add(refColumn); - } - } + } finally { try { - if(exportedKeyIterator!=null) { - getMetaDataDialect().close(exportedKeyIterator); + if(importedKeyIterator!=null) { + getMetaDataDialect().close(importedKeyIterator); } } catch(JDBCException se) { log.warn("Exception while closing result set for foreign key meta data",se); } } - } catch(JDBCException se) { + } catch (JDBCException se) { //throw sec.convert(se, "Exception while reading foreign keys for " + referencedTable, null); log.warn("Exception while reading foreign keys for " + referencedTable + " [" + se.toString() + "]", se); // sybase (and possibly others has issues with exportedkeys) see HBX-411 // we continue after this to allow user provided keys to be added. } - + + } + + protected ForeignKeysInfo processForeignKeysStage2(ForeignKeysInfo foreignKeys, DatabaseCollector dbs, Table referencedTable, ProgressListener progress) throws JDBCBinderException { + + Map dependentColumns = foreignKeys == null ? new HashMap() : foreignKeys.dependentColumns; + Map dependentTables = foreignKeys == null ? new HashMap() : foreignKeys.dependentTables; + Map referencedColumns = foreignKeys == null ? new HashMap() : foreignKeys.referencedColumns; + List userForeignKeys = revengStrategy.getForeignKeys(TableIdentifier.create(referencedTable)); - if(userForeignKeys!=null) { + if (userForeignKeys != null) { Iterator iterator = userForeignKeys.iterator(); - while ( iterator.hasNext() ) { + while (iterator.hasNext()) { ForeignKey element = (ForeignKey) iterator.next(); - - if(!equalTable(referencedTable, element.getReferencedTable() ) ) { + if (!equalTable(referencedTable, element.getReferencedTable())) { log.debug("Referenced table " + element.getReferencedTable().getName() + " is not " + referencedTable + ". Ignoring userdefined foreign key " + element ); continue; // skip non related foreign keys } - - String userfkName = element.getName(); + String userfkName = element.getName(); Table userfkTable = element.getTable(); - List userColumns = element.getColumns(); List userrefColumns = element.getReferencedColumns(); - - Table deptable = (Table) dependentTables.get(userfkName); - if(deptable!=null) { // foreign key already defined!? + Table deptable = (Table)dependentTables.get(userfkName); + if (deptable != null) { // foreign key already defined!? throw new MappingException("Foreign key " + userfkName + " already defined in the database!"); } - - deptable = dbs.getTable(userfkTable.getSchema(), userfkTable.getCatalog(), userfkTable.getName() ); - if(deptable==null) { + deptable = dbs.getTable(quote(userfkTable.getSchema()), quote(userfkTable.getCatalog()), quote(userfkTable.getName()) ); + if (deptable == null) { // filter out stuff we don't have tables for! log.debug("User defined foreign key " + userfkName + " references unknown or filtered table " + TableIdentifier.create(userfkTable) ); - continue; + continue; } - dependentTables.put(userfkName, deptable); - - List depColumns = new ArrayList(userColumns.size() ); + List depColumns = new ArrayList(userColumns.size()); Iterator colIterator = userColumns.iterator(); - while(colIterator.hasNext() ) { - Column jdbcColumn = (Column) colIterator.next(); - Column column = new Column(jdbcColumn.getName() ); + while (colIterator.hasNext()) { + Column jdbcColumn = (Column)colIterator.next(); + Column column = new Column(jdbcColumn.getName()); Column existingColumn = deptable.getColumn(column); column = existingColumn==null ? column : existingColumn; depColumns.add(column); } - - List refColumns = new ArrayList(userrefColumns.size() ); + List refColumns = new ArrayList(userrefColumns.size()); colIterator = userrefColumns.iterator(); - while(colIterator.hasNext() ) { + while (colIterator.hasNext()) { Column jdbcColumn = (Column) colIterator.next(); - Column column = new Column(jdbcColumn.getName() ); + Column column = new Column(jdbcColumn.getName()); Column existingColumn = referencedTable.getColumn(column); column = existingColumn==null ? column : existingColumn; refColumns.add(column); } - referencedColumns.put(userfkName, refColumns ); dependentColumns.put(userfkName, depColumns ); } } - - - return new ForeignKeysInfo(referencedTable, dependentTables, dependentColumns, referencedColumns); - - } - + if (foreignKeys == null) { + return new ForeignKeysInfo(referencedTable, dependentTables, dependentColumns, referencedColumns); + } + return foreignKeys; + } /** * @param dbs @@ -532,7 +553,7 @@ String comment = (String) tableRs.get("REMARKS"); String tableType = (String) tableRs.get("TABLE_TYPE"); - if(dbs.getTable(schemaName, catalogName, tableName)!=null) { + if(dbs.getTable(quote(schemaName), quote(catalogName), quote(tableName))!=null) { log.debug("Ignoring " + tableName + " since it has already been processed"); continue; } else { @@ -549,7 +570,7 @@ } log.debug("Adding table " + tableName + " of type " + tableType); progress.startSubTask("Found " + tableName); - Table table = dbs.addTable(quote(getSchemaForModel(schemaName)), getCatalogForModel(catalogName), quote(tableName)); + Table table = dbs.addTable(quote(getSchemaForModel(schemaName)), quote(getCatalogForModel(catalogName)), quote(tableName)); table.setComment(comment); if(tableType.equals("TABLE")) { hasIndices.add(table); Index: src/java/org/hibernate/cfg/reveng/dialect/JDBCMetaDataDialect.java =================================================================== --- src/java/org/hibernate/cfg/reveng/dialect/JDBCMetaDataDialect.java (revision 16604) +++ src/java/org/hibernate/cfg/reveng/dialect/JDBCMetaDataDialect.java (working copy) @@ -201,6 +201,32 @@ throw getSQLExceptionConverter().convert(e, "Error while reading exported keys meta data for " + Table.qualify(xcatalog, xschema, xtable), null); } } + + public Iterator getImportedKeys(final String xcatalog, final String xschema, final String xtable) { + try { + final String catalog = caseForSearch( xcatalog ); + final String schema = caseForSearch( xschema ); + final String table = caseForSearch( xtable ); + + log.debug("getImportedKeys(" + catalog + "." + schema + "." + table + ")"); + ResultSet tableRs = getMetaData().getImportedKeys(catalog, schema, table); + + return new ResultSetIterator(tableRs, getSQLExceptionConverter()) { + + Map element = new HashMap(); + protected Object convertRow(ResultSet rs) throws SQLException { + element.clear(); + putExportedKeysPart( element, rs ); + return element; + } + protected Throwable handleSQLException(SQLException e) { + throw getSQLExceptionConverter().convert(e, "Error while reading exported keys meta data for " + Table.qualify(catalog, schema, table), null); + } + }; + } catch (SQLException e) { + throw getSQLExceptionConverter().convert(e, "Error while reading exported keys meta data for " + Table.qualify(xcatalog, xschema, xtable), null); + } + } protected void putExportedKeysPart(Map element, ResultSet rs) throws SQLException { element.put( "PKTABLE_NAME", rs.getString("PKTABLE_NAME")); Index: src/test/org/hibernate/tool/hbm2x/CachedMetaDataTest.java =================================================================== --- src/test/org/hibernate/tool/hbm2x/CachedMetaDataTest.java (revision 16604) +++ src/test/org/hibernate/tool/hbm2x/CachedMetaDataTest.java (working copy) @@ -68,6 +68,14 @@ } } + public Iterator getImportedKeys(String catalog, String schema, String table) { + if(failOnDelegateAccess) { + throw new IllegalStateException("delegate not accessible"); + } else { + return delegate.getImportedKeys(catalog, schema, table); + } + } + public Iterator getIndexInfo(String catalog, String schema, String table) { if(failOnDelegateAccess) { throw new IllegalStateException("delegate not accessible"); Index: src/java/org/hibernate/cfg/reveng/dialect/OracleMetaDataDialect.java =================================================================== --- src/java/org/hibernate/cfg/reveng/dialect/OracleMetaDataDialect.java (revision 16604) +++ src/java/org/hibernate/cfg/reveng/dialect/OracleMetaDataDialect.java (working copy) @@ -440,6 +440,12 @@ + Table.qualify(catalog, schema, table), null); } } + + public Iterator getImportedKeys(final String catalog, final String schema, + final String table) { + // for Oracle -> get keys via imported or exported - use the same way + return getExportedKeys(catalog, schema, table); + } public void close() { try {