I am developing a web based application in Grails. I have come across a situation where I would like to try and suppress GORM from creating a foreign key constraint on a fie
For those struggling with this problem using Grails 3 with gorm-hibernate5, I found a solution based on Graeme's comment on grails-data-mapping #880.
I implemented a custom SchemaManagementTool and added it to the application configuration:
hibernate.schema_management_tool = CustomSchemaManagementTool
The Hibernate SchemaManagementTool ultimately delegates the raw SQL commands to a GenerationTarget (typically GenerationTargetToDatabase), so our goal is to provide our own GenerationTarget.
This would be easiest if we could override HibernateSchemaManagementTool.buildGenerationTargets, but this is unfortunately not exposed. Instead, we need to develop our own SchemaCreator and SchemaDropper and return them in the CustomSchemaManagementTool:
class CustomSchemaManagementTool extends HibernateSchemaManagementTool {
@Override
SchemaCreator getSchemaCreator(Map options) {
return new CustomSchemaCreator(this, getSchemaFilterProvider(options).getCreateFilter())
}
@Override
SchemaDropper getSchemaDropper(Map options) {
return new CustomSchemaDropper(this, getSchemaFilterProvider(options).getDropFilter())
}
// We unfortunately copy this private method from HibernateSchemaManagementTool
private SchemaFilterProvider getSchemaFilterProvider(Map options) {
final Object configuredOption = (options == null) ? null : options.get(AvailableSettings.HBM2DDL_FILTER_PROVIDER)
return serviceRegistry.getService(StrategySelector.class).resolveDefaultableStrategy(
SchemaFilterProvider.class,
configuredOption,
DefaultSchemaFilterProvider.INSTANCE
)
}
}
For the SchemaCreator and SchemaDropper implementation, we can override doCreation and doDrop respectively. These are essentially copied from the Hibernate implementations, but with a CustomGenerationTarget instead of GenerationTargetToDatabase:
class CustomSchemaCreator extends SchemaCreatorImpl {
private final HibernateSchemaManagementTool tool
CustomSchemaCreator(HibernateSchemaManagementTool tool, SchemaFilter schemaFilter) {
super(tool, schemaFilter)
this.tool = tool
}
@Override
void doCreation(Metadata metadata, ExecutionOptions options, SourceDescriptor sourceDescriptor, TargetDescriptor targetDescriptor) {
final JdbcContext jdbcContext = tool.resolveJdbcContext( options.getConfigurationValues() )
final GenerationTarget[] targets = new GenerationTarget[ targetDescriptor.getTargetTypes().size() ]
targets[0] = new CustomGenerationTarget(tool.getDdlTransactionIsolator(jdbcContext), true)
super.doCreation(metadata, jdbcContext.getDialect(), options, sourceDescriptor, targets)
}
}
class CustomSchemaDropper extends SchemaDropperImpl {
private final HibernateSchemaManagementTool tool
CustomSchemaDropper(HibernateSchemaManagementTool tool, SchemaFilter schemaFilter) {
super(tool, schemaFilter)
this.tool = tool
}
@Override
void doDrop(Metadata metadata, ExecutionOptions options, SourceDescriptor sourceDescriptor, TargetDescriptor targetDescriptor) {
final JdbcContext jdbcContext = tool.resolveJdbcContext( options.getConfigurationValues() )
final GenerationTarget[] targets = new GenerationTarget[ targetDescriptor.getTargetTypes().size() ]
targets[0] = new CustomGenerationTarget(tool.getDdlTransactionIsolator(jdbcContext), true)
super.doDrop(metadata, options, jdbcContext.getDialect(), sourceDescriptor, targets)
}
}
In this case I use the same CustomGenerationTarget for both create and drop, but you could easily split this up into different classes. Now we finally get the payoff by extending GenerationTargetToDatabase and overriding the accept method. By only calling super.accept on SQL statements to keep, you can filter out undesired DDL statements.
class CustomGenerationTarget extends GenerationTargetToDatabase {
CustomGenerationTarget(DdlTransactionIsolator ddlTransactionIsolator, boolean releaseAfterUse) {
super(ddlTransactionIsolator, releaseAfterUse)
}
@Override
void accept(String command) {
if (shouldAccept(command))
super.accept(command)
}
boolean shouldAccept(String command) {
// Custom filtering logic here, e.g.:
if (command =~ /references legacy\.xyz/)
return false
return true
}
}
It's not the most elegant solution, but you can get the job done.
I also tried providing my own SchemaFilterProvider (and a custom SchemaFilter). This unfortunately only allows filtering of tables/namespaces - not foreign keys.