Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,20 @@ public interface SchemaToolingSettings {
*/
String HBM2DDL_SKIP_DEFAULT_IMPORT_FILE = "hibernate.hbm2ddl.skip_default_import_file";

/// Whether {@linkplain org.hibernate.mapping.Index indexes} should be validated when
/// performing {@linkplain org.hibernate.tool.schema.Action#VALIDATE schema validation}.
/// Valid values are defined by [org.hibernate.tool.schema.internal.ConstraintValidationType].
///
/// @since 7.3
String INDEX_VALIDATION = "hibernate.tooling.schema.index_validation";

/// Whether {@linkplain org.hibernate.mapping.UniqueKey unique keys} should be validated when
/// performing {@linkplain org.hibernate.tool.schema.Action#VALIDATE schema validation}.
/// Valid values are defined by [org.hibernate.tool.schema.internal.ConstraintValidationType].
///
/// @since 7.3
String UNIQUE_KEY_VALIDATION = "hibernate.tooling.schema.unique_key_validation";

/**
* Specifies whether to automatically create also the database schema/catalog.
* The default is false.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@
*/
package org.hibernate.tool.schema.internal;

import java.util.Locale;

import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.relational.Namespace;
import org.hibernate.boot.model.relational.Sequence;
import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.internal.util.StringHelper;
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.DatabaseInformation;
import org.hibernate.tool.schema.extract.spi.IndexInformation;
import org.hibernate.tool.schema.extract.spi.SequenceInformation;
import org.hibernate.tool.schema.extract.spi.TableInformation;
import org.hibernate.tool.schema.spi.ContributableMatcher;
Expand All @@ -27,7 +29,12 @@

import org.jboss.logging.Logger;

import java.util.Objects;

import static java.util.Locale.ROOT;
import static org.hibernate.boot.model.naming.Identifier.toIdentifier;
import static org.hibernate.cfg.SchemaToolingSettings.INDEX_VALIDATION;
import static org.hibernate.cfg.SchemaToolingSettings.UNIQUE_KEY_VALIDATION;
import static org.hibernate.tool.schema.internal.ColumnDefinitions.hasMatchingType;
import static org.hibernate.tool.schema.internal.Helper.buildDatabaseInformation;

Expand Down Expand Up @@ -141,6 +148,9 @@ protected void validateTable(
validateColumnType( table, column, existingColumn, metadata, dialect );
validateColumnNullability( table, column, existingColumn );
}

validateIndexes( table, tableInformation, metadata, options, dialect );
validateUniqueKeys( table, tableInformation, metadata, options, dialect );
}

protected void validateColumnType(
Expand All @@ -156,9 +166,9 @@ protected void validateColumnType(
"table [%s]; found [%s (Types#%s)], but expecting [%s (Types#%s)]",
column.getName(),
table.getQualifiedTableName(),
columnInformation.getTypeName().toLowerCase(Locale.ROOT),
columnInformation.getTypeName().toLowerCase( ROOT),
JdbcTypeNameMapper.getTypeName( columnInformation.getTypeCode() ),
column.getSqlType( metadata ).toLowerCase(Locale.ROOT),
column.getSqlType( metadata ).toLowerCase( ROOT),
JdbcTypeNameMapper.getTypeName( column.getSqlTypeCode( metadata ) )
)
);
Expand All @@ -181,6 +191,138 @@ private void validateColumnNullability(Table table, Column column, ColumnInforma
}
}

private void validateIndexes(
Table table,
TableInformation tableInformation,
Metadata metadata,
ExecutionOptions options,
Dialect dialect) {
var validationType = ConstraintValidationType.interpret( INDEX_VALIDATION, options.getConfigurationValues() );

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe move this check up?

Suggested change
if ( validationType == ConstraintValidationType.NONE ) {
return;
}

table.getIndexes().forEach((rawName,index) -> {
assert StringHelper.isNotEmpty( rawName );
assert Objects.equals( rawName, index.getName() );
if ( validationType == ConstraintValidationType.NONE ) {
return;
}
else if ( validationType == ConstraintValidationType.NAMED ) {
if ( rawName.startsWith( "IDX" ) ) {
// this is not a great check as the user could very well
// have explicitly chosen a name that starts with this as well,
// but...
return;
}
}

var name = metadata.getDatabase().toIdentifier( rawName );
final IndexInformation indexInformation = tableInformation.getIndex( name );

if ( indexInformation == null ) {
throw new SchemaManagementException(
String.format(
ROOT,
"Missing index named `%s` on table `%s`",
name.render( dialect ),
tableInformation.getName().render()
)
);
}

var indicesMatch = true;
assert index.getSelectables().size() == index.getColumnSpan();
if ( index.getColumnSpan() != indexInformation.getIndexedColumns().size() ) {
indicesMatch = false;
}
else {
for ( int i = 0; i < index.getSelectables().size(); i++ ) {
final Selectable column = index.getSelectables().get( i );
final ColumnInformation columnInfo = indexInformation.getIndexedColumns().get( i );
if ( !column.getText().equals( columnInfo.getColumnIdentifier().getText() ) ) {
indicesMatch = false;
break;
}
}
}

if ( !indicesMatch ) {
throw new SchemaManagementException(
String.format(
ROOT,
"Index mismatch - `%s` on table `%s`",
name.render( dialect ),
tableInformation.getName().render()
)
);
}
} );
}

