HHH-18056 Support for JPA 32 table options

This commit is contained in:
Andrea Boriero 2024-05-08 17:06:16 +02:00 committed by Steve Ebersole
parent 68b8ae3f22
commit 5caa0b2735
18 changed files with 366 additions and 0 deletions

View File

@ -713,6 +713,7 @@ public abstract class CollectionBinder {
final List<AnnotationUsage<JoinColumn>> joins; final List<AnnotationUsage<JoinColumn>> joins;
final List<AnnotationUsage<JoinColumn>> inverseJoins; final List<AnnotationUsage<JoinColumn>> inverseJoins;
final List<AnnotationUsage<Index>> jpaIndexes; final List<AnnotationUsage<Index>> jpaIndexes;
final String options;
//JPA 2 has priority //JPA 2 has priority
if ( collectionTable != null ) { if ( collectionTable != null ) {
@ -723,6 +724,7 @@ public abstract class CollectionBinder {
joins = collectionTable.getList( "joinColumns" ); joins = collectionTable.getList( "joinColumns" );
inverseJoins = null; inverseJoins = null;
jpaIndexes = collectionTable.getList( "indexes" ); jpaIndexes = collectionTable.getList( "indexes" );
options = collectionTable.getString( "options" );
} }
else { else {
catalog = assocTable.getString( "catalog" ); catalog = assocTable.getString( "catalog" );
@ -732,6 +734,7 @@ public abstract class CollectionBinder {
joins = assocTable.getList( "joinColumns" ); joins = assocTable.getList( "joinColumns" );
inverseJoins = assocTable.getList( "inverseJoinColumns" ); inverseJoins = assocTable.getList( "inverseJoinColumns" );
jpaIndexes = assocTable.getList( "indexes" ); jpaIndexes = assocTable.getList( "indexes" );
options = assocTable.getString( "options" );
} }
collectionBinder.setExplicitAssociationTable( true ); collectionBinder.setExplicitAssociationTable( true );
@ -749,6 +752,7 @@ public abstract class CollectionBinder {
} }
associationTableBinder.setUniqueConstraints( uniqueConstraints ); associationTableBinder.setUniqueConstraints( uniqueConstraints );
associationTableBinder.setJpaIndex( jpaIndexes ); associationTableBinder.setJpaIndex( jpaIndexes );
associationTableBinder.setOptions( options );
//set check constraint in the second pass //set check constraint in the second pass
annJoins = joins.isEmpty() ? null : joins; annJoins = joins.isEmpty() ? null : joins;
annInverseJoins = inverseJoins == null || inverseJoins.isEmpty() ? null : inverseJoins; annInverseJoins = inverseJoins == null || inverseJoins.isEmpty() ? null : inverseJoins;
@ -2589,6 +2593,7 @@ public abstract class CollectionBinder {
(usage) -> { (usage) -> {
TableBinder.addTableCheck( collectionTable, usage.findAttributeValue( "check" ) ); TableBinder.addTableCheck( collectionTable, usage.findAttributeValue( "check" ) );
TableBinder.addTableComment( collectionTable, usage.getString( "comment" ) ); TableBinder.addTableComment( collectionTable, usage.getString( "comment" ) );
TableBinder.addTableOptions( collectionTable, usage.getString( "options" ) );
} }
); );
} }

View File

