Ayelet
Ayelet

Reputation: 31

'Schema-validation: missing table' with custom SchemaFilterProvider after upgrading to Spring Boot 3 (Hibernate 6)

When upgrading to Spring Boot 3 and Hibernate 6, it seems like property hibernate.hbm2ddl.schema_filter_provider doesn't work properly. I have a custom implementation of SchemaFilterProvider that excludes my_table from validation, and I set the hibernate.hbm2ddl.schema_filter_provider property to use it. I get this error: org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [my_table]

After debugging it, I found that DefaultSchemaFilter.INSTANCE is in use, and the above property is not being considered:

Validation is done here:

    protected void validateTables(
            Metadata metadata,
            DatabaseInformation databaseInformation,
            ExecutionOptions options,
            ContributableMatcher contributableInclusionFilter,
            Dialect dialect, Namespace namespace) {

        final NameSpaceTablesInformation tables = databaseInformation.getTablesInformation( namespace );
        for ( Table table : namespace.getTables() ) {
            if ( options.getSchemaFilter().includeTable( table )
                    && table.isPhysicalTable()
                    && contributableInclusionFilter.matches( table ) ) {
                validateTable(
                        table,
                        tables.getTableInformation( table ),
                        metadata,
                        options,
                        dialect
                );
            }
        }
    }

and options is built here:

    public static ExecutionOptions buildExecutionOptions(
            final Map<String,Object> configurationValues,
            final ExceptionHandler exceptionHandler) {
        return buildExecutionOptions(
                configurationValues,
                DefaultSchemaFilter.INSTANCE,
                exceptionHandler
        );
    }

Meaning DefaultSchemaFilter.INSTANCE will always be in use.

There's an open bug to Hibernate with no response: https://hibernate.atlassian.net/browse/HBX-2476

How should I exclude my_table from validation?

Upvotes: 3

Views: 734

Answers (1)

Pavel D.
Pavel D.

Reputation: 281

It is almost a year now since https://hibernate.atlassian.net/browse/HBX-2476 was opened and it is still not resolved. Going to share a nasty workaround that works at least for me, until the bug is fixed.

A temporary workaround

Using reflection, set the value of the (static final) DefaultSchemaFilter.INSTANCE to reference an instance of the custom SchemaFilter that worked with earlier Hibernate versions. This way when Hibernate is going to use the "hard coded" DefaultSchemaFilter.INSTANCE as filter, it is actually going to use our custom filter implementation. Note that the custom implementation extends DefaultSchemaFilter.

For example, assume we have this custom implementation that skips validation of PostgreSQL temp tables:

public class CustomSchemaFilter extends DefaultSchemaFilter {

    public static final CustomSchemaFilter INSTANCE = new CustomSchemaFilter();

    @Override
    public boolean includeTable( Table table ) {
        return !"pg_temp".equals(table.getSchema());
    }
}

We need to set DefaultSchemaFilter.INSTANCE = CustomSchemaFilter.INSTANCE.

The exact implementation of the reflection code that achieves this depends on Java version. For Java 17, based on https://stackoverflow.com/a/56043252/9061851, I used this:

try {
    Field defaultSchemaFilterInstanceField = DefaultSchemaFilter.class.getDeclaredField("INSTANCE");
    MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
    VarHandle modifiers = lookup.findVarHandle(Field.class, "modifiers", int.class);
    modifiers.set(defaultSchemaFilterInstanceField, defaultSchemaFilterInstanceField.getModifiers() & ~Modifier.FINAL);
    defaultSchemaFilterInstanceField.setAccessible(true);
    defaultSchemaFilterInstanceField.set(null, CustomSchemaFilter.INSTANCE);
} catch (NoSuchFieldException | IllegalAccessException e) {
    throw new RuntimeException(e);
}

However this requires adding --add-opens=java.base/java.lang.reflect=ALL-UNNAMED to the JVM run parameters.

This piece of code should be called before Hibernate runs its validations. The simplest option is to call it from main() before loading Spring Boot.
Reminder: this is just a temporary workaround.

Upvotes: 0

Related Questions