private void validateUniqueKeys(
Table table,
TableInformation tableInformation,
Metadata metadata,
ExecutionOptions options,
Dialect dialect) {
var validationType = ConstraintValidationType.interpret( UNIQUE_KEY_VALIDATION, options.getConfigurationValues() );

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe move this check up?

Suggested change
if ( validationType == ConstraintValidationType.NONE ) {
return;
}

table.getUniqueKeys().forEach( (rawName, uk) -> {
assert StringHelper.isNotEmpty( rawName );
assert Objects.equals( rawName, uk.getName() );
if ( validationType == ConstraintValidationType.NONE ) {
return;
}
else if ( validationType == ConstraintValidationType.NAMED ) {
if ( rawName.startsWith( "UK" ) ) {
// this is not a great check as the user could very well
// have explicitly chosen a name that starts with this as well,
// but...
return;
}
}

var name = metadata.getDatabase().toIdentifier( rawName );
final IndexInformation ukInfo = tableInformation.getIndex( name );

if ( ukInfo == null ) {
throw new SchemaManagementException(
String.format(
ROOT,
"Missing unique constraint named `%s` on table `%s`",
name.render( dialect ),
tableInformation.getName().render()
)
);
}

var matches = true;
assert uk.getColumns().size() == uk.getColumnSpan();
if ( uk.getColumnSpan() != ukInfo.getIndexedColumns().size() ) {
matches = false;
}
else {
for ( int i = 0; i < uk.getColumns().size(); i++ ) {
final Column column = uk.getColumns().get( i );
final ColumnInformation columnInfo = ukInfo.getIndexedColumns().get( i );
if ( !column.getName().equals( columnInfo.getColumnIdentifier().getText() ) ) {
matches = false;
break;
}
}
}

if ( !matches ) {
throw new SchemaManagementException(
String.format(
ROOT,
"Unique-key mismatch - `%s` on table `%s`",
name.render( dialect ),
tableInformation.getName().render()
)
);
}
} );
}

protected void validateSequence(Sequence sequence, SequenceInformation sequenceInformation) {
if ( sequenceInformation == null ) {
throw new SchemaManagementException(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.tool.schema.internal;


import java.util.Map;

/// Used to determine whether a constraint (index, unique key, etc.)
/// should be validated.
///
/// @implNote Yes, yes, an index is not technically a constraint - this is just
/// for nice simple naming.
///
/// @see org.hibernate.cfg.SchemaToolingSettings#INDEX_VALIDATION
/// @see org.hibernate.cfg.SchemaToolingSettings#UNIQUE_KEY_VALIDATION
///
/// @since 7.3
///
/// @author Steve Ebersole
public enum ConstraintValidationType {
/// No validation will occur.
NONE,
/// Validation will occur only for explicitly named constraints.
NAMED,
/// Validation will occur for all constraints.
ALL;

public static ConstraintValidationType interpret(
String name,
Map<String, Object> configurationValues) {
final Object setting = configurationValues.get( name );
if ( setting == null ) {
return NONE;
}

if ( setting instanceof ConstraintValidationType type ) {
return type;
}

var settingName = setting.toString();
if ( NAMED.name().equalsIgnoreCase( settingName ) ) {
return NAMED;
}
else if ( ALL.name().equalsIgnoreCase( settingName ) ) {
return ALL;
}
else {
return NONE;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ public ExceptionHandler getExceptionHandler() {
};
}

private static void performDatabaseAction(
public static void performDatabaseAction(
final Action action,
Metadata metadata,
SchemaManagementTool tool,
Expand Down
Loading
Loading