@ -451,6 +451,7 @@ public class EntityBinder {
TableBinder.addJpaIndexes( table, jpaTableUsage.getList( "indexes" ), context ); TableBinder.addJpaIndexes( table, jpaTableUsage.getList( "indexes" ), context );
TableBinder.addTableCheck( table, jpaTableUsage.findAttributeValue( "check" ) ); TableBinder.addTableCheck( table, jpaTableUsage.findAttributeValue( "check" ) );
TableBinder.addTableComment( table, jpaTableUsage.getString( "comment" ) ); TableBinder.addTableComment( table, jpaTableUsage.getString( "comment" ) );
TableBinder.addTableOptions( table, jpaTableUsage.getString( "options" ) );
} }
final InFlightMetadataCollector.EntityTableXref entityTableXref = context final InFlightMetadataCollector.EntityTableXref entityTableXref = context
@ -2183,6 +2184,7 @@ public class EntityBinder {
final Table table = join.getTable(); final Table table = join.getTable();
TableBinder.addTableCheck( table, joinTable.findAttributeValue( "check" ) ); TableBinder.addTableCheck( table, joinTable.findAttributeValue( "check" ) );
TableBinder.addTableComment( table, joinTable.getString( "comment" ) ); TableBinder.addTableComment( table, joinTable.getString( "comment" ) );
TableBinder.addTableOptions( table, joinTable.getString( "options" ) );
return join; return join;
} }
@ -2201,6 +2203,7 @@ public class EntityBinder {
new IndexBinder( context ).bindIndexes( table, secondaryTable.getList( "indexes" ) ); new IndexBinder( context ).bindIndexes( table, secondaryTable.getList( "indexes" ) );
TableBinder.addTableCheck( table, secondaryTable.findAttributeValue( "check" ) ); TableBinder.addTableCheck( table, secondaryTable.findAttributeValue( "check" ) );
TableBinder.addTableComment( table, secondaryTable.getString( "comment" ) ); TableBinder.addTableComment( table, secondaryTable.getString( "comment" ) );
TableBinder.addTableOptions( table, secondaryTable.getString( "options" ) );
return join; return join;
} }

View File

@ -106,6 +106,7 @@ public class ImplicitToOneJoinTableSecondPass implements SecondPass {
tableBinder.setUniqueConstraints( joinTable.getList( "uniqueConstraints" ) ); tableBinder.setUniqueConstraints( joinTable.getList( "uniqueConstraints" ) );
tableBinder.setJpaIndex( joinTable.getList( "indexes" ) ); tableBinder.setJpaIndex( joinTable.getList( "indexes" ) );
tableBinder.setOptions( joinTable.getString( "options" ) );
return tableBinder; return tableBinder;
} }

View File

@ -78,6 +78,7 @@ public class TableBinder {
private boolean isJPA2ElementCollection; private boolean isJPA2ElementCollection;
private List<AnnotationUsage<UniqueConstraint>> uniqueConstraints; private List<AnnotationUsage<UniqueConstraint>> uniqueConstraints;
private List<AnnotationUsage<Index>> indexes; private List<AnnotationUsage<Index>> indexes;
private String options;
public void setBuildingContext(MetadataBuildingContext buildingContext) { public void setBuildingContext(MetadataBuildingContext buildingContext) {
this.buildingContext = buildingContext; this.buildingContext = buildingContext;
@ -111,6 +112,10 @@ public class TableBinder {
this.indexes = indexes; this.indexes = indexes;
} }
public void setOptions(String options) {
this.options = options;
}
public void setJPA2ElementCollection(boolean isJPA2ElementCollection) { public void setJPA2ElementCollection(boolean isJPA2ElementCollection) {
this.isJPA2ElementCollection = isJPA2ElementCollection; this.isJPA2ElementCollection = isJPA2ElementCollection;
} }
@ -294,6 +299,7 @@ public class TableBinder {
isAbstract, isAbstract,
uniqueConstraints, uniqueConstraints,
indexes, indexes,
options,
buildingContext, buildingContext,
null, null,
null null
@ -444,6 +450,7 @@ public class TableBinder {
isAbstract, isAbstract,
uniqueConstraints, uniqueConstraints,
null, null,
null,
buildingContext, buildingContext,
null, null,
null null
@ -466,6 +473,7 @@ public class TableBinder {
isAbstract, isAbstract,
uniqueConstraints, uniqueConstraints,
null, null,
null,
buildingContext, buildingContext,
subselect, subselect,
denormalizedSuperTableXref denormalizedSuperTableXref
@ -479,6 +487,7 @@ public class TableBinder {
boolean isAbstract, boolean isAbstract,
List<AnnotationUsage<UniqueConstraint>> uniqueConstraints, List<AnnotationUsage<UniqueConstraint>> uniqueConstraints,
List<AnnotationUsage<Index>> indexes, List<AnnotationUsage<Index>> indexes,
String options,
MetadataBuildingContext buildingContext, MetadataBuildingContext buildingContext,
String subselect, String subselect,
InFlightMetadataCollector.EntityTableXref denormalizedSuperTableXref) { InFlightMetadataCollector.EntityTableXref denormalizedSuperTableXref) {
@ -503,6 +512,9 @@ public class TableBinder {
new IndexBinder( buildingContext ).bindIndexes( table, indexes ); new IndexBinder( buildingContext ).bindIndexes( table, indexes );
} }
if ( options != null ) {
table.setOptions( options );
}
metadataCollector.addTableNameBinding( logicalName, table ); metadataCollector.addTableNameBinding( logicalName, table );
return table; return table;
@ -895,6 +907,12 @@ public class TableBinder {
} }
} }
static void addTableOptions(Table table, String options) {
if ( StringHelper.isNotEmpty( options ) ) {
table.setOptions( options );
}
}
public void setDefaultName( public void setDefaultName(
String ownerClassName, String ownerClassName,
String ownerEntity, String ownerEntity,

View File

@ -1196,4 +1196,9 @@ public class CockroachDialect extends Dialect {
public boolean supportsFromClauseInUpdate() { public boolean supportsFromClauseInUpdate() {
return true; return true;
} }
@Override
public boolean supportsTableOptions() {
return true;
}
} }

View File

@ -1316,4 +1316,9 @@ public class DB2Dialect extends Dialect {
public boolean supportsFromClauseInUpdate() { public boolean supportsFromClauseInUpdate() {
return getDB2Version().isSameOrAfter( 11 ); return getDB2Version().isSameOrAfter( 11 );
} }
@Override
public boolean supportsTableOptions() {
return true;
}
} }

View File

@ -5653,5 +5653,13 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
return sqlCheckConstraint; return sqlCheckConstraint;
} }
/**
* Does this dialect support appending table options SQL fragment at the end of the SQL Table creation statement?
*
* @return {@code true} indicates it does; {@code false} indicates it does not;
*/
public boolean supportsTableOptions(){
return false;
}
} }

