HHH-18221 Fix for Incomplete list of existing foreign keys - DatabaseMetaData.crossReferences(...) not used

This commit is contained in:
Vladimír Kuruc 2024-06-03 14:30:56 +02:00 committed by Christian Beikov
parent 6542c5604a
commit a8dde4d40f
4 changed files with 195 additions and 42 deletions

View File

@ -573,6 +573,26 @@ public class InformixDialect extends Dialect {
return BeforeUseAction.CREATE; return BeforeUseAction.CREATE;
} }
@Override
public String[] getCreateSchemaCommand(String schemaName) {
return new String[] { "create schema authorization " + schemaName };
}
@Override
public String[] getDropSchemaCommand(String schemaName) {
return new String[] { "" };
}
@Override
public boolean useCrossReferenceForeignKeys(){
return true;
}
@Override
public String getCrossReferenceParentTableFilter(){
return "%";
}
@Override @Override
public UniqueDelegate getUniqueDelegate() { public UniqueDelegate getUniqueDelegate() {
return uniqueDelegate; return uniqueDelegate;

View File

@ -2612,6 +2612,23 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
+ " " + foreignKeyDefinition; + " " + foreignKeyDefinition;
} }
/**
* Does the dialect also need cross-references to get a complete
* list of foreign keys?
*/
public boolean useCrossReferenceForeignKeys(){
return false;
}
/**
* Some dialects require a not null primaryTable filter.
* Sometimes a wildcard entry is sufficient for the like condition.
* @return
*/
public String getCrossReferenceParentTableFilter(){
return null;
}
/** /**
* The syntax used to add a primary key constraint to a table. * The syntax used to add a primary key constraint to a table.
* *

View File

@ -1264,6 +1264,92 @@ public abstract class AbstractInformationExtractorImpl implements InformationExt
ExtractionContext.ResultSetProcessor<T> processor ExtractionContext.ResultSetProcessor<T> processor
) throws SQLException; ) throws SQLException;
/**
* Must do the following:
* <ol>
* <li>
* obtain a {@link ResultSet} containing a row for each foreign key
* column making up a foreign key for any existing
* foreignCatalog/foreignSchema/foreignTable combination as specified by
* parameters described below.
* The {@link ResultSet} must contain the following, consistent
* with the corresponding columns returned by {@link DatabaseMetaData#getCrossReference}:
* <ul>
* <li>
* column label {@link #getResultSetForeignKeyLabel} -
* foreign key name (may be null)
* </li>
* <li>
* column label {@link #getResultSetPrimaryKeyCatalogLabel} -
* primary key table catalog being imported (may be null)
* </li>
* <li>
* column label {@link #getResultSetPrimaryKeySchemaLabel} -
* primary key table schema being imported (may be null)
* </li>
* <li>
* column label {@link #getResultSetPrimaryKeyTableLabel} -
* primary key table name being imported
* </li>
* <li>
* column label {@link #getResultSetForeignKeyColumnNameLabel} -
* foreign key column name
* </li>
* <li>
* column label {@link #getResultSetPrimaryKeyColumnNameLabel} -
* primary key column name being imported
* </li>
* </ul>
* The ResultSet must be ordered by the primary key
* foreignCatalog/foreignSchema/foreignTable and column position within the key.
* </li>
* <li> execute {@code processor.process( resultSet )};</li>
* <li>
* release resources whether {@code processor.process( resultSet )}
* executes successfully or not.
* </li>
* </ol>
* <p>
* The {@code parentCatalog}, {@code parentSchema}, {@code parentTable},
* {@code foreignCatalog}, {@code foreignSchema}, {@code foreignTable}
* parameters are as specified by {@link DatabaseMetaData#getCrossReference(
* String, String, String, String, String, String)}
* and are copied here:
*
* @param parentCatalog a catalog name; must match the catalog name
* as it is stored in the database; "" retrieves those without a
* catalog; {@code null} means drop catalog name from the selection criteria
* @param parentSchema a schema name; must match the schema name as
* it is stored in the database; "" retrieves those without a schema;
* {@code null} means drop schema name from the selection criteria
* @param parentTable the name of the table that exports the key; must match
* the table name as it is stored in the database
* @param foreignCatalog a catalog name; must match the catalog name as
* it is stored in the database; "" retrieves those without a
* catalog; {@code null} means drop catalog name from the selection criteria
* @param foreignSchema a schema name; must match the schema name as it
* is stored in the database; "" retrieves those without a schema;
* {@code null} means drop schema name from the selection criteria
* @param foreignTable the name of the table that imports the key; must match
* the table name as it is stored in the database
* @param processor - the provided ResultSetProcessor.
* @param <T> - defined by {@code processor}
* @return - defined by {@code processor}
* @throws SQLException - if a database error occurs
* @see #processImportedKeysResultSet(String, String, String,
* ExtractionContext.ResultSetProcessor)
*/
protected abstract <T> T processCrossReferenceResultSet(
String parentCatalog,
String parentSchema,
String parentTable,
String foreignCatalog,
String foreignSchema,
String foreignTable,
ExtractionContext.ResultSetProcessor<T> processor
) throws SQLException;
@Override @Override
public Iterable<ForeignKeyInformation> getForeignKeys(TableInformation tableInformation) { public Iterable<ForeignKeyInformation> getForeignKeys(TableInformation tableInformation) {
final Map<Identifier, ForeignKeyBuilder> fkBuilders = new HashMap<>(); final Map<Identifier, ForeignKeyBuilder> fkBuilders = new HashMap<>();
@ -1289,13 +1375,7 @@ public abstract class AbstractInformationExtractorImpl implements InformationExt
} }
try { try {
processImportedKeysResultSet( ExtractionContext.ResultSetProcessor<Void> processor = resultSet -> {
catalogFilter,
schemaFilter,
tableInformation.getName().getTableName().getText(),
resultSet -> {
// todo : need to account for getCrossReference() as well...
while ( resultSet.next() ) { while ( resultSet.next() ) {
// IMPL NOTE : The builder is mainly used to collect the column reference mappings // IMPL NOTE : The builder is mainly used to collect the column reference mappings
final Identifier fkIdentifier = DatabaseIdentifier.toIdentifier( final Identifier fkIdentifier = DatabaseIdentifier.toIdentifier(
@ -1333,9 +1413,25 @@ public abstract class AbstractInformationExtractorImpl implements InformationExt
); );
} }
return null; return null;
} };
processImportedKeysResultSet(
catalogFilter,
schemaFilter,
tableInformation.getName().getTableName().getText(),
processor);
final Dialect dialect = extractionContext.getJdbcEnvironment().getDialect();
if (dialect.useCrossReferenceForeignKeys()) {
processCrossReferenceResultSet(
null,
null,
dialect.getCrossReferenceParentTableFilter(),
catalogFilter,
schemaFilter,
tableInformation.getName().getTableName().getText(),
processor
); );
} }
}
catch (SQLException e) { catch (SQLException e) {
throw convertSQLException( throw convertSQLException(
e, e,

View File

@ -134,6 +134,26 @@ public class InformationExtractorJdbcDatabaseMetaDataImpl extends AbstractInform
} }
} }
@Override
protected <T> T processCrossReferenceResultSet(
String parentCatalog,
String parentSchema,
String parentTable,
String foreignCatalog,
String foreignSchema,
String foreignTable,
ExtractionContext.ResultSetProcessor<T> processor) throws SQLException {
try (ResultSet resultSet = getExtractionContext().getJdbcDatabaseMetaData().getCrossReference(
parentCatalog,
parentSchema,
parentTable,
foreignCatalog,
foreignSchema,
foreignTable) ) {
return processor.process( resultSet );
}
}
protected void addColumns(TableInformation tableInformation) { protected void addColumns(TableInformation tableInformation) {
final Dialect dialect = getExtractionContext().getJdbcEnvironment().getDialect(); final Dialect dialect = getExtractionContext().getJdbcEnvironment().getDialect();