-
-
Save shawnz/8f7308741e2ff6270cc8878ac69b4f6b to your computer and use it in GitHub Desktop.
| spring.jpa.hibernate.ddl-auto = validate | |
| spring.jpa.properties.hibernate.schema_management_tool = org.shawnz.WarnValidatorSchemaManagementTool |
| package org.shawnz; | |
| import org.hibernate.boot.Metadata; | |
| import org.hibernate.boot.model.naming.Identifier; | |
| import org.hibernate.boot.model.relational.Sequence; | |
| import org.hibernate.dialect.Dialect; | |
| import org.hibernate.mapping.Column; | |
| import org.hibernate.mapping.Selectable; | |
| import org.hibernate.mapping.Table; | |
| import org.hibernate.tool.schema.extract.spi.ColumnInformation; | |
| import org.hibernate.tool.schema.extract.spi.SequenceInformation; | |
| import org.hibernate.tool.schema.extract.spi.TableInformation; | |
| import org.hibernate.tool.schema.internal.GroupedSchemaValidatorImpl; | |
| import org.hibernate.tool.schema.internal.HibernateSchemaManagementTool; | |
| import org.hibernate.tool.schema.spi.ExecutionOptions; | |
| import org.hibernate.tool.schema.spi.SchemaFilter; | |
| import org.hibernate.type.descriptor.JdbcTypeNameMapper; | |
| import org.slf4j.Logger; | |
| import org.slf4j.LoggerFactory; | |
| import java.util.Iterator; | |
| import java.util.Locale; | |
| public class WarnSchemaValidator extends GroupedSchemaValidatorImpl { | |
| private static Logger log = LoggerFactory.getLogger(WarnSchemaValidator.class); | |
| public WarnSchemaValidator(HibernateSchemaManagementTool tool, SchemaFilter validateFilter) { | |
| super(tool, validateFilter); | |
| } | |
| // from https://github.com/hibernate/hibernate-orm/blob/2bcb1b0a6d8600ba3a60eae6460dcb52bf5628e7/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java#L113 | |
| @Override | |
| protected void validateTable( | |
| Table table, | |
| TableInformation tableInformation, | |
| Metadata metadata, | |
| ExecutionOptions options, | |
| Dialect dialect) { | |
| if ( tableInformation == null ) { | |
| log.warn( | |
| String.format( | |
| "Schema-validation: missing table [%s]", | |
| table.getQualifiedTableName().toString() | |
| ) | |
| ); | |
| // don't validate columns of non-existent table | |
| return; | |
| } | |
| final Iterator selectableItr = table.getColumnIterator(); | |
| while ( selectableItr.hasNext() ) { | |
| final Selectable selectable = (Selectable) selectableItr.next(); | |
| if ( Column.class.isInstance( selectable ) ) { | |
| final Column column = (Column) selectable; | |
| final ColumnInformation existingColumn = tableInformation.getColumn( Identifier.toIdentifier( column.getQuotedName() ) ); | |
| if ( existingColumn == null ) { | |
| log.warn(String.format( | |
| "Schema-validation: missing column [%s] in table [%s]", | |
| column.getName(), | |
| table.getQualifiedTableName() | |
| ) | |
| ); | |
| // don't validate type of non-existent column | |
| continue; | |
| } | |
| validateColumnType( table, column, existingColumn, metadata, options, dialect ); | |
| } | |
| } | |
| } | |
| @Override | |
| protected void validateColumnType( | |
| Table table, | |
| Column column, | |
| ColumnInformation columnInformation, | |
| Metadata metadata, | |
| ExecutionOptions options, | |
| Dialect dialect) { | |
| boolean typesMatch = column.getSqlTypeCode( metadata ) == columnInformation.getTypeCode() | |
| || column.getSqlType( dialect, metadata ).toLowerCase(Locale.ROOT).startsWith( columnInformation.getTypeName().toLowerCase(Locale.ROOT) ); | |
| if ( !typesMatch ) { | |
| log.warn( | |
| String.format( | |
| "Schema-validation: wrong column type encountered in column [%s] in " + | |
| "table [%s]; found [%s (Types#%s)], but expecting [%s (Types#%s)]", | |
| column.getName(), | |
| table.getQualifiedTableName(), | |
| columnInformation.getTypeName().toLowerCase(Locale.ROOT), | |
| JdbcTypeNameMapper.getTypeName( columnInformation.getTypeCode() ), | |
| column.getSqlType().toLowerCase(Locale.ROOT), | |
| JdbcTypeNameMapper.getTypeName( column.getSqlTypeCode( metadata ) ) | |
| ) | |
| ); | |
| } | |
| // this is the old Hibernate check... | |
| // | |
| // but I think a better check involves checks against type code and then the type code family, not | |
| // just the type name. | |
| // | |
| // See org.hibernate.type.descriptor.sql.JdbcTypeFamilyInformation | |
| // todo : this ^^ | |
| } | |
| @Override | |
| protected void validateSequence(Sequence sequence, SequenceInformation sequenceInformation) { | |
| if ( sequenceInformation == null ) { | |
| log.warn( | |
| String.format( "Schema-validation: missing sequence [%s]", sequence.getName() ) | |
| ); | |
| // don't validate properties of non-existent sequence | |
| return; | |
| } | |
| if ( sequenceInformation.getIncrementSize() > 0 | |
| && sequence.getIncrementSize() != sequenceInformation.getIncrementSize() ) { | |
| log.warn( | |
| String.format( | |
| "Schema-validation: sequence [%s] defined inconsistent increment-size; found [%s] but expecting [%s]", | |
| sequence.getName(), | |
| sequenceInformation.getIncrementSize(), | |
| sequence.getIncrementSize() | |
| ) | |
| ); | |
| } | |
| } | |
| } |
| package org.shawnz; | |
| import org.hibernate.boot.registry.selector.spi.StrategySelector; | |
| import org.hibernate.cfg.AvailableSettings; | |
| import org.hibernate.tool.schema.internal.DefaultSchemaFilterProvider; | |
| import org.hibernate.tool.schema.internal.HibernateSchemaManagementTool; | |
| import org.hibernate.tool.schema.spi.SchemaFilterProvider; | |
| import org.hibernate.tool.schema.spi.SchemaValidator; | |
| import java.util.Map; | |
| public class WarnValidatorSchemaManagementTool extends HibernateSchemaManagementTool { | |
| @Override | |
| public SchemaValidator getSchemaValidator(Map options) { | |
| return new WarnSchemaValidator(this, getSchemaFilterProvider( options ).getValidateFilter()); | |
| } | |
| // from https://github.com/hibernate/hibernate-orm/blob/2bcb1b0a6d8600ba3a60eae6460dcb52bf5628e7/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/HibernateSchemaManagementTool.java#L95 | |
| private SchemaFilterProvider getSchemaFilterProvider(Map options) { | |
| final Object configuredOption = (options == null) | |
| ? null | |
| : options.get( AvailableSettings.HBM2DDL_FILTER_PROVIDER ); | |
| return getServiceRegistry().getService( StrategySelector.class ).resolveDefaultableStrategy( | |
| SchemaFilterProvider.class, | |
| configuredOption, | |
| DefaultSchemaFilterProvider.INSTANCE | |
| ); | |
| } | |
| } |
@shawnz thank you so much for creating this awesome solution.
Did you even consider contributing this to hibernate?
Does this have any kind of license (could you update this gist to have license header or comment something here)?
I am asking if I could copy&paste this as is into a proprietary software and do not want to violate your copyright.
Surely it is just 2 classes so I could more or less rewrite myself from memory with slight changes and my personal style, but I would prefer to simply put a link in the header of these files pointing to this gist and saying that this is e.g. Apache 2.0 or WTFPL before pushing anything related... Thanks in advance.
@hohwille Since I copied some code from hibernate to make this, those parts fall under the hibernate LGPL license. Otherwise, feel free to use for any purpose, commercial or otherwise
👍 Thanks @shawnz for the quick and friendly response.
Additionally, to see missing constraints, set the following properties:
This will output a file "update.sql" with the missing constraints in the root directory of the app.