View File

@ -1007,4 +1007,8 @@ public class H2Dialect extends Dialect {
return true; return true;
} }
@Override
public boolean supportsTableOptions() {
return true;
}
} }

View File

@ -727,4 +727,9 @@ public class HSQLDialect extends Dialect {
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() { public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS; return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
} }
@Override
public boolean supportsTableOptions() {
return true;
}
} }

View File

@ -1581,4 +1581,9 @@ public class MySQLDialect extends Dialect {
} }
return sqlCheckConstraint; return sqlCheckConstraint;
} }
@Override
public boolean supportsTableOptions() {
return true;
}
} }

View File

@ -1702,4 +1702,9 @@ public class OracleDialect extends Dialect {
} }
return sqlCheckConstraint; return sqlCheckConstraint;
} }
@Override
public boolean supportsTableOptions() {
return true;
}
} }

View File

@ -1598,4 +1598,9 @@ public class PostgreSQLDialect extends Dialect {
public boolean supportsFromClauseInUpdate() { public boolean supportsFromClauseInUpdate() {
return true; return true;
} }
@Override
public boolean supportsTableOptions() {
return true;
}
} }

View File

@ -75,6 +75,7 @@ public class Table implements Serializable, ContributableDatabaseObject {
private boolean hasDenormalizedTables; private boolean hasDenormalizedTables;
private String comment; private String comment;
private String viewQuery; private String viewQuery;
private String options;
private List<Function<SqlStringGenerationContext, InitCommand>> initCommandProducers; private List<Function<SqlStringGenerationContext, InitCommand>> initCommandProducers;
@ -884,4 +885,12 @@ public class Table implements Serializable, ContributableDatabaseObject {
return unmodifiableList( initCommands ); return unmodifiableList( initCommands );
} }
} }
public String getOptions() {
return options;
}
public void setOptions(String options) {
this.options = options;
}
} }

View File

@ -19,6 +19,7 @@ import org.hibernate.boot.model.relational.QualifiedTableName;
import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.aggregate.AggregateSupport; import org.hibernate.dialect.aggregate.AggregateSupport;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.AggregateColumn; import org.hibernate.mapping.AggregateColumn;
import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.CheckConstraint; import org.hibernate.mapping.CheckConstraint;
@ -113,6 +114,12 @@ public class StandardTableExporter implements Exporter<Table> {
} }
final List<String> sqlStrings = new ArrayList<>(); final List<String> sqlStrings = new ArrayList<>();
if ( StringHelper.isNotEmpty( table.getOptions() ) ) {
if ( dialect.supportsTableOptions() ) {
createTable.append( " " );
createTable.append( table.getOptions() );
}
}
sqlStrings.add( createTable.toString() ); sqlStrings.add( createTable.toString() );
applyComments( table, formattedTableName, sqlStrings ); applyComments( table, formattedTableName, sqlStrings );
applyInitCommands( table, sqlStrings, context ); applyInitCommands( table, sqlStrings, context );

View File

@ -0,0 +1,155 @@
package org.hibernate.orm.test.schemaupdate.tableoptions;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.EnumSet;
import java.util.Locale;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.schema.TargetType;
import org.hibernate.testing.orm.junit.BaseUnitTest;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.util.ServiceRegistryUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
@BaseUnitTest
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsTableOptions.class)
public class TableOptionsTest {
static final String TABLE_NAME = "PRIMARY_TABLE";
static final String TABLE_OPTIONS = "option_1";
static final String SECONDARY_TABLE_NAME = "SECOND_TABLE";
static final String SECONDARY_TABLE_OPTIONS = "option_2";
static final String JOIN_TABLE_NAME = "JOIN_TABLE";
static final String JOIN_TABLE_OPTIONS = "option_3";
static final String COLLECTION_TABLE_NAME = "COLLECTION_TABLE";
static final String COLLECTION_TABLE_OPTIONS = "option_4";
private File output;
private StandardServiceRegistry ssr;
private MetadataImplementor metadata;
@BeforeEach
public void setUp() throws IOException {
output = File.createTempFile( "update_script", ".sql" );
output.deleteOnExit();
ssr = ServiceRegistryUtil.serviceRegistry();
}
@AfterEach
public void tearsDown() {
output.delete();
StandardServiceRegistryBuilder.destroy( ssr );
}
@Test
public void testTableCommentAreCreated() throws Exception {
createSchema( TestEntity.class );
assertTrue(
tableCreationStatementContainsOptions( output, TABLE_NAME, TABLE_OPTIONS ),
"Table " + TABLE_NAME + " options has not been created "
);
assertTrue(
tableCreationStatementContainsOptions( output, SECONDARY_TABLE_NAME, SECONDARY_TABLE_OPTIONS ),
"SecondaryTable " + SECONDARY_TABLE_NAME + " options has not been created "
);
assertTrue(
tableCreationStatementContainsOptions( output, JOIN_TABLE_NAME, JOIN_TABLE_OPTIONS ),
"Join Table " + JOIN_TABLE_NAME + " options has not been created "
);
assertTrue(
tableCreationStatementContainsOptions( output, COLLECTION_TABLE_NAME, COLLECTION_TABLE_OPTIONS ),
"Join Table " + COLLECTION_TABLE_NAME + " options has not been created "
);
}
@Test
public void testXmlMappingTableCommentAreCreated() throws Exception {
createSchema( "org/hibernate/orm/test/schemaupdate/tableoptions/TestEntity.xml" );
assertTrue(
tableCreationStatementContainsOptions( output, TABLE_NAME, TABLE_OPTIONS ),
"Table " + TABLE_NAME + " options has not been created "
);
assertTrue(
tableCreationStatementContainsOptions( output, SECONDARY_TABLE_NAME, SECONDARY_TABLE_OPTIONS ),
"SecondaryTable " + SECONDARY_TABLE_NAME + " options has not been created "
);
assertTrue(
tableCreationStatementContainsOptions( output, JOIN_TABLE_NAME, JOIN_TABLE_OPTIONS ),
"Join Table " + JOIN_TABLE_NAME + " options has not been created "
);
assertTrue(
tableCreationStatementContainsOptions( output, COLLECTION_TABLE_NAME, COLLECTION_TABLE_OPTIONS ),
"Join Table " + COLLECTION_TABLE_NAME + " options has not been created "
);
}
private static boolean tableCreationStatementContainsOptions(
File output,
String tableName,
String options) throws Exception {
String[] fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase()
.split( System.lineSeparator() );
for ( int i = 0; i < fileContent.length; i++ ) {
String statement = fileContent[i].toUpperCase( Locale.ROOT );
if ( statement.contains( "CREATE TABLE " + tableName.toUpperCase( Locale.ROOT ) ) ) {
if ( statement.contains( options.toUpperCase( Locale.ROOT ) ) ) {
return true;
}
}
}
return false;
}
private void createSchema(String... xmlMapping) {
final MetadataSources metadataSources = new MetadataSources( ssr );
for ( String xml : xmlMapping ) {
metadataSources.addResource( xml );
}
metadata = (MetadataImplementor) metadataSources.buildMetadata();
metadata.orderColumns( false );
metadata.validate();
new SchemaExport()
.setHaltOnError( true )
.setOutputFile( output.getAbsolutePath() )
.setFormat( false )
.create( EnumSet.of( TargetType.SCRIPT ), metadata );
}
private void createSchema(Class... annotatedClasses) {
final MetadataSources metadataSources = new MetadataSources( ssr );
for ( Class c : annotatedClasses ) {
metadataSources.addAnnotatedClass( c );
}
metadata = (MetadataImplementor) metadataSources.buildMetadata();
metadata.orderColumns( false );
metadata.validate();
new SchemaExport()
.setHaltOnError( true )
.setOutputFile( output.getAbsolutePath() )
.setFormat( false )
.create( EnumSet.of( TargetType.SCRIPT ), metadata );
}
}

View File

@ -0,0 +1,89 @@
package org.hibernate.orm.test.schemaupdate.tableoptions;
import java.util.List;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.Table;
@Entity
@Table(
name = TableOptionsTest.TABLE_NAME,
options = TableOptionsTest.TABLE_OPTIONS
)
@SecondaryTable(
name = TableOptionsTest.SECONDARY_TABLE_NAME,
options = TableOptionsTest.SECONDARY_TABLE_OPTIONS
)
public class TestEntity {
@Id
private Long id;
@Column(name = "NAME_COLUMN")
private String name;
@Column(name = "SECOND_NAME", table = TableOptionsTest.SECONDARY_TABLE_NAME)
private String secondName;
@ManyToOne(fetch = FetchType.LAZY)
@JoinTable(
name = TableOptionsTest.JOIN_TABLE_NAME,
options = TableOptionsTest.JOIN_TABLE_OPTIONS
)
private TestEntity testEntity;
@ElementCollection
@CollectionTable(
name = TableOptionsTest.COLLECTION_TABLE_NAME,
options = TableOptionsTest.COLLECTION_TABLE_OPTIONS
)
private List<String> stringFields;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSecondName() {
return secondName;
}
public void setSecondName(String secondName) {
this.secondName = secondName;
}
public TestEntity getTestEntity() {
return testEntity;
}
public void setTestEntity(TestEntity testEntity) {
this.testEntity = testEntity;
}
public List<String> getStringFields() {
return stringFields;
}
public void setStringFields(List<String> stringFields) {
this.stringFields = stringFields;
}
}

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Hibernate, Relational Persistence for Idiomatic Java
~
~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
-->
<entity-mappings xmlns="http://www.hibernate.org/xsd/orm/mapping" version="3.2">
<package>org.hibernate.orm.test.schemaupdate.tableoptions</package>
<entity class="TestEntity" metadata-complete="true">
<table name="PRIMARY_TABLE" options="option_1"/>
<secondary-table name="SECOND_TABLE" options="option_2"/>
<attributes>
<id name="id"/>
<basic name="name">
<column name="NAME_COLUMN"/>
</basic>
<basic name="secondName" >
<column name="secondName" table="SECOND_TABLE"/>
</basic>
<many-to-one name="testEntity">
<join-table name="JOIN_TABLE"
options="option_3"/>
</many-to-one>
<element-collection name="stringFields">
<collection-table name="COLLECTION_TABLE" options="option_4"/>
</element-collection>
</attributes>
</entity>
</entity-mappings>

View File

@ -705,4 +705,11 @@ abstract public class DialectFeatureChecks {
return dialect.getNationalizationSupport() == NationalizationSupport.EXPLICIT; return dialect.getNationalizationSupport() == NationalizationSupport.EXPLICIT;
} }
} }
public static class SupportsTableOptions implements DialectFeatureCheck{
@Override
public boolean apply(Dialect dialect) {
return dialect.supportsTableOptions();
}
}
} }