From 6fae23a4dca2f169c43d4a9fca00b136e090942d Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Tue, 25 Jan 2022 15:45:31 +0000 Subject: [PATCH 001/201] 5.6.6-SNAPSHOT --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index b2d8dafd4f07..33771ce08ccb 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=5.6.5.Final \ No newline at end of file +hibernateVersion=5.6.6-SNAPSHOT \ No newline at end of file From 29b896bace1675210c2e732270bb6e6070c8cf83 Mon Sep 17 00:00:00 2001 From: Karel Maesen Date: Wed, 2 Feb 2022 20:53:54 +0100 Subject: [PATCH 002/201] HHH-14932 Use the correct WKB Dialect --- .../postgis/PGGeometryTypeDescriptor.java | 15 ++-- .../dialect/postgis/PostgisPG82Dialect.java | 4 +- .../dialect/postgis/PostgisPG91Dialect.java | 4 +- .../dialect/postgis/PostgisPG92Dialect.java | 4 +- .../dialect/postgis/PostgisPG93Dialect.java | 4 +- .../dialect/postgis/PostgisPG94Dialect.java | 4 +- .../dialect/postgis/PostgisPG95Dialect.java | 2 +- .../dialect/postgis/PostgisPG9Dialect.java | 4 +- .../dialect/postgis/PostgisSupport.java | 35 +++++---- .../dialect/postgis/TestWKBPostgis221.java | 73 +++++++++++++++++++ 10 files changed, 114 insertions(+), 35 deletions(-) create mode 100644 hibernate-spatial/src/test/java/org/hibernate/spatial/dialect/postgis/TestWKBPostgis221.java diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGGeometryTypeDescriptor.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGGeometryTypeDescriptor.java index d4a589789688..335db450c6db 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGGeometryTypeDescriptor.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGGeometryTypeDescriptor.java @@ -53,10 +53,11 @@ public Geometry toGeometry(Object object) { if ( object == null ) { return null; } - ByteBuffer buffer = null; + ByteBuffer buffer; if ( object instanceof PGobject ) { String pgValue = ( (PGobject) object ).getValue(); + assert pgValue != null; if ( pgValue.startsWith( "00" ) || pgValue.startsWith( "01" ) ) { //we have a WKB because this pgValue starts with the bit-order byte buffer = ByteBuffer.from( pgValue ); @@ -91,7 +92,7 @@ public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescript return new ValueBinder() { @Override - public final void bind(PreparedStatement st, X value, int index, WrapperOptions options) + public void bind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { if ( value == null ) { st.setNull( index, Types.OTHER ); @@ -102,7 +103,7 @@ public final void bind(PreparedStatement st, X value, int index, WrapperOptions } @Override - public final void bind(CallableStatement st, X value, String name, WrapperOptions options) + public void bind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { if ( value == null ) { st.setNull( name, Types.OTHER ); @@ -112,21 +113,21 @@ public final void bind(CallableStatement st, X value, String name, WrapperOption } } - protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + private void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { final PGobject obj = toPGobject( value, options ); st.setObject( index, obj ); } - protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + private void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { final PGobject obj = toPGobject( value, options ); st.setObject( name, obj ); } private PGobject toPGobject(X value, WrapperOptions options) throws SQLException { - final WkbEncoder encoder = Wkb.newEncoder( Wkb.Dialect.POSTGIS_EWKB_1 ); - final Geometry geometry = javaTypeDescriptor.unwrap( value, Geometry.class, options ); + final WkbEncoder encoder = Wkb.newEncoder( wkbDialect ); + final Geometry geometry = javaTypeDescriptor.unwrap( value, Geometry.class, options ); final String hexString = encoder.encode( geometry, ByteOrder.NDR ).toString(); final PGobject obj = new PGobject(); obj.setType( "geometry" ); diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG82Dialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG82Dialect.java index 9351bceabb82..f93cb8aaf04c 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG82Dialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG82Dialect.java @@ -23,7 +23,7 @@ public class PostgisPG82Dialect extends PostgreSQL82Dialect implements SpatialDialect { - transient private PostgisSupport support = new PostgisSupport(); + final transient private PostgisSupport support = new PostgisSupport(); /** * Creates an instance @@ -45,7 +45,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry typeContributions, serviceRegistry ); - support.contributeTypes( typeContributions, serviceRegistry ); + support.contributeTypes( typeContributions, serviceRegistry, PGGeometryTypeDescriptor.INSTANCE_WKB_1 ); } @Override diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG91Dialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG91Dialect.java index f2577625f0b6..bc795f787300 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG91Dialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG91Dialect.java @@ -23,7 +23,7 @@ public class PostgisPG91Dialect extends PostgreSQL91Dialect implements SpatialDialect { - transient private PostgisSupport support = new PostgisSupport(); + final transient private PostgisSupport support = new PostgisSupport(); /** * Creates an instance @@ -45,7 +45,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry typeContributions, serviceRegistry ); - support.contributeTypes( typeContributions, serviceRegistry ); + support.contributeTypes( typeContributions, serviceRegistry, PGGeometryTypeDescriptor.INSTANCE_WKB_1 ); } @Override diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG92Dialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG92Dialect.java index e71f982f863b..dea76b6b45e6 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG92Dialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG92Dialect.java @@ -23,7 +23,7 @@ public class PostgisPG92Dialect extends PostgreSQL92Dialect implements SpatialDialect { - transient private PostgisSupport support = new PostgisSupport(); + final transient private PostgisSupport support = new PostgisSupport(); /** * Creates an instance @@ -45,7 +45,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry typeContributions, serviceRegistry ); - support.contributeTypes( typeContributions, serviceRegistry ); + support.contributeTypes( typeContributions, serviceRegistry, PGGeometryTypeDescriptor.INSTANCE_WKB_1 ); } @Override diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG93Dialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG93Dialect.java index ee4ddd988590..52ec43a3a8cc 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG93Dialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG93Dialect.java @@ -23,7 +23,7 @@ public class PostgisPG93Dialect extends PostgreSQL93Dialect implements SpatialDialect { - transient private PostgisSupport support = new PostgisSupport(); + final transient private PostgisSupport support = new PostgisSupport(); /** * Creates an instance @@ -45,7 +45,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry typeContributions, serviceRegistry ); - support.contributeTypes( typeContributions, serviceRegistry ); + support.contributeTypes( typeContributions, serviceRegistry, PGGeometryTypeDescriptor.INSTANCE_WKB_1 ); } @Override diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG94Dialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG94Dialect.java index 6c14a2afc2e1..972a6cdc1117 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG94Dialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG94Dialect.java @@ -23,7 +23,7 @@ public class PostgisPG94Dialect extends PostgreSQL94Dialect implements SpatialDialect { - transient private PostgisSupport support = new PostgisSupport(); + final transient private PostgisSupport support = new PostgisSupport(); /** * Creates an instance @@ -45,7 +45,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry typeContributions, serviceRegistry ); - support.contributeTypes( typeContributions, serviceRegistry ); + support.contributeTypes( typeContributions, serviceRegistry, PGGeometryTypeDescriptor.INSTANCE_WKB_1 ); } @Override diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG95Dialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG95Dialect.java index 1c31d221ad74..96101cec41ef 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG95Dialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG95Dialect.java @@ -25,7 +25,7 @@ public class PostgisPG95Dialect extends PostgreSQL95Dialect implements PGSpatial public PostgisPG95Dialect() { super(); registerColumnType( - PGGeometryTypeDescriptor.INSTANCE_WKB_1.getSqlType(), + PGGeometryTypeDescriptor.INSTANCE_WKB_2.getSqlType(), "GEOMETRY" ); for ( Map.Entry entry : functionsToRegister() ) { diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG9Dialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG9Dialect.java index 88fefa520530..939dbdf8b74e 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG9Dialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisPG9Dialect.java @@ -23,7 +23,7 @@ public class PostgisPG9Dialect extends PostgreSQL9Dialect implements SpatialDialect { - transient private PostgisSupport support = new PostgisSupport(); + final transient private PostgisSupport support = new PostgisSupport(); /** * Creates an instance @@ -31,7 +31,7 @@ public class PostgisPG9Dialect extends PostgreSQL9Dialect implements SpatialDial public PostgisPG9Dialect() { super(); registerColumnType( - PGGeometryTypeDescriptor.INSTANCE_WKB_1.getSqlType(), + PGGeometryTypeDescriptor.INSTANCE_WKB_2.getSqlType(), "GEOMETRY" ); for ( Map.Entry entry : support.functionsToRegister() ) { diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisSupport.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisSupport.java index ef361f47f5f0..c4f99bdd1c8f 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisSupport.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisSupport.java @@ -20,6 +20,7 @@ import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.SpatialRelation; import org.hibernate.spatial.dialect.SpatialFunctionsRegistry; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; /** * Created by Karel Maesen, Geovise BVBA on 29/10/16. @@ -36,20 +37,28 @@ public PostgisSupport() { postgisFunctions = new PostgisFunctions(); } - public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { - typeContributions.contributeType( new GeolatteGeometryType( PGGeometryTypeDescriptor.INSTANCE_WKB_1 ) ); - typeContributions.contributeType( new JTSGeometryType( PGGeometryTypeDescriptor.INSTANCE_WKB_1 ) ); + public void contributeTypes( + TypeContributions typeContributions, + ServiceRegistry serviceRegistry, + SqlTypeDescriptor wkbType) { + typeContributions.contributeType( new GeolatteGeometryType( wkbType ) ); + typeContributions.contributeType( new JTSGeometryType( wkbType ) ); typeContributions.contributeJavaTypeDescriptor( GeolatteGeometryJavaTypeDescriptor.INSTANCE ); typeContributions.contributeJavaTypeDescriptor( JTSGeometryJavaTypeDescriptor.INSTANCE ); } + public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { + contributeTypes( typeContributions, serviceRegistry, PGGeometryTypeDescriptor.INSTANCE_WKB_2 ); + } + + public SpatialFunctionsRegistry functionsToRegister() { return postgisFunctions; } - public boolean isSpatial(int typeCode){ - return typeCode == Types.OTHER || typeCode == PGGeometryTypeDescriptor.INSTANCE_WKB_1.getSqlType(); + public boolean isSpatial(int typeCode) { + return typeCode == Types.OTHER || typeCode == PGGeometryTypeDescriptor.INSTANCE_WKB_2.getSqlType(); } /** @@ -117,17 +126,13 @@ public String getSpatialFilterExpression(String columnName) { */ @Override public String getSpatialAggregateSQL(String columnName, int aggregation) { - switch ( aggregation ) { - case SpatialAggregate.EXTENT: - final StringBuilder stbuf = new StringBuilder(); - stbuf.append( "st_extent(" ).append( columnName ).append( ")::geometry" ); - return stbuf.toString(); - default: - throw new IllegalArgumentException( - "Aggregation of type " - + aggregation + " are not supported by this dialect" - ); + if ( aggregation == SpatialAggregate.EXTENT ) { + return "st_extent(" + columnName + ")::geometry"; } + throw new IllegalArgumentException( + "Aggregation of type " + + aggregation + " are not supported by this dialect" + ); } /** diff --git a/hibernate-spatial/src/test/java/org/hibernate/spatial/dialect/postgis/TestWKBPostgis221.java b/hibernate-spatial/src/test/java/org/hibernate/spatial/dialect/postgis/TestWKBPostgis221.java new file mode 100644 index 000000000000..ee4f0427014f --- /dev/null +++ b/hibernate-spatial/src/test/java/org/hibernate/spatial/dialect/postgis/TestWKBPostgis221.java @@ -0,0 +1,73 @@ +/* + * 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 . + */ + +package org.hibernate.spatial.dialect.postgis; + +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import org.geolatte.geom.G2D; +import org.geolatte.geom.Point; + +import static org.geolatte.geom.builder.DSL.g; +import static org.geolatte.geom.builder.DSL.point; +import static org.geolatte.geom.crs.CoordinateReferenceSystems.WGS84; +import static org.junit.Assert.assertEquals; + + +@TestForIssue(jiraKey = "HHH-14932") +@RequiresDialect(PostgisPG95Dialect.class) +public class TestWKBPostgis221 extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Foo.class }; + } + + @Before + public void setup() { + inTransaction( session -> session.persist( new Foo( + 1, + point( WGS84 ) + ) ) ); + } + + @Test + public void test() { + inTransaction( session -> { + List list = session + .createQuery( "from Foo", Foo.class ) + .getResultList(); + assertEquals( point( WGS84 ), list.get( 0 ).point ); + } ); + } + + @Entity(name = "Foo") + @Table(name = "Foo") + public static class Foo { + @Id + long id; + Point point; + + public Foo() { + } + + public Foo(long id, Point point) { + this.id = id; + this.point = point; + } + + } +} From 1f4162a77b9f892eb348970869ddd1e93a70c24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 4 Feb 2022 12:32:04 +0100 Subject: [PATCH 003/201] HHH-15069 Fix backwards-incompatible changes for callers of PersistentIdentifierGenerator getters --- .../id/MultipleHiLoPerTableGenerator.java | 14 ++++++++++++++ .../id/PersistentIdentifierGenerator.java | 12 ++++++++++++ .../org/hibernate/id/SequenceGenerator.java | 18 ++++++++++++++++++ .../id/enhanced/DatabaseStructure.java | 11 +++++++++++ .../id/enhanced/SequenceStructure.java | 12 ++++++++++++ .../id/enhanced/SequenceStyleGenerator.java | 10 ++++++++++ .../hibernate/id/enhanced/TableGenerator.java | 6 ++++++ .../hibernate/id/enhanced/TableStructure.java | 11 +++++++++++ 8 files changed, 94 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java index ea617988cfdb..bc077ec9415b 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java @@ -22,6 +22,7 @@ import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.model.relational.QualifiedNameParser; import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.internal.FormatStyle; import org.hibernate.engine.jdbc.spi.JdbcServices; @@ -93,6 +94,8 @@ public class MultipleHiLoPerTableGenerator implements PersistentIdentifierGenera private QualifiedName qualifiedTableName; private QualifiedName physicalTableName; + @Deprecated + private String formattedTableNameForLegacyGetter; private String segmentColumnName; private String segmentName; private String valueColumnName; @@ -343,6 +346,13 @@ public void registerExportables(Database database) { // allow physical naming strategies a chance to kick in physicalTableName = table.getQualifiedTableName(); + + final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); + final Dialect dialect = jdbcEnvironment.getDialect(); + this.formattedTableNameForLegacyGetter = jdbcEnvironment.getQualifiedObjectNameFormatter().format( + physicalTableName, + dialect + ); } @Override @@ -376,4 +386,8 @@ public void initialize(SqlStringGenerationContext context) { } + @Deprecated + public Object generatorKey() { + return formattedTableNameForLegacyGetter; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java index 4a129711e4ab..5798d717a57a 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java @@ -58,4 +58,16 @@ public interface PersistentIdentifierGenerator extends IdentifierGenerator { * The key under which to find the {@link org.hibernate.boot.model.naming.ObjectNameNormalizer} in the config param map. */ String IDENTIFIER_NORMALIZER = "identifier_normalizer"; + + /** + * Return a key unique to the underlying database objects. Prevents us from + * trying to create/remove them multiple times. + * + * @return Object an identifying key for this generator + * @deprecated No longer necessary. + */ + @Deprecated + default Object generatorKey() { + return null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/SequenceGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/SequenceGenerator.java index d480034c5dcd..20742e057868 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/SequenceGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/SequenceGenerator.java @@ -20,6 +20,7 @@ import org.hibernate.boot.model.relational.QualifiedNameParser; import org.hibernate.boot.model.relational.Sequence; import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.config.ConfigurationHelper; @@ -64,6 +65,8 @@ public class SequenceGenerator private QualifiedName logicalQualifiedSequenceName; private QualifiedName physicalSequenceName; + @Deprecated + private String formattedSequenceNameForLegacyGetter; private Type identifierType; private String sql; @@ -71,6 +74,17 @@ protected Type getIdentifierType() { return identifierType; } + @Override + @Deprecated + public Object generatorKey() { + return getSequenceName(); + } + + @Deprecated + public String getSequenceName() { + return formattedSequenceNameForLegacyGetter; + } + public QualifiedName getPhysicalSequenceName() { return physicalSequenceName; } @@ -174,6 +188,10 @@ public void registerExportables(Database database) { ); } this.physicalSequenceName = sequence.getName(); + + final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); + this.formattedSequenceNameForLegacyGetter = jdbcEnvironment.getQualifiedObjectNameFormatter() + .format( physicalSequenceName, jdbcEnvironment.getDialect() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/DatabaseStructure.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/DatabaseStructure.java index 80b493ed5e8c..4079578a188d 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/DatabaseStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/DatabaseStructure.java @@ -19,6 +19,17 @@ * @author Steve Ebersole */ public interface DatabaseStructure extends ExportableProducer { + + /** + * The name of the database structure (table or sequence). + * @deprecated Use {@link #getPhysicalName()} instead. + */ + @Deprecated + default String getName() { + // Not a great implementation, but that'll have to do: it's only for backwards compatibility. + return getPhysicalName().render(); + } + /** * The physical name of the database structure (table or sequence). *

diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java index fe3051fae5b9..06d86cfe3d88 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStructure.java @@ -43,6 +43,8 @@ public class SequenceStructure implements DatabaseStructure { private String sql; private boolean applyIncrementSizeToSourceValues; private int accessCounter; + @Deprecated + private String formattedSequenceNameForLegacyGetter; protected QualifiedName physicalSequenceName; public SequenceStructure( @@ -58,6 +60,12 @@ public SequenceStructure( this.numberType = numberType; } + @Override + @Deprecated + public String getName() { + return formattedSequenceNameForLegacyGetter; + } + @Override public QualifiedName getPhysicalName() { return physicalSequenceName; @@ -181,5 +189,9 @@ protected void buildSequence(Database database) { } this.physicalSequenceName = sequence.getName(); + + final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); + this.formattedSequenceNameForLegacyGetter = jdbcEnvironment.getQualifiedObjectNameFormatter() + .format( physicalSequenceName, jdbcEnvironment.getDialect() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java index 5f15d73fad06..40ffdb47a2ae 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java @@ -534,6 +534,16 @@ public Serializable generate(SharedSessionContractImplementor session, Object ob return optimizer.generate( databaseStructure.buildCallback( session ) ); } + + // PersistentIdentifierGenerator implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + @Deprecated + public Object generatorKey() { + return databaseStructure.getName(); + } + + // BulkInsertionCapableIdentifierGenerator implementation ~~~~~~~~~~~~~~~~~ @Override diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java index 250d7c26d635..4747c4c9245a 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java @@ -249,6 +249,12 @@ public class TableGenerator implements PersistentIdentifierGenerator { private Optimizer optimizer; private long accessCount; + @Override + @Deprecated + public Object generatorKey() { + return qualifiedTableName.render(); + } + /** * Type mapping for the identifier. * diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java index e9924fe01708..5d1ddfc12dd4 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java @@ -55,6 +55,8 @@ public class TableStructure implements DatabaseStructure { private final Class numberType; private QualifiedName physicalTableName; + @Deprecated + private String formattedTableNameForLegacyGetter; private String valueColumnNameText; private String selectQuery; @@ -78,6 +80,12 @@ public TableStructure( this.numberType = numberType; } + @Override + @Deprecated + public String getName() { + return formattedTableNameForLegacyGetter; + } + @Override public QualifiedName getPhysicalName() { return physicalTableName; @@ -247,6 +255,9 @@ public void registerExportables(Database database) { } this.physicalTableName = table.getQualifiedTableName(); + this.formattedTableNameForLegacyGetter = jdbcEnvironment.getQualifiedObjectNameFormatter() + .format( physicalTableName, dialect ); + valueColumnNameText = logicalValueColumnNameIdentifier.render( dialect ); if ( tableCreated ) { ExportableColumn valueColumn = new ExportableColumn( From 9f4ebcab1faa127b2d2fe68b587a3e6be447759d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 4 Feb 2022 11:01:48 +0100 Subject: [PATCH 004/201] HHH-15069 Fix backwards-incompatible changes for implementors of InsertGeneratedIdentifierDelegate --- .../InsertGeneratedIdentifierDelegate.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/id/insert/InsertGeneratedIdentifierDelegate.java b/hibernate-core/src/main/java/org/hibernate/id/insert/InsertGeneratedIdentifierDelegate.java index 564a7cad92f3..826f0d3ffe23 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/insert/InsertGeneratedIdentifierDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/id/insert/InsertGeneratedIdentifierDelegate.java @@ -22,6 +22,18 @@ */ public interface InsertGeneratedIdentifierDelegate { + /** + * Build a {@link org.hibernate.sql.Insert} specific to the delegate's mode + * of handling generated key values. + * + * @return The insert object. + * @deprecated Implement {@link #prepareIdentifierGeneratingInsert(SqlStringGenerationContext)} instead. + */ + @Deprecated + default IdentifierGeneratingInsert prepareIdentifierGeneratingInsert() { + throw new IllegalStateException("prepareIdentifierGeneratingInsert(...) was not implemented!"); + } + /** * Build a {@link org.hibernate.sql.Insert} specific to the delegate's mode * of handling generated key values. @@ -29,7 +41,9 @@ public interface InsertGeneratedIdentifierDelegate { * @param context A context to help generate SQL strings * @return The insert object. */ - IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(SqlStringGenerationContext context); + default IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(SqlStringGenerationContext context) { + return prepareIdentifierGeneratingInsert(); + } /** * Perform the indicated insert SQL statement and determine the identifier value From 97f87325f84ee6ce1a14881bbee15ff479174bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 4 Feb 2022 11:09:37 +0100 Subject: [PATCH 005/201] HHH-15069 Fix backwards-incompatible changes for implementors of UniqueDelegate --- .../dialect/unique/UniqueDelegate.java | 79 +++++++++++++++++-- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/unique/UniqueDelegate.java b/hibernate-core/src/main/java/org/hibernate/dialect/unique/UniqueDelegate.java index 6fdc173af796..a00dff0f7e05 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/unique/UniqueDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/unique/UniqueDelegate.java @@ -35,6 +35,21 @@ * @author Brett Meyer */ public interface UniqueDelegate { + /** + * Get the fragment that can be used to make a column unique as part of its column definition. + *

+ * This is intended for dialects which do not support unique constraints + * + * @param column The column to which to apply the unique + * @return The fragment (usually "unique"), empty string indicates the uniqueness will be indicated using a + * different approach + * @deprecated Implement {@link #getColumnDefinitionUniquenessFragment(Column, SqlStringGenerationContext)} instead. + */ + @Deprecated + default String getColumnDefinitionUniquenessFragment(Column column) { + throw new IllegalStateException("getColumnDefinitionUniquenessFragment(...) was not implemented!"); + } + /** * Get the fragment that can be used to make a column unique as part of its column definition. *

@@ -45,7 +60,27 @@ public interface UniqueDelegate { * @return The fragment (usually "unique"), empty string indicates the uniqueness will be indicated using a * different approach */ - public String getColumnDefinitionUniquenessFragment(Column column, SqlStringGenerationContext context); + default String getColumnDefinitionUniquenessFragment(Column column, SqlStringGenerationContext context) { + return getColumnDefinitionUniquenessFragment( column ); + } + + /** + * Get the fragment that can be used to apply unique constraints as part of table creation. The implementation + * should iterate over the {@link org.hibernate.mapping.UniqueKey} instances for the given table (see + * {@link org.hibernate.mapping.Table#getUniqueKeyIterator()} and generate the whole fragment for all + * unique keys + *

+ * Intended for Dialects which support unique constraint definitions, but just not in separate ALTER statements. + * + * @param table The table for which to generate the unique constraints fragment + * @return The fragment, typically in the form {@code ", unique(col1, col2), unique( col20)"}. NOTE: The leading + * comma is important! + * @deprecated Implement {@link #getTableCreationUniqueConstraintsFragment(Table, SqlStringGenerationContext)} instead. + */ + @Deprecated + default String getTableCreationUniqueConstraintsFragment(Table table) { + throw new IllegalStateException("getTableCreationUniqueConstraintsFragment(...) was not implemented!"); + } /** * Get the fragment that can be used to apply unique constraints as part of table creation. The implementation @@ -60,7 +95,22 @@ public interface UniqueDelegate { * @return The fragment, typically in the form {@code ", unique(col1, col2), unique( col20)"}. NOTE: The leading * comma is important! */ - public String getTableCreationUniqueConstraintsFragment(Table table, SqlStringGenerationContext context); + default String getTableCreationUniqueConstraintsFragment(Table table, SqlStringGenerationContext context) { + return getTableCreationUniqueConstraintsFragment( table ); + } + + /** + * Get the SQL ALTER TABLE command to be used to create the given UniqueKey. + * + * @param uniqueKey The UniqueKey instance. Contains all information about the columns + * @param metadata Access to the bootstrap mapping information + * @return The ALTER TABLE command + * @deprecated Implement {@link #getAlterTableToAddUniqueKeyCommand(UniqueKey, Metadata, SqlStringGenerationContext)} instead. + */ + @Deprecated + default String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata) { + throw new IllegalStateException("getAlterTableToAddUniqueKeyCommand(...) was not implemented!"); + } /** * Get the SQL ALTER TABLE command to be used to create the given UniqueKey. @@ -70,8 +120,23 @@ public interface UniqueDelegate { * @param context A context for SQL string generation * @return The ALTER TABLE command */ - public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, - SqlStringGenerationContext context); + default String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, + SqlStringGenerationContext context) { + return getAlterTableToAddUniqueKeyCommand( uniqueKey, metadata ); + } + + /** + * Get the SQL ALTER TABLE command to be used to drop the given UniqueKey. + * + * @param uniqueKey The UniqueKey instance. Contains all information about the columns + * @param metadata Access to the bootstrap mapping information + * @return The ALTER TABLE command + * @deprecated Implement {@link #getAlterTableToDropUniqueKeyCommand(UniqueKey, Metadata, SqlStringGenerationContext)} instead. + */ + @Deprecated + default String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata) { + throw new IllegalStateException("getAlterTableToDropUniqueKeyCommand(...) was not implemented!"); + } /** * Get the SQL ALTER TABLE command to be used to drop the given UniqueKey. @@ -81,7 +146,9 @@ public String getAlterTableToAddUniqueKeyCommand(UniqueKey uniqueKey, Metadata m * @param context A context for SQL string generation * @return The ALTER TABLE command */ - public String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, - SqlStringGenerationContext context); + default String getAlterTableToDropUniqueKeyCommand(UniqueKey uniqueKey, Metadata metadata, + SqlStringGenerationContext context) { + return getAlterTableToDropUniqueKeyCommand( uniqueKey, metadata ); + } } From 1afcae2f2aec782664d5068f088d31ae9b215271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 4 Feb 2022 11:13:33 +0100 Subject: [PATCH 006/201] HHH-15069 Fix backwards-incompatible changes for implementors of BulkInsertionCapableIdentifierGenerator --- .../BulkInsertionCapableIdentifierGenerator.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/id/BulkInsertionCapableIdentifierGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/BulkInsertionCapableIdentifierGenerator.java index 371b8d0f7735..21c592d834d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/BulkInsertionCapableIdentifierGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/BulkInsertionCapableIdentifierGenerator.java @@ -7,6 +7,7 @@ package org.hibernate.id; import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.dialect.Dialect; /** * Specialized contract for {@link IdentifierGenerator} implementations capable of being used in conjunction @@ -32,5 +33,18 @@ public interface BulkInsertionCapableIdentifierGenerator extends IdentifierGener * * @return The identifier value generation fragment (SQL). {@code null} indicates that no fragment is needed. */ - public String determineBulkInsertionIdentifierGenerationSelectFragment(SqlStringGenerationContext context); + default String determineBulkInsertionIdentifierGenerationSelectFragment(Dialect dialect) { + throw new IllegalStateException("determineBulkInsertionIdentifierGenerationSelectFragment(...) was not implemented!"); + } + + /** + * Return the select expression fragment, if any, that generates the identifier values. + * + * @param context A context for SQL string generation. + * + * @return The identifier value generation fragment (SQL). {@code null} indicates that no fragment is needed. + */ + default String determineBulkInsertionIdentifierGenerationSelectFragment(SqlStringGenerationContext context) { + return determineBulkInsertionIdentifierGenerationSelectFragment( context.getDialect() ); + } } From 36050cb3fd59e8b2711032ac5e230592fd793e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 4 Feb 2022 11:21:58 +0100 Subject: [PATCH 007/201] HHH-15069 Fix backwards-incompatible changes for implementors of CompositeNestedGeneratedValueGenerator.GenerationPlan --- .../hibernate/id/CompositeNestedGeneratedValueGenerator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java index dd730989aea5..5ee2a9eaa94a 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java @@ -79,7 +79,8 @@ public interface GenerationPlan extends ExportableProducer { * * @param context A context to help generate SQL strings */ - void initialize(SqlStringGenerationContext context); + default void initialize(SqlStringGenerationContext context) { + } /** * Execute the value generation. From 4eb683b3573ef1abd2a1e2da802fe85259de5fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 4 Feb 2022 12:06:25 +0100 Subject: [PATCH 008/201] HHH-15069 Fix backwards-incompatible changes for callers of RelationalModel --- .../SqlStringGenerationContextImpl.java | 51 +++++++++++++++++-- .../hibernate/mapping/RelationalModel.java | 15 ++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java index 1f6fa93d3a4c..3cfec4057aec 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java @@ -6,6 +6,7 @@ */ package org.hibernate.boot.model.relational.internal; +import java.sql.SQLException; import java.util.Map; import org.hibernate.boot.model.naming.Identifier; @@ -17,13 +18,18 @@ import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.env.internal.QualifiedObjectNameFormatterStandardImpl; import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; +import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport; import org.hibernate.engine.jdbc.env.spi.QualifiedObjectNameFormatter; +import org.jboss.logging.Logger; + public class SqlStringGenerationContextImpl implements SqlStringGenerationContext { + private static final Logger log = Logger.getLogger( SqlStringGenerationContextImpl.class ); /** * @param jdbcEnvironment The JDBC environment, to extract the dialect, identifier helper, etc. @@ -67,6 +73,37 @@ public static SqlStringGenerationContext fromExplicit(JdbcEnvironment jdbcEnviro return new SqlStringGenerationContextImpl( jdbcEnvironment, actualDefaultCatalog, actualDefaultSchema ); } + /** + * @param dialect The dialect to use. + * @param defaultCatalog The default catalog to use. + * @param defaultSchema The default schema to use. + * @return An {@link SqlStringGenerationContext}. + * @deprecated Only use for backwards compatibility in deprecated methods. + * New methods should take the {@link SqlStringGenerationContext} as an argument, + * and should not need to create their own context. + */ + @Deprecated + public static SqlStringGenerationContext forBackwardsCompatibility(Dialect dialect, String defaultCatalog, String defaultSchema) { + NameQualifierSupport nameQualifierSupport = dialect.getNameQualifierSupport(); + if ( nameQualifierSupport == null ) { + // assume both catalogs and schemas are supported + nameQualifierSupport = NameQualifierSupport.BOTH; + } + QualifiedObjectNameFormatter qualifiedObjectNameFormatter = + new QualifiedObjectNameFormatterStandardImpl( nameQualifierSupport ); + + Identifier actualDefaultCatalog = null; + if ( nameQualifierSupport.supportsCatalogs() ) { + actualDefaultCatalog = Identifier.toIdentifier( defaultCatalog ); + } + Identifier actualDefaultSchema = null; + if ( nameQualifierSupport.supportsSchemas() ) { + actualDefaultSchema = Identifier.toIdentifier( defaultSchema ); + } + return new SqlStringGenerationContextImpl( dialect, null, qualifiedObjectNameFormatter, + actualDefaultCatalog, actualDefaultSchema ); + } + public static SqlStringGenerationContext forTests(JdbcEnvironment jdbcEnvironment) { return forTests( jdbcEnvironment, null, null ); } @@ -87,9 +124,17 @@ public static SqlStringGenerationContext forTests(JdbcEnvironment jdbcEnvironmen @SuppressWarnings("deprecation") private SqlStringGenerationContextImpl(JdbcEnvironment jdbcEnvironment, Identifier defaultCatalog, Identifier defaultSchema) { - this.dialect = jdbcEnvironment.getDialect(); - this.identifierHelper = jdbcEnvironment.getIdentifierHelper(); - this.qualifiedObjectNameFormatter = jdbcEnvironment.getQualifiedObjectNameFormatter(); + this( jdbcEnvironment.getDialect(), jdbcEnvironment.getIdentifierHelper(), + jdbcEnvironment.getQualifiedObjectNameFormatter(), + defaultCatalog, defaultSchema ); + } + + private SqlStringGenerationContextImpl(Dialect dialect, IdentifierHelper identifierHelper, + QualifiedObjectNameFormatter qualifiedObjectNameFormatter, + Identifier defaultCatalog, Identifier defaultSchema) { + this.dialect = dialect; + this.identifierHelper = identifierHelper; + this.qualifiedObjectNameFormatter = qualifiedObjectNameFormatter; this.defaultCatalog = defaultCatalog; this.defaultSchema = defaultSchema; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/RelationalModel.java b/hibernate-core/src/main/java/org/hibernate/mapping/RelationalModel.java index 3f011773febd..79b68f4ec1e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/RelationalModel.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/RelationalModel.java @@ -7,6 +7,8 @@ package org.hibernate.mapping; import org.hibernate.HibernateException; import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; +import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.Mapping; /** @@ -17,6 +19,19 @@ */ @Deprecated public interface RelationalModel { + @Deprecated + default String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) throws HibernateException { + return sqlCreateString( p, SqlStringGenerationContextImpl.forBackwardsCompatibility( dialect, defaultCatalog, defaultSchema ), + defaultCatalog, defaultSchema ); + } + String sqlCreateString(Mapping p, SqlStringGenerationContext context, String defaultCatalog, String defaultSchema) throws HibernateException; + + @Deprecated + default String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) throws HibernateException { + return sqlDropString( SqlStringGenerationContextImpl.forBackwardsCompatibility( dialect, defaultCatalog, defaultSchema ), + defaultCatalog, defaultSchema ); + } + String sqlDropString(SqlStringGenerationContext context, String defaultCatalog, String defaultSchema); } From 3c7c6f34aea307d999c02448be470c482a2449ba Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 10 Feb 2022 11:41:20 -0600 Subject: [PATCH 009/201] back-port JUnit5 based testing support --- documentation/documentation.gradle | 4 +- gradle/libraries.gradle | 12 +- .../internal/util/ReflectHelper.java | 2 +- .../main/java/org/hibernate/sql/JoinType.java | 23 +- .../org/hibernate/tool/schema/Action.java | 8 + .../spi/SchemaManagementToolCoordinator.java | 84 +++- hibernate-testing/hibernate-testing.gradle | 14 +- .../ExtraJavaServicesClassLoaderService.java | 73 +++ .../testing/jdbc/SQLStatementInspector.java | 105 ++++ .../testing/logger/LogInspectionHelper.java | 6 +- .../domain/AbstractDomainModelDescriptor.java | 34 ++ .../orm/domain/DomainModelDescriptor.java | 62 +++ .../testing/orm/domain/MappingFeature.java | 55 +++ .../orm/domain/MonetaryAmountConverter.java | 34 ++ .../orm/domain/StandardDomainModel.java | 34 ++ .../testing/orm/domain/animal/Address.java | 111 +++++ .../testing/orm/domain/animal/Animal.java | 124 +++++ .../orm/domain/animal/AnimalDomainModel.java | 44 ++ .../testing/orm/domain/animal/Cat.java | 16 + .../orm/domain/animal/Classification.java | 28 ++ .../testing/orm/domain/animal/Dog.java | 16 + .../orm/domain/animal/DomesticAnimal.java | 28 ++ .../testing/orm/domain/animal/Human.java | 170 +++++++ .../testing/orm/domain/animal/Lizard.java | 15 + .../testing/orm/domain/animal/Mammal.java | 67 +++ .../testing/orm/domain/animal/Name.java | 56 +++ .../testing/orm/domain/animal/PettingZoo.java | 16 + .../testing/orm/domain/animal/Reptile.java | 22 + .../orm/domain/animal/StateProvince.java | 79 +++ .../testing/orm/domain/animal/Zoo.java | 145 ++++++ .../orm/domain/animal/package-info.java | 11 + .../testing/orm/domain/contacts/Address.java | 83 ++++ .../testing/orm/domain/contacts/Contact.java | 145 ++++++ .../domain/contacts/ContactsDomainModel.java | 30 ++ .../orm/domain/contacts/PhoneNumber.java | 72 +++ .../orm/domain/gambit/BasicEntity.java | 65 +++ .../testing/orm/domain/gambit/Component.java | 129 +++++ .../orm/domain/gambit/EmbeddedIdEntity.java | 97 ++++ .../orm/domain/gambit/EntityOfArrays.java | 66 +++ .../orm/domain/gambit/EntityOfBasics.java | 336 +++++++++++++ .../orm/domain/gambit/EntityOfComposites.java | 66 +++ .../gambit/EntityOfDynamicComponent.java | 62 +++ .../orm/domain/gambit/EntityOfLists.java | 219 +++++++++ .../orm/domain/gambit/EntityOfMaps.java | 453 ++++++++++++++++++ .../orm/domain/gambit/EntityOfSets.java | 345 +++++++++++++ .../domain/gambit/EntityWithAggregateId.java | 79 +++ .../EntityWithLazyManyToOneSelfReference.java | 95 ++++ .../domain/gambit/EntityWithLazyOneToOne.java | 68 +++ .../gambit/EntityWithManyToOneJoinTable.java | 89 ++++ .../EntityWithManyToOneSelfReference.java | 94 ++++ .../EntityWithManyToOneWithoutJoinTable.java | 55 +++ .../EntityWithNonIdAttributeNamedId.java | 37 ++ .../gambit/EntityWithNotAggregateId.java | 99 ++++ .../domain/gambit/EntityWithOneToMany.java | 97 ++++ .../gambit/EntityWithOneToManyNotOwned.java | 46 ++ .../orm/domain/gambit/EntityWithOneToOne.java | 67 +++ .../gambit/EntityWithOneToOneJoinTable.java | 71 +++ .../EntityWithOneToOneSharingPrimaryKey.java | 71 +++ .../testing/orm/domain/gambit/EnumValue.java | 47 ++ .../orm/domain/gambit/EnumValueConverter.java | 24 + .../orm/domain/gambit/GambitDomainModel.java | 44 ++ .../orm/domain/gambit/MutableValue.java | 33 ++ .../testing/orm/domain/gambit/Shirt.java | 109 +++++ .../gambit/SimpleBasicSortComparator.java | 20 + .../orm/domain/gambit/SimpleComponent.java | 42 ++ .../orm/domain/gambit/SimpleEntity.java | 119 +++++ .../orm/domain/gambit/VersionedEntity.java | 72 +++ .../testing/orm/domain/helpdesk/Account.java | 99 ++++ .../domain/helpdesk/HelpDeskDomainModel.java | 25 + .../testing/orm/domain/helpdesk/Incident.java | 99 ++++ .../testing/orm/domain/helpdesk/Status.java | 31 ++ .../testing/orm/domain/helpdesk/Ticket.java | 36 ++ .../orm/domain/retail/CardPayment.java | 35 ++ .../orm/domain/retail/CashPayment.java | 24 + .../orm/domain/retail/DomesticVendor.java | 24 + .../orm/domain/retail/ForeignVendor.java | 24 + .../testing/orm/domain/retail/LineItem.java | 73 +++ .../testing/orm/domain/retail/Name.java | 75 +++ .../testing/orm/domain/retail/Order.java | 66 +++ .../testing/orm/domain/retail/Payment.java | 51 ++ .../testing/orm/domain/retail/Product.java | 82 ++++ .../orm/domain/retail/RetailDomainModel.java | 61 +++ .../orm/domain/retail/SalesAssociate.java | 47 ++ .../testing/orm/domain/retail/Vendor.java | 61 +++ .../jpa/PersistenceUnitDescriptorAdapter.java | 116 +++++ .../orm/jpa/PersistenceUnitInfoAdapter.java | 105 ++++ .../orm/jpa/PersistenceUnitInfoImpl.java | 163 +++++++ .../AbstractEntityManagerFactoryScope.java | 158 ++++++ .../BaseSessionFactoryFunctionalTest.java | 376 +++++++++++++++ .../testing/orm/junit/BaseUnitTest.java | 28 ++ .../orm/junit/BootstrapServiceRegistry.java | 42 ++ .../BootstrapServiceRegistryProducer.java | 17 + .../junit/ClassLoadingIsolaterExtension.java | 56 +++ .../testing/orm/junit/DialectContext.java | 62 +++ .../orm/junit/DialectFeatureCheck.java | 18 + .../orm/junit/DialectFeatureChecks.java | 320 +++++++++++++ .../orm/junit/DialectFilterExtension.java | 122 +++++ .../testing/orm/junit/DomainModel.java | 111 +++++ .../orm/junit/DomainModelExtension.java | 295 ++++++++++++ .../junit/DomainModelFunctionalTesting.java | 38 ++ .../junit/DomainModelParameterResolver.java | 48 ++ .../orm/junit/DomainModelProducer.java | 17 + .../testing/orm/junit/DomainModelScope.java | 55 +++ .../orm/junit/DomainModelScopeAware.java | 14 + .../junit/EntityManagerFactoryExtension.java | 287 +++++++++++ ...EntityManagerFactoryParameterResolver.java | 49 ++ .../junit/EntityManagerFactoryProducer.java | 29 ++ .../orm/junit/EntityManagerFactoryScope.java | 36 ++ .../EntityManagerFactoryScopeContainer.java | 28 ++ .../EntityManagerFactoryScopeExtension.java | 116 +++++ .../testing/orm/junit/ExpectedException.java | 33 ++ .../orm/junit/ExpectedExceptionExtension.java | 44 ++ .../testing/orm/junit/ExtraAssertions.java | 82 ++++ .../testing/orm/junit/FailureExpected.java | 50 ++ .../orm/junit/FailureExpectedExtension.java | 165 +++++++ .../orm/junit/FailureExpectedGroup.java | 27 ++ ...FunctionalEntityManagerFactoryTesting.java | 42 ++ .../testing/orm/junit/JUnitHelper.java | 44 ++ .../hibernate/testing/orm/junit/JiraKey.java | 31 ++ .../testing/orm/junit/JiraKeyGroup.java | 25 + .../org/hibernate/testing/orm/junit/Jpa.java | 103 ++++ .../hibernate/testing/orm/junit/Logger.java | 30 ++ .../testing/orm/junit/LoggingInspections.java | 48 ++ .../junit/LoggingInspectionsExtension.java | 60 +++ .../orm/junit/LoggingInspectionsScope.java | 73 +++ .../LoggingInspectionsScopeResolver.java | 37 ++ .../orm/junit/MessageKeyInspection.java | 41 ++ .../junit/MessageKeyInspectionExtension.java | 97 ++++ .../testing/orm/junit/MessageKeyWatcher.java | 24 + .../orm/junit/MessageKeyWatcherImpl.java | 114 +++++ .../orm/junit/MessageKeyWatcherResolver.java | 27 ++ .../testing/orm/junit/RequiresDialect.java | 49 ++ .../orm/junit/RequiresDialectFeature.java | 54 +++ .../junit/RequiresDialectFeatureGroup.java | 37 ++ .../testing/orm/junit/RequiresDialects.java | 28 ++ .../testing/orm/junit/ServiceRegistry.java | 98 ++++ .../orm/junit/ServiceRegistryExtension.java | 336 +++++++++++++ .../ServiceRegistryFunctionalTesting.java | 34 ++ .../ServiceRegistryParameterResolver.java | 52 ++ .../orm/junit/ServiceRegistryProducer.java | 20 + .../orm/junit/ServiceRegistryScope.java | 55 +++ .../orm/junit/ServiceRegistryScopeAware.java | 14 + .../testing/orm/junit/SessionFactory.java | 59 +++ .../orm/junit/SessionFactoryExtension.java | 401 ++++++++++++++++ .../SessionFactoryFunctionalTesting.java | 46 ++ .../SessionFactoryParameterResolver.java | 33 ++ .../orm/junit/SessionFactoryProducer.java | 30 ++ .../orm/junit/SessionFactoryScope.java | 41 ++ .../orm/junit/SessionFactoryScopeAware.java | 17 + .../SessionFactoryScopeParameterResolver.java | 31 ++ .../hibernate/testing/orm/junit/Setting.java | 22 + .../testing/orm/junit/SettingProvider.java | 19 + .../testing/orm/junit/SkipForDialect.java | 46 ++ .../orm/junit/SkipForDialectGroup.java | 29 ++ .../testing/orm/junit/TestingUtil.java | 99 ++++ .../orm/transaction/TransactionUtil.java | 147 ++++++ 156 files changed, 11721 insertions(+), 36 deletions(-) create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/boot/ExtraJavaServicesClassLoaderService.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInspector.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/AbstractDomainModelDescriptor.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/DomainModelDescriptor.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MappingFeature.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MonetaryAmountConverter.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/StandardDomainModel.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Address.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Animal.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/AnimalDomainModel.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Cat.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Classification.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Dog.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/DomesticAnimal.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Human.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Lizard.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Mammal.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Name.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/PettingZoo.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Reptile.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/StateProvince.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Zoo.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/package-info.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Address.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Contact.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/ContactsDomainModel.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/PhoneNumber.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/BasicEntity.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Component.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EmbeddedIdEntity.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfArrays.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfComposites.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfDynamicComponent.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfLists.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfMaps.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfSets.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithAggregateId.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyManyToOneSelfReference.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyOneToOne.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneJoinTable.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneSelfReference.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneWithoutJoinTable.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNonIdAttributeNamedId.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNotAggregateId.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToMany.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToManyNotOwned.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOne.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneJoinTable.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneSharingPrimaryKey.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EnumValue.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EnumValueConverter.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/GambitDomainModel.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/MutableValue.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Shirt.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleBasicSortComparator.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleComponent.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleEntity.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/VersionedEntity.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Account.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/HelpDeskDomainModel.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Incident.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Status.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Ticket.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CardPayment.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CashPayment.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/DomesticVendor.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/ForeignVendor.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/LineItem.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Name.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Order.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Payment.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Product.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/RetailDomainModel.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/SalesAssociate.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Vendor.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitDescriptorAdapter.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitInfoAdapter.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitInfoImpl.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/AbstractEntityManagerFactoryScope.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseSessionFactoryFunctionalTest.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseUnitTest.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BootstrapServiceRegistry.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BootstrapServiceRegistryProducer.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ClassLoadingIsolaterExtension.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectContext.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureCheck.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFilterExtension.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModel.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelExtension.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelFunctionalTesting.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelParameterResolver.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelProducer.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScope.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScopeAware.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryExtension.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryParameterResolver.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryProducer.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScope.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScopeContainer.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScopeExtension.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedException.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedExceptionExtension.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExtraAssertions.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpected.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedExtension.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedGroup.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FunctionalEntityManagerFactoryTesting.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JUnitHelper.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKey.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKeyGroup.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Jpa.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Logger.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspections.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsExtension.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsScope.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsScopeResolver.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyInspection.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyInspectionExtension.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcher.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcherImpl.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcherResolver.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialect.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeature.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeatureGroup.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialects.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistry.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryExtension.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryFunctionalTesting.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryParameterResolver.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryProducer.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScope.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScopeAware.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactory.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryFunctionalTesting.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryParameterResolver.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryProducer.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScope.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeAware.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeParameterResolver.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Setting.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SettingProvider.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialect.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialectGroup.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/TestingUtil.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/orm/transaction/TransactionUtil.java diff --git a/documentation/documentation.gradle b/documentation/documentation.gradle index 275e27527a03..15f00ab5230a 100644 --- a/documentation/documentation.gradle +++ b/documentation/documentation.gradle @@ -216,10 +216,10 @@ task buildTutorialZip(type: Zip) { from 'src/main/asciidoc/quickstart/tutorials' destinationDir = tasks.renderGettingStartedGuides.outputDir archiveName = 'hibernate-tutorials.zip' - expand( + expand( version: project.version, slf4j: "1.7.5", - junit: project.junitVersion, + junit: project.junit4Version, h2: project.h2Version ) } diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index ba9581f1d5ab..1cbb98fc34b6 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -8,8 +8,10 @@ // build a map of the dependency artifacts to use. Allows centralized definition of the version of artifacts to // use. In that respect it serves a role similar to in Maven ext { + junit5Version = '5.8.2' + junitVintageVersion = junit5Version + junit4Version = '4.13.2' - junitVersion = '4.13.2' h2Version = '1.4.197' bytemanVersion = '4.0.16' //Compatible with JDK 17 jnpVersion = '5.0.6.CR1' @@ -116,8 +118,14 @@ ext { // ~~~~~~~~~~~~~~~~~~~~~~~~~~ testing + junit5_api: "org.junit.jupiter:junit-jupiter-api:${junit5Version}", + junit5_jupiter: "org.junit.jupiter:junit-jupiter-engine:${junit5Version}", + junit5_params : "org.junit.jupiter:junit-jupiter-params:${junit5Version}", + junit: "junit:junit:${junit4Version}", + junit5_vintage: "org.junit.vintage:junit-vintage-engine:${junitVintageVersion}", + log4j2: "org.apache.logging.log4j:log4j-core:2.17.1", - junit: "junit:junit:${junitVersion}", + byteman: "org.jboss.byteman:byteman:${bytemanVersion}", byteman_install: "org.jboss.byteman:byteman-install:${bytemanVersion}", byteman_bmunit: "org.jboss.byteman:byteman-bmunit:${bytemanVersion}", diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java index fd4ab26ba813..66e82fd20800 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java @@ -435,7 +435,7 @@ private static Field locateField(Class clazz, String propertyName) { } } - private static boolean isStaticField(Field field) { + public static boolean isStaticField(Field field) { return field != null && ( field.getModifiers() & Modifier.STATIC ) == Modifier.STATIC; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/JoinType.java b/hibernate-core/src/main/java/org/hibernate/sql/JoinType.java index 0155e5a65bc8..2f0afe281da3 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/JoinType.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/JoinType.java @@ -13,21 +13,28 @@ */ public enum JoinType { - NONE( -666 ), - INNER_JOIN( 0 ), - LEFT_OUTER_JOIN( 1 ), - RIGHT_OUTER_JOIN( 2 ), - FULL_JOIN( 4 ); - private int joinTypeValue; - - JoinType(int joinTypeValue) { + NONE( -666, null ), + INNER_JOIN( 0, "inner" ), + LEFT_OUTER_JOIN( 1, "left" ), + RIGHT_OUTER_JOIN( 2, "right" ), + FULL_JOIN( 4, "full" ); + + private final int joinTypeValue; + private final String sqlText; + + JoinType(int joinTypeValue, String sqlText) { this.joinTypeValue = joinTypeValue; + this.sqlText = sqlText; } public int getJoinTypeValue() { return joinTypeValue; } + public String getSqlText() { + return sqlText; + } + public static JoinType parse(int joinType) { if ( joinType < 0 ) { return NONE; diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java index 8265628b3224..cb7ed8d2a51f 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java @@ -76,6 +76,14 @@ public enum Action { this.externalHbm2ddlName = externalHbm2ddlName; } + public String getExternalJpaName() { + return externalJpaName; + } + + public String getExternalHbm2ddlName() { + return externalHbm2ddlName; + } + @Override public String toString() { return getClass().getSimpleName() + "(externalJpaName=" + externalJpaName + ", externalHbm2ddlName=" + externalHbm2ddlName + ")"; diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java index 4c0c33807cb9..a35a47f835af 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java @@ -7,7 +7,9 @@ package org.hibernate.tool.schema.spi; import java.util.EnumSet; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.hibernate.boot.Metadata; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; @@ -41,6 +43,8 @@ import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_SCRIPTS_ACTION; import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_SCRIPTS_CREATE_TARGET; import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_SCRIPTS_DROP_TARGET; +import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER; +import static org.hibernate.internal.util.NullnessHelper.coalesceSuppliedValues; /** * Responsible for coordinating SchemaManagementTool execution(s) for auto-tooling whether @@ -527,27 +531,71 @@ public Action getScriptAction() { } public static ActionGrouping interpret(Map configurationValues) { - Object databaseActionSetting = configurationValues.get( HBM2DDL_DATABASE_ACTION ); - Object scriptsActionSetting = configurationValues.get( HBM2DDL_SCRIPTS_ACTION ); - if ( databaseActionSetting == null ) { - databaseActionSetting = configurationValues.get( JAKARTA_HBM2DDL_DATABASE_ACTION ); - } - if ( scriptsActionSetting == null ) { - scriptsActionSetting = configurationValues.get( JAKARTA_HBM2DDL_SCRIPTS_ACTION ); - } - // interpret the JPA settings first - Action databaseAction = Action.interpretJpaSetting( databaseActionSetting ); - Action scriptAction = Action.interpretJpaSetting( scriptsActionSetting ); - - // if no JPA settings were specified, look at the legacy HBM2DDL_AUTO setting... - if ( databaseAction == Action.NONE && scriptAction == Action.NONE ) { - final Action hbm2ddlAutoAction = Action.interpretHbm2ddlSetting( configurationValues.get( HBM2DDL_AUTO ) ); - if ( hbm2ddlAutoAction != Action.NONE ) { - databaseAction = hbm2ddlAutoAction; + // default to the JPA settings + Action databaseActionToUse = determineJpaDbActionSetting( configurationValues ); + Action scriptActionToUse = determineJpaScriptActionSetting( configurationValues ); + Action autoAction = determineAutoSettingImpliedAction( configurationValues, null ); + + if ( databaseActionToUse == null && scriptActionToUse == null ) { + // no JPA (jakarta nor javax) settings were specified, use the legacy Hibernate + // `hbm2ddl.auto` setting to possibly set the database-action + if ( autoAction != null ) { + databaseActionToUse = autoAction; } } - return new ActionGrouping( databaseAction, scriptAction ); + if ( databaseActionToUse == null ) { + databaseActionToUse = Action.NONE; + } + + if ( scriptActionToUse == null ) { + scriptActionToUse = Action.NONE; + } + + if ( databaseActionToUse == Action.NONE && scriptActionToUse == Action.NONE ) { + log.debugf( "No schema actions specified" ); + } + + return new ActionGrouping( databaseActionToUse, scriptActionToUse ); + } + + private static Action determineJpaDbActionSetting(Map configurationValues) { + final Object scriptsActionSetting = coalesceSuppliedValues( + () -> configurationValues.get( JAKARTA_HBM2DDL_DATABASE_ACTION ), + () -> { + final Object setting = configurationValues.get( HBM2DDL_DATABASE_ACTION ); + if ( setting != null ) { + DEPRECATION_LOGGER.deprecatedSetting( HBM2DDL_DATABASE_ACTION, JAKARTA_HBM2DDL_DATABASE_ACTION ); + } + return setting; + } + ); + + return scriptsActionSetting == null ? null : Action.interpretJpaSetting( scriptsActionSetting ); + } + + private static Action determineJpaScriptActionSetting(Map configurationValues) { + final Object scriptsActionSetting = coalesceSuppliedValues( + () -> configurationValues.get( JAKARTA_HBM2DDL_SCRIPTS_ACTION ), + () -> { + final Object setting = configurationValues.get( HBM2DDL_SCRIPTS_ACTION ); + if ( setting != null ) { + DEPRECATION_LOGGER.deprecatedSetting( HBM2DDL_SCRIPTS_ACTION, JAKARTA_HBM2DDL_SCRIPTS_ACTION ); + } + return setting; + } + ); + + return scriptsActionSetting == null ? null : Action.interpretJpaSetting( scriptsActionSetting ); + } + + public static Action determineAutoSettingImpliedAction(Map settings, Action defaultValue) { + final Object autoActionSetting = settings.get( HBM2DDL_AUTO ); + if ( autoActionSetting == null ) { + return defaultValue; + } + + return Action.interpretHbm2ddlSetting( autoActionSetting ); } } } diff --git a/hibernate-testing/hibernate-testing.gradle b/hibernate-testing/hibernate-testing.gradle index 0f39d04069a5..ecf47ebc8094 100644 --- a/hibernate-testing/hibernate-testing.gradle +++ b/hibernate-testing/hibernate-testing.gradle @@ -13,12 +13,22 @@ apply from: rootProject.file( 'gradle/published-java-module.gradle' ) dependencies { compile project( ':hibernate-core' ) compile( libraries.jta ) - compile( libraries.junit ) + + compile libraries.junit + compile libraries.junit5_api + compile libraries.junit5_params + + compile libraries.assertj + + compile libraries.log4j2 + + compile 'javax.money:money-api:1.0.1' + compile 'org.javamoney:moneta:1.1' + compile( libraries.byteman ) compile( libraries.byteman_install ) compile( libraries.byteman_bmunit ) compile( libraries.xapool ) - compile( libraries.log4j2 ) compile( libraries.jboss_tx_spi ) { transitive=false; } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/boot/ExtraJavaServicesClassLoaderService.java b/hibernate-testing/src/main/java/org/hibernate/testing/boot/ExtraJavaServicesClassLoaderService.java new file mode 100644 index 000000000000..70dbfc66c1c4 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/boot/ExtraJavaServicesClassLoaderService.java @@ -0,0 +1,73 @@ +/* + * 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 + */ +package org.hibernate.testing.boot; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl; + +/** + * @author Steve Ebersole + */ +public class ExtraJavaServicesClassLoaderService extends ClassLoaderServiceImpl { + private final List> extraJavaServices; + + public ExtraJavaServicesClassLoaderService(List> extraJavaServices) { + this.extraJavaServices = extraJavaServices; + } + + @Override + public Collection loadJavaServices(Class serviceContract) { + final Collection baseServices = super.loadJavaServices( serviceContract ); + final List services = new ArrayList<>( baseServices ); + + applyExtraJavaServices( serviceContract, services ); + + return services; + } + + private void applyExtraJavaServices(Class serviceContract, List services) { + extraJavaServices.forEach( + (javaServiceDescriptor) -> { + if ( serviceContract.isAssignableFrom( javaServiceDescriptor.role ) ) { + try { + final Object serviceInstance = javaServiceDescriptor.impl.getDeclaredConstructor().newInstance(); + //noinspection unchecked + services.add( (S) serviceInstance ); + } + catch (NoSuchMethodException | IllegalAccessException e) { + throw new RuntimeException( "Unable to access constructor for specified 'extra' Java service : " + javaServiceDescriptor.impl.getName(), e ); + } + catch (InstantiationException | InvocationTargetException e) { + throw new RuntimeException( "Unable to instantiate specified 'extra' Java service : " + javaServiceDescriptor.impl.getName(), e ); + } + } + } + ); + } + + public static class JavaServiceDescriptor { + private final Class role; + private final Class impl; + + public JavaServiceDescriptor(Class role, Class impl) { + this.role = role; + this.impl = impl; + } + + public Class getRole() { + return role; + } + + public Class getImpl() { + return impl; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInspector.java b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInspector.java new file mode 100644 index 000000000000..231d5098f100 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SQLStatementInspector.java @@ -0,0 +1,105 @@ +/* + * 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 + */ +package org.hibernate.testing.jdbc; + +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.resource.jdbc.spi.StatementInspector; +import org.hibernate.sql.JoinType; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * @author Andrea Boriero + */ +public class SQLStatementInspector implements StatementInspector { + private final List sqlQueries = new LinkedList<>(); + + public SQLStatementInspector() { + } + + @Override + public String inspect(String sql) { + sqlQueries.add( sql ); + return sql; + } + + public List getSqlQueries() { + return sqlQueries; + } + + public void clear() { + sqlQueries.clear(); + } + + public int getNumberOfJoins(int position) { + final String sql = sqlQueries.get( position ); + String fromPart = sql.toLowerCase( Locale.ROOT ).split( " from " )[1].split( " where " )[0]; + return fromPart.split( "(\\sjoin\\s|,\\s)", -1 ).length - 1; + } + + public void assertExecuted(String expected) { + assertTrue( sqlQueries.contains( expected ) ); + } + + public void assertNumberOfJoins(int queryNumber, int expectedNumberOfJoins) { + assertNumberOfOccurrenceInQuery( queryNumber, "join", expectedNumberOfJoins ); + } + + public void assertExecutedCount(int expected) { + assertEquals( "Number of executed statements ",expected, sqlQueries.size() ); + } + + public void assertNumberOfJoins(int queryNumber, JoinType joinType, int expectedNumberOfOccurrences) { + String query = sqlQueries.get( queryNumber ); + String[] parts = query.split( " join " ); + int actual = getCount( parts, joinType ); + assertThat( "number of " + joinType + "join", actual, is( expectedNumberOfOccurrences ) ); + } + + private int getCount(String[] parts, JoinType joinType) { + final int end = parts.length - 1; + int count = 0; + for ( int i = 0; i < end; i++ ) { + if ( parts[i].endsWith( " " + joinType.getSqlText() ) ) { + count++; + } + } + return count; + } + + public void assertNumberOfOccurrenceInQuery(int queryNumber, String toCheck, int expectedNumberOfOccurrences) { + String query = sqlQueries.get( queryNumber ); + int actual = query.split( " " + toCheck + " ", -1 ).length - 1; + assertThat( "number of " + toCheck, actual, is( expectedNumberOfOccurrences ) ); + } + + public void assertIsSelect(int queryNumber) { + String query = sqlQueries.get( queryNumber ); + assertTrue( query.toLowerCase( Locale.ROOT ).startsWith( "select" ) ); + } + + public void assertIsInsert(int queryNumber) { + String query = sqlQueries.get( queryNumber ); + assertTrue( query.toLowerCase( Locale.ROOT ).startsWith( "insert" ) ); + } + + public void assertIsUpdate(int queryNumber) { + String query = sqlQueries.get( queryNumber ); + assertTrue( query.toLowerCase( Locale.ROOT ).startsWith( "update" ) ); + } + + public static SQLStatementInspector extractFromSession(SessionImplementor session) { + return (SQLStatementInspector) session.getJdbcSessionContext().getStatementInspector(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/logger/LogInspectionHelper.java b/hibernate-testing/src/main/java/org/hibernate/testing/logger/LogInspectionHelper.java index a81e78bbf9a7..2892991d0795 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/logger/LogInspectionHelper.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/logger/LogInspectionHelper.java @@ -19,16 +19,16 @@ * * @author Sanne Grinovero (C) 2015 Red Hat Inc. */ -final class LogInspectionHelper { +public final class LogInspectionHelper { private LogInspectionHelper() { } - static void registerListener(LogListener listener, BasicLogger log) { + public static void registerListener(LogListener listener, BasicLogger log) { convertType( log ).registerListener( listener ); } - static void clearAllListeners(BasicLogger log) { + public static void clearAllListeners(BasicLogger log) { convertType( log ).clearAllListeners(); } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/AbstractDomainModelDescriptor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/AbstractDomainModelDescriptor.java new file mode 100644 index 000000000000..f09692aecbae --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/AbstractDomainModelDescriptor.java @@ -0,0 +1,34 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain; + +import org.hibernate.boot.MetadataSources; + +/** + * Convenience base class test domain models based on annotated classes + * + * @author Steve Ebersole + */ +public abstract class AbstractDomainModelDescriptor implements DomainModelDescriptor { + private final Class[] annotatedClasses; + + protected AbstractDomainModelDescriptor(Class... annotatedClasses) { + this.annotatedClasses = annotatedClasses; + } + + @Override + public Class[] getAnnotatedClasses() { + return annotatedClasses; + } + + @Override + public void applyDomainModel(MetadataSources sources) { + for ( Class annotatedClass : annotatedClasses ) { + sources.addAnnotatedClass( annotatedClass ); + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/DomainModelDescriptor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/DomainModelDescriptor.java new file mode 100644 index 000000000000..4d5eb852ea07 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/DomainModelDescriptor.java @@ -0,0 +1,62 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain; + +import java.util.EnumSet; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.dialect.Dialect; + +/** + * Describes a standard domain model + * + * @see StandardDomainModel + * @see org.hibernate.testing.orm.junit.DomainModel + * @see org.hibernate.testing.orm.junit.DomainModelFunctionalTesting + * @see org.hibernate.testing.orm.junit.DomainModelExtension + * + * @author Steve Ebersole + */ +public interface DomainModelDescriptor { + + Class[] getAnnotatedClasses(); + + /** + * Apply the model classes to the given MetadataSources + */ + void applyDomainModel(MetadataSources sources); + + /** + * The namespace to apply the model to. This is interpreted as a catalog + * name or a schema name depending on the capability of the underlying database + * via {@link Dialect}. Would require a new Dialect method I think, though + * we could also leverage the driver's db-metadata to ascertain which interpretation + * to use which would not need any (more) test-specific Dialect feature. + * + * Note however that this might be a useful feature as well for users instead of + * JPA's {@link javax.persistence.Table#catalog} / {@link javax.persistence.Table#schema}. + * AKA, something like `@org.hibernate.annotations.Namespace("a_name")` or + * `@org.hibernate.annotations.Table( namespace="a_name", ... )`. + * + * This may be {@code null} indicating that the default namespace should be used. + * + * Note that domain models can use the same namespace so long as they do not share + * db-object (tables, etc) names + */ + default String getNamespace() { + return null; + } + + /** + * Identifies the specific mapping features this domain model uses. + */ + default EnumSet getMappingFeaturesUsed() { + // for now just return none. this is simply informative, not used to + // drive any functionality - so maybe it's not important to add + return EnumSet.noneOf( MappingFeature.class ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MappingFeature.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MappingFeature.java new file mode 100644 index 000000000000..536992fa6ce7 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MappingFeature.java @@ -0,0 +1,55 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain; + +import java.util.EnumSet; + +/** + * Identifies specific mapping features used by a {@link DomainModelDescriptor}. + * + * The intent is to help categorize which models use specific mapping features + * to help facilitate testing various outcomes based on those features. + * + * For example, when writing a test that depends on JPA's {@link javax.persistence.AttributeConverter}, + * we could just see which DomainModel reports using {@link #CONVERTER} and re-use that + * model. + * + * @author Steve Ebersole + */ +public enum MappingFeature { + CONVERTER, + ENUMERATED, + DYNAMIC_MODEL, + + DISCRIMINATOR_INHERIT, + JOINED_INHERIT, + UNION_INHERIT, + + SECONDARY_TABLE, + + AGG_COMP_ID, + NON_AGG_COMP_ID, + ID_CLASS, + + EMBEDDABLE, + MANY_ONE, + ONE_ONE, + ONE_MANY, + MANY_MANY, + ANY, + MANY_ANY, + + COLLECTION_TABLE, + JOIN_TABLE, + JOIN_COLUMN, + + ; + + public static EnumSet all() { + return EnumSet.allOf( MappingFeature.class ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MonetaryAmountConverter.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MonetaryAmountConverter.java new file mode 100644 index 000000000000..b529f2bc7778 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MonetaryAmountConverter.java @@ -0,0 +1,34 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain; + +import java.util.Locale; +import javax.money.Monetary; +import javax.money.MonetaryAmount; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +/** + * @author Steve Ebersole + */ +@Converter( autoApply = true ) +public class MonetaryAmountConverter implements AttributeConverter { + @Override + public Double convertToDatabaseColumn(MonetaryAmount attribute) { + return attribute.getNumber().numberValueExact( Double.class ); + } + + @Override + public MonetaryAmount convertToEntityAttribute(Double dbData) { + if ( dbData == null ) { + return null; + } + + return Monetary.getDefaultAmountFactory().setNumber( dbData ).setCurrency( Monetary.getCurrency( Locale.US ) ).create(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/StandardDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/StandardDomainModel.java new file mode 100644 index 000000000000..f5b3e67d5dca --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/StandardDomainModel.java @@ -0,0 +1,34 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain; + +import org.hibernate.testing.orm.domain.animal.AnimalDomainModel; +import org.hibernate.testing.orm.domain.contacts.ContactsDomainModel; +import org.hibernate.testing.orm.domain.gambit.GambitDomainModel; +import org.hibernate.testing.orm.domain.helpdesk.HelpDeskDomainModel; +import org.hibernate.testing.orm.domain.retail.RetailDomainModel; + +/** + * @author Steve Ebersole + */ +public enum StandardDomainModel { + CONTACTS( ContactsDomainModel.INSTANCE ), + ANIMAL( AnimalDomainModel.INSTANCE ), + GAMBIT( GambitDomainModel.INSTANCE ), + HELPDESK( HelpDeskDomainModel.INSTANCE ), + RETAIL( RetailDomainModel.INSTANCE ); + + private final DomainModelDescriptor domainModel; + + StandardDomainModel(DomainModelDescriptor domainModel) { + this.domainModel = domainModel; + } + + public DomainModelDescriptor getDescriptor() { + return domainModel; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Address.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Address.java new file mode 100644 index 000000000000..fd187cb2f873 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Address.java @@ -0,0 +1,111 @@ +/* + * 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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import javax.persistence.Embeddable; + +@Embeddable +public class Address { + private String street; + private String city; + private String postalCode; + private String country; +// private StateProvince stateProvince; + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getPostalCode() { + return postalCode; + } + + public void setPostalCode(String postalCode) { + this.postalCode = postalCode; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + +// @ManyToOne +// @JoinColumn( name = "state_prov_fk" ) +// public StateProvince getStateProvince() { +// return stateProvince; +// } +// +// public void setStateProvince(StateProvince stateProvince) { +// this.stateProvince = stateProvince; +// } + +// @Override +// public boolean equals(Object o) { +// if ( this == o ) { +// return true; +// } +// if ( o == null || getClass() != o.getClass() ) { +// return false; +// } +// +// Address address = ( Address ) o; +// +// if ( city != null ? !city.equals( address.city ) : address.city != null ) { +// return false; +// } +// if ( country != null ? !country.equals( address.country ) : address.country != null ) { +// return false; +// } +// if ( postalCode != null ? !postalCode.equals( address.postalCode ) : address.postalCode != null ) { +// return false; +// } +// if ( stateProvince != null ? !stateProvince.equals( address.stateProvince ) : address.stateProvince != null ) { +// return false; +// } +// if ( street != null ? !street.equals( address.street ) : address.street != null ) { +// return false; +// } +// +// return true; +// } +// +// @Override +// public int hashCode() { +// int result = street != null ? street.hashCode() : 0; +// result = 31 * result + ( city != null ? city.hashCode() : 0 ); +// result = 31 * result + ( postalCode != null ? postalCode.hashCode() : 0 ); +// result = 31 * result + ( country != null ? country.hashCode() : 0 ); +// result = 31 * result + ( stateProvince != null ? stateProvince.hashCode() : 0 ); +// return result; +// } +// +// @Override +// public String toString() { +// return "Address{" + +// "street='" + street + '\'' + +// ", city='" + city + '\'' + +// ", postalCode='" + postalCode + '\'' + +// ", country='" + country + '\'' + +// ", stateProvince=" + stateProvince + +// '}'; +// } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Animal.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Animal.java new file mode 100644 index 000000000000..fd5b6f4d6fd4 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Animal.java @@ -0,0 +1,124 @@ +/* + * 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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; + +@Entity +@Inheritance( strategy = InheritanceType.JOINED ) +public class Animal { + private Long id; + private float bodyWeight; + private Set offspring; + private Animal mother; + private Animal father; + private String description; + private Zoo zoo; + private String serialNumber; + + public Animal() { + } + + public Animal(String description, float bodyWeight) { + this.description = description; + this.bodyWeight = bodyWeight; + } + + @Id + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Column( name = "body_weight" ) + public float getBodyWeight() { + return bodyWeight; + } + + public void setBodyWeight(float bodyWeight) { + this.bodyWeight = bodyWeight; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + @ManyToOne + @JoinColumn( name = "zoo_fk" ) + public Zoo getZoo() { + return zoo; + } + + public void setZoo(Zoo zoo) { + this.zoo = zoo; + } + + @ManyToOne + @JoinColumn( name = "mother_fk" ) + public Animal getMother() { + return mother; + } + + public void setMother(Animal mother) { + this.mother = mother; + } + + @ManyToOne + @JoinColumn( name = "father_fk" ) + public Animal getFather() { + return father; + } + + public void setFather(Animal father) { + this.father = father; + } + + @OneToMany + @JoinColumn( name = "mother_fk" ) + @OrderBy( "father_fk" ) + public Set getOffspring() { + return offspring; + } + + public void addOffspring(Animal offspring) { + if ( this.offspring == null ) { + this.offspring = new HashSet(); + } + + this.offspring.add( offspring ); + } + + public void setOffspring(Set offspring) { + this.offspring = offspring; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/AnimalDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/AnimalDomainModel.java new file mode 100644 index 000000000000..d8fdf2250f87 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/AnimalDomainModel.java @@ -0,0 +1,44 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.animal; + +import org.hibernate.boot.MetadataSources; + +import org.hibernate.testing.orm.domain.AbstractDomainModelDescriptor; + +/** + * @author Steve Ebersole + */ +public class AnimalDomainModel extends AbstractDomainModelDescriptor { + /** + * Singleton access + */ + public static final AnimalDomainModel INSTANCE = new AnimalDomainModel(); + + public static void applyContactsModel(MetadataSources sources) { + INSTANCE.applyDomainModel( sources ); + } + + public AnimalDomainModel() { + super( + Address.class, + Animal.class, + Cat.class, + Classification.class, + Dog.class, + DomesticAnimal.class, + Human.class, + Lizard.class, + Mammal.class, + Name.class, + PettingZoo.class, + Reptile.class, + StateProvince.class, + Zoo.class + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Cat.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Cat.java new file mode 100644 index 000000000000..dc2419eb5db6 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Cat.java @@ -0,0 +1,16 @@ +/* + * 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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import javax.persistence.Entity; +import javax.persistence.PrimaryKeyJoinColumn; + +@Entity +@PrimaryKeyJoinColumn( name = "cat_id_fk" ) +public class Cat extends DomesticAnimal { + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Classification.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Classification.java new file mode 100644 index 000000000000..429f2b5c9cba --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Classification.java @@ -0,0 +1,28 @@ +/* + * 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 . + */ +package org.hibernate.testing.orm.domain.animal; + +/** + * Mimic a JDK 5 enum. + * + * @author Steve Ebersole + */ +public enum Classification implements Comparable { + COOL, + LAME; + + public static Classification valueOf(Integer ordinal) { + if ( ordinal == null ) { + return null; + } + switch ( ordinal ) { + case 0: return COOL; + case 1: return LAME; + default: throw new IllegalArgumentException( "unknown classification ordinal [" + ordinal + "]" ); + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Dog.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Dog.java new file mode 100644 index 000000000000..908ef4461bfa --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Dog.java @@ -0,0 +1,16 @@ +/* + * 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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import javax.persistence.Entity; +import javax.persistence.PrimaryKeyJoinColumn; + +@Entity +@PrimaryKeyJoinColumn( name = "dog_id_fk" ) +public class Dog extends DomesticAnimal { + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/DomesticAnimal.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/DomesticAnimal.java new file mode 100644 index 000000000000..d7d4cfecbdb5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/DomesticAnimal.java @@ -0,0 +1,28 @@ +/* + * 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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.PrimaryKeyJoinColumn; + +@Entity +@PrimaryKeyJoinColumn( name = "domestic_animal_id_fk" ) +public class DomesticAnimal extends Mammal { + private Human owner; + + @ManyToOne + @JoinColumn( name = "owner_fk" ) + public Human getOwner() { + return owner; + } + + public void setOwner(Human owner) { + this.owner = owner; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Human.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Human.java new file mode 100644 index 000000000000..3e018a903b1a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Human.java @@ -0,0 +1,170 @@ +/* + * 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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.hibernate.annotations.ColumnTransformer; +import org.hibernate.annotations.SortNatural; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.MapKeyColumn; +import javax.persistence.OneToMany; +import javax.persistence.PrimaryKeyJoinColumn; + +@Entity +@PrimaryKeyJoinColumn( name = "human_id_fk" ) +public class Human extends Mammal { + private Name name; + private String nickName; + private double heightInches; + + private BigInteger bigIntegerValue; + private BigDecimal bigDecimalValue; + private int intValue; + private float floatValue; + + private Collection friends; + private Collection pets; + private Map family; + private Set nickNames; + private Map addresses; + + @Embedded + public Name getName() { + return name; + } + + public void setName(Name name) { + this.name = name; + } + + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + + @Column( name = "height_centimeters", nullable = false ) + @ColumnTransformer( read = "height_centimeters / 2.54E0", write = "? * 2.54E0" ) + public double getHeightInches() { + return heightInches; + } + + public void setHeightInches(double height) { + this.heightInches = height; + } + + public BigDecimal getBigDecimalValue() { + return bigDecimalValue; + } + + public void setBigDecimalValue(BigDecimal bigDecimalValue) { + this.bigDecimalValue = bigDecimalValue; + } + + public BigInteger getBigIntegerValue() { + return bigIntegerValue; + } + + public void setBigIntegerValue(BigInteger bigIntegerValue) { + this.bigIntegerValue = bigIntegerValue; + } + + public float getFloatValue() { + return floatValue; + } + + public void setFloatValue(float floatValue) { + this.floatValue = floatValue; + } + + public int getIntValue() { + return intValue; + } + + public void setIntValue(int intValue) { + this.intValue = intValue; + } + + @ElementCollection + @CollectionTable( name = "human_nick_names", joinColumns = @JoinColumn( name = "human_fk" ) ) + @Column( name = "nick_name" ) + @SortNatural + public Set getNickNames() { + return nickNames; + } + + public void setNickNames(Set nickNames) { + this.nickNames = nickNames; + } + + @ManyToMany + @JoinTable( + name = "friends", + joinColumns = @JoinColumn( name = "friend_fk1" ), + inverseJoinColumns = @JoinColumn( name = "friend_fk2" ) + ) + public Collection getFriends() { + return friends; + } + + public void setFriends(Collection friends) { + this.friends = friends; + } + + @OneToMany( mappedBy = "owner" ) + public Collection getPets() { + return pets; + } + + public void setPets(Collection pets) { + this.pets = pets; + } + + @ManyToMany + @JoinTable( + name = "family", + joinColumns = @JoinColumn( name = "family_fk1" ), + inverseJoinColumns = @JoinColumn( name = "family_fk2" ) + ) + @MapKeyColumn( name = "relationship" ) + public Map getFamily() { + return family; + } + + + public void setFamily(Map family) { + this.family = family; + } + + @ElementCollection + @CollectionTable( name = "human_addresses", joinColumns = @JoinColumn( name = "human_fk" ) ) + @MapKeyColumn( name = "`type`" ) + public Map getAddresses() { + return addresses; + } + + public void setAddresses(Map addresses) { + this.addresses = addresses; + } + + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Lizard.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Lizard.java new file mode 100644 index 000000000000..fc73da12a964 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Lizard.java @@ -0,0 +1,15 @@ +/* + * 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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import javax.persistence.Entity; +import javax.persistence.PrimaryKeyJoinColumn; + +@Entity +@PrimaryKeyJoinColumn( name = "lizard_id_fk" ) +public class Lizard extends Reptile { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Mammal.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Mammal.java new file mode 100644 index 000000000000..240a1700a1e0 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Mammal.java @@ -0,0 +1,67 @@ +/* + * 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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import java.util.Date; + +import javax.persistence.Entity; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +@Entity +@PrimaryKeyJoinColumn( name = "mammal_id_fk" ) +public class Mammal extends Animal { + private boolean pregnant; + private Date birthdate; + + public boolean isPregnant() { + return pregnant; + } + + public void setPregnant(boolean pregnant) { + this.pregnant = pregnant; + } + + @Temporal( TemporalType.DATE ) + public Date getBirthdate() { + return birthdate; + } + + + public void setBirthdate(Date birthdate) { + this.birthdate = birthdate; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !( o instanceof Mammal ) ) { + return false; + } + + Mammal mammal = ( Mammal ) o; + + if ( pregnant != mammal.pregnant ) { + return false; + } + if ( birthdate != null ? !birthdate.equals( mammal.birthdate ) : mammal.birthdate != null ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = ( pregnant ? 1 : 0 ); + result = 31 * result + ( birthdate != null ? birthdate.hashCode() : 0 ); + return result; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Name.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Name.java new file mode 100644 index 000000000000..834dd96d48e6 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Name.java @@ -0,0 +1,56 @@ +/* + * 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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import javax.persistence.Column; +import javax.persistence.Embeddable; + +@Embeddable +public class Name { + private String first; + private Character initial; + private String last; + + public Name() {} + + public Name(String first, Character initial, String last) { + this.first = first; + this.initial = initial; + this.last = last; + } + + public Name(String first, char initial, String last) { + this( first, Character.valueOf( initial ), last ); + } + + @Column( name = "name_first" ) + public String getFirst() { + return first; + } + + public void setFirst(String first) { + this.first = first; + } + + @Column( name = "name_initial" ) + public Character getInitial() { + return initial; + } + + public void setInitial(Character initial) { + this.initial = initial; + } + + @Column( name = "name_last" ) + public String getLast() { + return last; + } + + public void setLast(String last) { + this.last = last; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/PettingZoo.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/PettingZoo.java new file mode 100644 index 000000000000..7a35f65ec521 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/PettingZoo.java @@ -0,0 +1,16 @@ +/* + * 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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; + +@Entity +@DiscriminatorValue( "P" ) +public class PettingZoo extends Zoo { + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Reptile.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Reptile.java new file mode 100644 index 000000000000..709236df6445 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Reptile.java @@ -0,0 +1,22 @@ +/* + * 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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import javax.persistence.Entity; +import javax.persistence.PrimaryKeyJoinColumn; + +@Entity +@PrimaryKeyJoinColumn( name = "reptile_id_fk" ) +public class Reptile extends Animal { + private float bodyTemperature; + public float getBodyTemperature() { + return bodyTemperature; + } + public void setBodyTemperature(float bodyTemperature) { + this.bodyTemperature = bodyTemperature; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/StateProvince.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/StateProvince.java new file mode 100644 index 000000000000..dd575f842c6b --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/StateProvince.java @@ -0,0 +1,79 @@ +/* + * 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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class StateProvince { + private Long id; + private String name; + private String isoCode; + + @Id + 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 getIsoCode() { + return isoCode; + } + + public void setIsoCode(String isoCode) { + this.isoCode = isoCode; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !( o instanceof StateProvince ) ) { + return false; + } + + StateProvince that = ( StateProvince ) o; + + if ( isoCode != null ? !isoCode.equals( that.getIsoCode() ) : that.getIsoCode() != null ) { + return false; + } + if ( name != null ? !name.equals( that.getName() ) : that.getName() != null ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + ( isoCode != null ? isoCode.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "StateProvince{" + + "id=" + id + + ", name='" + name + '\'' + + ", isoCode='" + isoCode + '\'' + + '}'; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Zoo.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Zoo.java new file mode 100644 index 000000000000..30096faf7737 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/Zoo.java @@ -0,0 +1,145 @@ +/* + * 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 . + */ +package org.hibernate.testing.orm.domain.animal; + +import java.util.HashMap; +import java.util.Map; + +import javax.persistence.DiscriminatorColumn; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.MapKeyColumn; +import javax.persistence.OneToMany; + +@Entity +@Inheritance +@DiscriminatorColumn( name = "zooType" ) +@DiscriminatorValue( "Z" ) +public class Zoo { + private Long id; + private String name; + private Classification classification; + private Map directors = new HashMap(); + private Map animals = new HashMap(); + private Map mammals = new HashMap(); + private Address address; + + public Zoo() { + } + + public Zoo(String name, Address address) { + this.name = name; + this.address = address; + } + + @Id + 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; + } + + @ManyToMany + @JoinTable( + name = "t_directors", + joinColumns = @JoinColumn( name = "zoo_fk" ), + inverseJoinColumns = @JoinColumn( name = "director_fk" ) + ) + @MapKeyColumn( name = "`title`" ) + public Map getDirectors() { + return directors; + } + + public void setDirectors(Map directors) { + this.directors = directors; + } + + @OneToMany + @JoinColumn( name = "mammal_fk" ) + @MapKeyColumn( name = "name" ) + public Map getMammals() { + return mammals; + } + + public void setMammals(Map mammals) { + this.mammals = mammals; + } + + @OneToMany( mappedBy = "zoo" ) + @MapKeyColumn( name = "serialNumber" ) + public Map getAnimals() { + return animals; + } + + public void setAnimals(Map animals) { + this.animals = animals; + } + + @Embedded + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + @Enumerated( value = EnumType.STRING ) + public Classification getClassification() { + return classification; + } + + public void setClassification(Classification classification) { + this.classification = classification; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !( o instanceof Zoo ) ) { + return false; + } + + Zoo zoo = ( Zoo ) o; + + if ( address != null ? !address.equals( zoo.address ) : zoo.address != null ) { + return false; + } + if ( name != null ? !name.equals( zoo.name ) : zoo.name != null ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + ( address != null ? address.hashCode() : 0 ); + return result; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/package-info.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/package-info.java new file mode 100644 index 000000000000..5a0624d85dae --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/animal/package-info.java @@ -0,0 +1,11 @@ +/* + * 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 + */ + +/** + * Standard model for Hibernate's legacy Animal model used in HQL testing + */ +package org.hibernate.testing.orm.domain.animal; diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Address.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Address.java new file mode 100644 index 000000000000..229619abaf8a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Address.java @@ -0,0 +1,83 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.contacts; + +import javax.persistence.Embeddable; + +/** + * @author Steve Ebersole + */ +@Embeddable +public class Address { + private Classification classification; + private String line1; + private String line2; + private PostalCode postalCode; + + public Classification getClassification() { + return classification; + } + + public void setClassification(Classification classification) { + this.classification = classification; + } + + public String getLine1() { + return line1; + } + + public void setLine1(String line1) { + this.line1 = line1; + } + + public String getLine2() { + return line2; + } + + public void setLine2(String line2) { + this.line2 = line2; + } + + public PostalCode getPostalCode() { + return postalCode; + } + + public void setPostalCode(PostalCode postalCode) { + this.postalCode = postalCode; + } + + + + public enum Classification { + HOME, + WORK, + MAIN, + OTHER + } + + @Embeddable + public static class PostalCode { + private int zipCode; + private int plus4; + + public int getZipCode() { + return zipCode; + } + + public void setZipCode(int zipCode) { + this.zipCode = zipCode; + } + + public int getPlus4() { + return plus4; + } + + public void setPlus4(int plus4) { + this.plus4 = plus4; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Contact.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Contact.java new file mode 100644 index 000000000000..e2186efde091 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Contact.java @@ -0,0 +1,145 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.contacts; + +import java.time.LocalDate; +import java.util.List; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OrderColumn; +import javax.persistence.SecondaryTable; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +/** + * @author Steve Ebersole + */ +@Entity +@Table( name = "contacts" ) +@SecondaryTable( name="contact_supp" ) +public class Contact { + private Integer id; + private Name name; + private Gender gender; + + private LocalDate birthDay; + + private List

addresses; + private List phoneNumbers; + + public Contact() { + } + + public Contact(Integer id, Name name, Gender gender, LocalDate birthDay) { + this.id = id; + this.name = name; + this.gender = gender; + this.birthDay = birthDay; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Name getName() { + return name; + } + + public void setName(Name name) { + this.name = name; + } + + public Gender getGender() { + return gender; + } + + public void setGender(Gender gender) { + this.gender = gender; + } + + @Temporal( TemporalType.DATE ) + @Column( table = "contact_supp" ) + public LocalDate getBirthDay() { + return birthDay; + } + + public void setBirthDay(LocalDate birthDay) { + this.birthDay = birthDay; + } + + @ElementCollection + @CollectionTable( name = "contact_addresses" ) + // NOTE : because of the @OrderColumn `addresses` is a List, while `phoneNumbers` is + // a BAG which is a List with no persisted order + @OrderColumn + public List
getAddresses() { + return addresses; + } + + public void setAddresses(List
addresses) { + this.addresses = addresses; + } + + @ElementCollection + @CollectionTable( name = "contact_phones" ) + public List getPhoneNumbers() { + return phoneNumbers; + } + + public void setPhoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + } + + @Embeddable + public static class Name { + private String first; + private String last; + + public Name() { + } + + public Name(String first, String last) { + this.first = first; + this.last = last; + } + + @Column(name = "firstname") + public String getFirst() { + return first; + } + + public void setFirst(String first) { + this.first = first; + } + + @Column(name = "lastname") + public String getLast() { + return last; + } + + public void setLast(String last) { + this.last = last; + } + } + + public enum Gender { + MALE, + FEMALE, + OTHER + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/ContactsDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/ContactsDomainModel.java new file mode 100644 index 000000000000..1c151a6503ba --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/ContactsDomainModel.java @@ -0,0 +1,30 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.contacts; + +import org.hibernate.boot.MetadataSources; + +import org.hibernate.testing.orm.domain.AbstractDomainModelDescriptor; + +/** + * @author Steve Ebersole + */ +public class ContactsDomainModel extends AbstractDomainModelDescriptor { + public static ContactsDomainModel INSTANCE = new ContactsDomainModel(); + + public static void applyContactsModel(MetadataSources sources) { + INSTANCE.applyDomainModel( sources ); + } + + private ContactsDomainModel() { + super( + Address.class, + PhoneNumber.class, + Contact.class + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/PhoneNumber.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/PhoneNumber.java new file mode 100644 index 000000000000..f0084ded5f49 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/PhoneNumber.java @@ -0,0 +1,72 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.contacts; + +import javax.persistence.Embeddable; + +/** + * @author Steve Ebersole + */ +@Embeddable +public class PhoneNumber { + private int areaCode; + private int prefix; + private int lineNumber; + + private Classification classification; + + public PhoneNumber() { + } + + public PhoneNumber(int areaCode, int prefix, int lineNumber, Classification classification) { + this.areaCode = areaCode; + this.prefix = prefix; + this.lineNumber = lineNumber; + this.classification = classification; + } + + public int getAreaCode() { + return areaCode; + } + + public void setAreaCode(int areaCode) { + this.areaCode = areaCode; + } + + public int getPrefix() { + return prefix; + } + + public void setPrefix(int prefix) { + this.prefix = prefix; + } + + public int getLineNumber() { + return lineNumber; + } + + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + public Classification getClassification() { + return classification; + } + + public void setClassification(Classification classification) { + this.classification = classification; + } + + public enum Classification { + HOME, + WORK, + MOBILE, + MAIN, + FAX, + OTHER + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/BasicEntity.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/BasicEntity.java new file mode 100644 index 000000000000..474cd724ec23 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/BasicEntity.java @@ -0,0 +1,65 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.Objects; + +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Chris Cranford + */ +@Entity +public class BasicEntity { + @Id + private Integer id; + private String data; + + public BasicEntity() { + + } + + public BasicEntity(Integer id, String data) { + this.id = id; + this.data = data; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + BasicEntity that = (BasicEntity) o; + return Objects.equals( id, that.id ) && + Objects.equals( data, that.data ); + } + + @Override + public int hashCode() { + return Objects.hash( id, data ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Component.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Component.java new file mode 100644 index 000000000000..6dd38eb9d88d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Component.java @@ -0,0 +1,129 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Embeddable; + +/** + * @author Steve Ebersole + */ +@Embeddable +@SuppressWarnings("unused") +public class Component { + + // alphabetical + private Integer basicInteger; + private Long basicLong; + private int basicPrimitiveInt; + private String basicString; + private Nested nested; + + @Embeddable + public static class Nested { + + // alphabetical + private String nestedValue; + private String secondNestedValue; + + public Nested() { + } + + public Nested(String nestedValue) { + this.nestedValue = nestedValue; + } + + public Nested(String nestedValue, String secondNestedValue) { + this.nestedValue = nestedValue; + this.secondNestedValue = secondNestedValue; + } + + public String getNestedValue() { + return nestedValue; + } + + public void setNestedValue(String nestedValue) { + this.nestedValue = nestedValue; + } + + public String getSecondNestedValue() { + return secondNestedValue; + } + + public void setSecondNestedValue(String secondNestedValue) { + this.secondNestedValue = secondNestedValue; + } + } + + public Component() { + } + + public Component( + String basicString, + Integer basicInteger, + Long basicLong, + int basicPrimitiveInt, + Nested nested) { + this.basicString = basicString; + this.basicInteger = basicInteger; + this.basicLong = basicLong; + this.basicPrimitiveInt = basicPrimitiveInt; + this.nested = nested; + } + + public Component( + Integer basicInteger, + Long basicLong, + int basicPrimitiveInt, + String basicString, + Nested nested) { + this.basicInteger = basicInteger; + this.basicLong = basicLong; + this.basicPrimitiveInt = basicPrimitiveInt; + this.basicString = basicString; + this.nested = nested; + } + + public String getBasicString() { + return basicString; + } + + public void setBasicString(String basicString) { + this.basicString = basicString; + } + + public Integer getBasicInteger() { + return basicInteger; + } + + public void setBasicInteger(Integer basicInteger) { + this.basicInteger = basicInteger; + } + + public Long getBasicLong() { + return basicLong; + } + + public void setBasicLong(Long basicLong) { + this.basicLong = basicLong; + } + + public int getBasicPrimitiveInt() { + return basicPrimitiveInt; + } + + public void setBasicPrimitiveInt(int basicPrimitiveInt) { + this.basicPrimitiveInt = basicPrimitiveInt; + } + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EmbeddedIdEntity.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EmbeddedIdEntity.java new file mode 100644 index 000000000000..8f4c261a61e3 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EmbeddedIdEntity.java @@ -0,0 +1,97 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.io.Serializable; +import java.util.Objects; + +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; + +/** + * @author Chris Cranford + */ +@Entity +public class EmbeddedIdEntity { + @EmbeddedId + private EmbeddedIdEntityId id; + private String data; + + public EmbeddedIdEntityId getId() { + return id; + } + + public void setId(EmbeddedIdEntityId id) { + this.id = id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + @Embeddable + public static class EmbeddedIdEntityId implements Serializable { + private Integer value1; + private String value2; + + EmbeddedIdEntityId() { + + } + + public EmbeddedIdEntityId(Integer value1, String value2) { + this.value1 = value1; + this.value2 = value2; + } + + public Integer getValue1() { + return value1; + } + + public void setValue1(Integer value1) { + this.value1 = value1; + } + + public String getValue2() { + return value2; + } + + public void setValue2(String value2) { + this.value2 = value2; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + EmbeddedIdEntityId that = (EmbeddedIdEntityId) o; + return Objects.equals( value1, that.value1 ) && + Objects.equals( value2, that.value2 ); + } + + @Override + public int hashCode() { + return Objects.hash( value1, value2 ); + } + + @Override + public String toString() { + return "EmbeddedIdEntityId{" + + "value1=" + value1 + + ", value2='" + value2 + '\'' + + '}'; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfArrays.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfArrays.java new file mode 100644 index 000000000000..1be2720fadce --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfArrays.java @@ -0,0 +1,66 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OrderColumn; + +/** + * @author Koen Aers + */ +@SuppressWarnings("unused") +@Entity +public class EntityOfArrays { + + private Integer id; + private String name; + + private String[] arrayOfBasics; + + + public EntityOfArrays() { + } + + public EntityOfArrays(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // arrayOfBasics + + @ElementCollection + @OrderColumn + public String[] getArrayOfBasics() { + return arrayOfBasics; + } + + public void setArrayOfBasics(String[] arrayOfBasics) { + this.arrayOfBasics = arrayOfBasics; + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java new file mode 100644 index 000000000000..f8b42c6c6272 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java @@ -0,0 +1,336 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.net.URL; +import java.sql.Clob; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.Date; +import javax.persistence.AttributeConverter; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.EntityResult; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.SqlResultSetMapping; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.Type; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings( "unused" ) +@SqlResultSetMapping( + name = "entity-of-basics-implicit", + entities = @EntityResult( entityClass = EntityOfBasics.class ) +) +@Entity +public class EntityOfBasics { + + public enum Gender { + MALE, + FEMALE, + OTHER + } + + private Integer id; + private Boolean theBoolean = false; + private Boolean theNumericBoolean = false; + private Boolean theStringBoolean = false; + private String theString; + private Integer theInteger; + private int theInt; + private double theDouble; + private URL theUrl; + private Clob theClob; + private Date theDate; + private Date theTime; + private Date theTimestamp; + private Instant theInstant; + private Gender gender; + private Gender convertedGender; + private Gender ordinalGender; + private Duration theDuration; + + private LocalDateTime theLocalDateTime; + private LocalDate theLocalDate; + private LocalTime theLocalTime; + private ZonedDateTime theZonedDateTime; + private OffsetDateTime theOffsetDateTime; + + private MutableValue mutableValue; + + public EntityOfBasics() { + } + + public EntityOfBasics(Integer id) { + this.id = id; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getTheString() { + return theString; + } + + public void setTheString(String theString) { + this.theString = theString; + } + + public Integer getTheInteger() { + return theInteger; + } + + public void setTheInteger(Integer theInteger) { + this.theInteger = theInteger; + } + + public int getTheInt() { + return theInt; + } + + public void setTheInt(int theInt) { + this.theInt = theInt; + } + + public double getTheDouble() { + return theDouble; + } + + public void setTheDouble(double theDouble) { + this.theDouble = theDouble; + } + + public URL getTheUrl() { + return theUrl; + } + + public void setTheUrl(URL theUrl) { + this.theUrl = theUrl; + } + + public Clob getTheClob() { + return theClob; + } + + public void setTheClob(Clob theClob) { + this.theClob = theClob; + } + + @Enumerated( EnumType.STRING ) + public Gender getGender() { + return gender; + } + + public void setGender(Gender gender) { + this.gender = gender; + } + + @Convert( converter = GenderConverter.class ) + @Column(name = "converted_gender", length = 1) + public Gender getConvertedGender() { + return convertedGender; + } + + public void setConvertedGender(Gender convertedGender) { + this.convertedGender = convertedGender; + } + + @Column(name = "ordinal_gender") + public Gender getOrdinalGender() { + return ordinalGender; + } + + public void setOrdinalGender(Gender ordinalGender) { + this.ordinalGender = ordinalGender; + } + + @Temporal( TemporalType.DATE ) + public Date getTheDate() { + return theDate; + } + + public void setTheDate(Date theDate) { + this.theDate = theDate; + } + + @Temporal( TemporalType.TIME ) + public Date getTheTime() { + return theTime; + } + + public void setTheTime(Date theTime) { + this.theTime = theTime; + } + + @Temporal( TemporalType.TIMESTAMP ) + public Date getTheTimestamp() { + return theTimestamp; + } + + public void setTheTimestamp(Date theTimestamp) { + this.theTimestamp = theTimestamp; + } + + @Temporal( TemporalType.TIMESTAMP ) + public Instant getTheInstant() { + return theInstant; + } + + public void setTheInstant(Instant theInstant) { + this.theInstant = theInstant; + } + + public LocalDateTime getTheLocalDateTime() { + return theLocalDateTime; + } + + public void setTheLocalDateTime(LocalDateTime theLocalDateTime) { + this.theLocalDateTime = theLocalDateTime; + } + + public LocalDate getTheLocalDate() { + return theLocalDate; + } + + public void setTheLocalDate(LocalDate theLocalDate) { + this.theLocalDate = theLocalDate; + } + + public LocalTime getTheLocalTime() { + return theLocalTime; + } + + public void setTheLocalTime(LocalTime theLocalTime) { + this.theLocalTime = theLocalTime; + } + + public OffsetDateTime getTheOffsetDateTime() { + return theOffsetDateTime; + } + + public void setTheOffsetDateTime(OffsetDateTime theOffsetDateTime) { + this.theOffsetDateTime = theOffsetDateTime; + } + + public ZonedDateTime getTheZonedDateTime() { + return theZonedDateTime; + } + + public void setTheZonedDateTime(ZonedDateTime theZonedDateTime) { + this.theZonedDateTime = theZonedDateTime; + } + + public Duration getTheDuration() { + return theDuration; + } + + public void setTheDuration(Duration theDuration) { + this.theDuration = theDuration; + } + + public Boolean isTheBoolean() { + return theBoolean; + } + + public void setTheBoolean(Boolean theBoolean) { + this.theBoolean = theBoolean; + } + + @Type( type = "numeric_boolean" ) + public Boolean isTheNumericBoolean() { + return theNumericBoolean; + } + + public void setTheNumericBoolean(Boolean theNumericBoolean) { + this.theNumericBoolean = theNumericBoolean; + } + + @Type( type = "true_false" ) + public Boolean isTheStringBoolean() { + return theStringBoolean; + } + + public void setTheStringBoolean(Boolean theStringBoolean) { + this.theStringBoolean = theStringBoolean; + } + + @Convert( converter = MutableValueConverter.class ) + public MutableValue getMutableValue() { + return mutableValue; + } + + public void setMutableValue(MutableValue mutableValue) { + this.mutableValue = mutableValue; + } + + public static class MutableValueConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(MutableValue attribute) { + return attribute == null ? null : attribute.getState(); + } + + @Override + public MutableValue convertToEntityAttribute(String dbData) { + return dbData == null ? null : new MutableValue( dbData ); + } + } + + public static class GenderConverter implements AttributeConverter { + @Override + public Character convertToDatabaseColumn(Gender attribute) { + if ( attribute == null ) { + return null; + } + + if ( attribute == Gender.OTHER ) { + return 'O'; + } + + if ( attribute == Gender.MALE ) { + return 'M'; + } + + return 'F'; + } + + @Override + public Gender convertToEntityAttribute(Character dbData) { + if ( dbData == null ) { + return null; + } + + if ( 'O' == dbData ) { + return Gender.OTHER; + } + + if ( 'M' == dbData ) { + return Gender.MALE; + } + + return Gender.FEMALE; + } + } +} + diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfComposites.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfComposites.java new file mode 100644 index 000000000000..03eb5221d90f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfComposites.java @@ -0,0 +1,66 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +@Entity +public class EntityOfComposites { + private Integer id; + private String name; + private Component component; + + public EntityOfComposites() { + } + + public EntityOfComposites(Integer id) { + this.id = id; + } + + public EntityOfComposites(Integer id, Component component) { + this.id = id; + this.component = component; + } + + public EntityOfComposites(Integer id, String name, Component component) { + this.id = id; + this.name = name; + this.component = component; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Embedded + public Component getComponent() { + return component; + } + + public void setComponent(Component component) { + this.component = component; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfDynamicComponent.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfDynamicComponent.java new file mode 100644 index 000000000000..6aa5038c705b --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfDynamicComponent.java @@ -0,0 +1,62 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Chris Cranford + */ +public class EntityOfDynamicComponent { + private Long id; + private String note; + private Map values = new HashMap<>(); + private Map valuesWithProperties = new HashMap<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getNote() { + return note; + } + + public void setNote(String note) { + this.note = note; + } + + public Map getValues() { + return values; + } + + public void setValues(Map values) { + this.values = values; + } + + public Map getValuesWithProperties() { + return valuesWithProperties; + } + + public void setValuesWithProperties(Map valuesWithProperties) { + this.valuesWithProperties = valuesWithProperties; + } + + @Override + public String toString() { + return "EntityOfDynamicComponent{" + + "id=" + id + + ", note='" + note + '\'' + + ", values=" + values + + ", valuesWithProperties=" + valuesWithProperties + + '}'; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfLists.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfLists.java new file mode 100644 index 000000000000..9950b9770fa6 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfLists.java @@ -0,0 +1,219 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.CollectionTable; +import javax.persistence.Convert; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.OrderColumn; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +@Entity +public class EntityOfLists { + private Integer id; + private String name; + + private List listOfBasics; + private List listOfNumbers; + + private List listOfConvertedEnums; + private List listOfEnums; + + private List listOfComponents; + + private List listOfOneToMany; + private List listOfManyToMany; + + public EntityOfLists() { + } + + public EntityOfLists(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // listOfBasics + + @ElementCollection + @OrderColumn + @CollectionTable(name = "EntityOfLists_basic") + public List getListOfBasics() { + return listOfBasics; + } + + public void setListOfBasics(List listOfBasics) { + this.listOfBasics = listOfBasics; + } + + @ElementCollection + @OrderColumn(name="num_indx") + @CollectionTable(name = "EntityOfLists_numbers") + public List getListOfNumbers() { + return listOfNumbers; + } + + public void setListOfNumbers(List listOfNumbers) { + this.listOfNumbers = listOfNumbers; + } + + public void addBasic(String basic) { + if ( listOfBasics == null ) { + listOfBasics = new ArrayList<>(); + } + listOfBasics.add( basic ); + } + + public void addNumber(double number) { + if ( listOfNumbers == null ) { + listOfNumbers = new ArrayList<>(); + } + listOfNumbers.add( number ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // listOfConvertedEnums + + @ElementCollection + @OrderColumn + @Convert(converter = EnumValueConverter.class) + @CollectionTable(name = "EntityOfLists_enum1") + public List getListOfConvertedEnums() { + return listOfConvertedEnums; + } + + public void setListOfConvertedEnums(List listOfConvertedEnums) { + this.listOfConvertedEnums = listOfConvertedEnums; + } + + public void addConvertedEnum(EnumValue value) { + if ( listOfConvertedEnums == null ) { + listOfConvertedEnums = new ArrayList<>(); + } + listOfConvertedEnums.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // listOfEnums + + @ElementCollection + @Enumerated(EnumType.STRING) + @OrderColumn + @CollectionTable(name = "EntityOfLists_enum2") + public List getListOfEnums() { + return listOfEnums; + } + + public void setListOfEnums(List listOfEnums) { + this.listOfEnums = listOfEnums; + } + + public void addEnum(EnumValue value) { + if ( listOfEnums == null ) { + listOfEnums = new ArrayList<>(); + } + listOfEnums.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // listOfComponents + + @ElementCollection + @OrderColumn + @CollectionTable(name = "EntityOfLists_comp") + public List getListOfComponents() { + return listOfComponents; + } + + public void setListOfComponents(List listOfComponents) { + this.listOfComponents = listOfComponents; + } + + public void addComponent(SimpleComponent value) { + if ( listOfComponents == null ) { + listOfComponents = new ArrayList<>(); + } + listOfComponents.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // listOfOneToMany + + @OneToMany + @OrderColumn + @CollectionTable(name = "EntityOfLists_o2m") + public List getListOfOneToMany() { + return listOfOneToMany; + } + + public void setListOfOneToMany(List listOfOneToMany) { + this.listOfOneToMany = listOfOneToMany; + } + + public void addOneToMany(SimpleEntity value) { + if ( listOfOneToMany == null ) { + listOfOneToMany = new ArrayList<>(); + } + listOfOneToMany.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // listOfManyToMany + + @ManyToMany + @OrderColumn + @CollectionTable(name = "EntityOfLists_m2m") + public List getListOfManyToMany() { + return listOfManyToMany; + } + + public void setListOfManyToMany(List listOfManyToMany) { + this.listOfManyToMany = listOfManyToMany; + } + + public void addManyToMany(SimpleEntity value) { + if ( listOfManyToMany == null ) { + listOfManyToMany = new ArrayList<>(); + } + listOfManyToMany.add( value ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfMaps.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfMaps.java new file mode 100644 index 000000000000..dfceb4d3ffe0 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfMaps.java @@ -0,0 +1,453 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.hibernate.annotations.OrderBy; +import org.hibernate.annotations.SortComparator; +import org.hibernate.annotations.SortNatural; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.MapKeyColumn; +import javax.persistence.MapKeyEnumerated; +import javax.persistence.MapKeyJoinColumn; +import javax.persistence.OneToMany; + +/** + * @author Steve Ebersole + * @author Fabio Massimo Ercoli + */ +@SuppressWarnings("unused") +@Entity +public class EntityOfMaps { + private Integer id; + private String name; + + private Map basicByBasic; + private Map numberByNumber; + + private SortedMap sortedBasicByBasic; + private SortedMap sortedBasicByBasicWithComparator; + private SortedMap sortedBasicByBasicWithSortNaturalByDefault; + + private Map basicByEnum; + private Map basicByConvertedEnum; + + private Map componentByBasic; + private Map basicByComponent; + + private Map oneToManyByBasic; + private Map basicByOneToMany; + + private Map manyToManyByBasic; + private Map componentByBasicOrdered; + + private SortedMap sortedManyToManyByBasic; + private SortedMap sortedManyToManyByBasicWithComparator; + private SortedMap sortedManyToManyByBasicWithSortNaturalByDefault; + + public EntityOfMaps() { + } + + public EntityOfMaps(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // basicByBasic + + @ElementCollection + @CollectionTable(name = "EntityOfMaps_basic_basic1") + @MapKeyColumn(name = "basic_key") + @Column(name = "basic_val") + public Map getBasicByBasic() { + return basicByBasic; + } + + public void setBasicByBasic(Map basicByBasic) { + this.basicByBasic = basicByBasic; + } + + public void addBasicByBasic(String key, String val) { + if ( basicByBasic == null ) { + basicByBasic = new HashMap<>(); + } + basicByBasic.put( key, val ); + } + + @ElementCollection + @CollectionTable(name = "EntityOfMaps_number_number1") + @MapKeyColumn(name = "number_key") + @Column(name = "number_val") + public Map getNumberByNumber() { + return numberByNumber; + } + + public void setNumberByNumber(Map numberByNumber) { + this.numberByNumber = numberByNumber; + } + + public void addNumberByNumber(int key, double val) { + if ( numberByNumber == null ) { + numberByNumber = new HashMap<>(); + } + numberByNumber.put( key, val ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedBasicByBasic + + @ElementCollection + @SortNatural + @CollectionTable(name = "EntityOfMaps_basic_basic2") + @MapKeyColumn(name = "basic_key") + @Column(name = "basic_val") + public SortedMap getSortedBasicByBasic() { + return sortedBasicByBasic; + } + + public void setSortedBasicByBasic(SortedMap sortedBasicByBasic) { + this.sortedBasicByBasic = sortedBasicByBasic; + } + + public void addSortedBasicByBasic(String key, String val) { + if ( sortedBasicByBasic == null ) { + sortedBasicByBasic = new TreeMap<>(); + } + sortedBasicByBasic.put( key, val ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedBasicByBasicWithComparator + + @ElementCollection + @SortComparator( SimpleBasicSortComparator.class ) + @CollectionTable(name = "EntityOfMaps_basic_basic3") + @MapKeyColumn(name = "basic_key") + @Column(name = "basic_val") + public SortedMap getSortedBasicByBasicWithComparator() { + return sortedBasicByBasicWithComparator; + } + + public void setSortedBasicByBasicWithComparator(SortedMap sortedBasicByBasicWithComparator) { + this.sortedBasicByBasicWithComparator = sortedBasicByBasicWithComparator; + } + + public void addSortedBasicByBasicWithComparator(String key, String val) { + if ( sortedBasicByBasicWithComparator == null ) { + sortedBasicByBasicWithComparator = new TreeMap<>(); + } + sortedBasicByBasicWithComparator.put( key, val ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedBasicByBasicWithSortNaturalByDefault + + @ElementCollection + @CollectionTable(name = "EntityOfMaps_basic_basic4") + @MapKeyColumn(name = "basic_key") + @Column(name = "basic_val") + public SortedMap getSortedBasicByBasicWithSortNaturalByDefault() { + return sortedBasicByBasicWithSortNaturalByDefault; + } + + public void setSortedBasicByBasicWithSortNaturalByDefault(SortedMap sortedBasicByBasicWithSortNaturalByDefault) { + this.sortedBasicByBasicWithSortNaturalByDefault = sortedBasicByBasicWithSortNaturalByDefault; + } + + public void addSortedBasicByBasicWithSortNaturalByDefault(String key, String val) { + if ( sortedBasicByBasicWithSortNaturalByDefault == null ) { + sortedBasicByBasicWithSortNaturalByDefault = new TreeMap<>(); + } + sortedBasicByBasicWithSortNaturalByDefault.put( key, val ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // basicByEnum + + @ElementCollection + @MapKeyEnumerated + @CollectionTable(name = "EntityOfMaps_basic_enum1") + @MapKeyColumn(name = "enum_key") + @Column(name = "basic_val") + public Map getBasicByEnum() { + return basicByEnum; + } + + public void setBasicByEnum(Map basicByEnum) { + this.basicByEnum = basicByEnum; + } + + public void addBasicByEnum(EnumValue key, String val) { + if ( basicByEnum == null ) { + basicByEnum = new HashMap<>(); + } + basicByEnum.put( key, val ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // basicByConvertedEnum + + @ElementCollection + @Convert(attributeName = "key", converter = EnumValueConverter.class) + @CollectionTable(name = "EntityOfMaps_basic_enum2") + @MapKeyColumn(name = "enum_key") + @Column(name = "basic_val") + public Map getBasicByConvertedEnum() { + return basicByConvertedEnum; + } + + public void setBasicByConvertedEnum(Map basicByConvertedEnum) { + this.basicByConvertedEnum = basicByConvertedEnum; + } + + public void addBasicByConvertedEnum(EnumValue key, String value) { + if ( basicByConvertedEnum == null ) { + basicByConvertedEnum = new HashMap<>(); + } + basicByConvertedEnum.put( key, value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // componentByBasic + + @ElementCollection + @CollectionTable(name = "EntityOfMaps_comp_basic1") + @MapKeyColumn(name = "basic_key") + public Map getComponentByBasic() { + return componentByBasic; + } + + public void setComponentByBasic(Map componentByBasic) { + this.componentByBasic = componentByBasic; + } + + public void addComponentByBasic(String key, SimpleComponent value) { + if ( componentByBasic == null ) { + componentByBasic = new HashMap<>(); + } + componentByBasic.put( key, value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // basicByComponent + + @ElementCollection + @CollectionTable(name = "EntityOfMaps_basic_comp") + @Column(name = "basic_val") + public Map getBasicByComponent() { + return basicByComponent; + } + + public void setBasicByComponent(Map basicByComponent) { + this.basicByComponent = basicByComponent; + } + + public void addBasicByComponent(SimpleComponent key, String value) { + if ( basicByComponent == null ) { + basicByComponent = new HashMap<>(); + } + basicByComponent.put( key, value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // oneToManyByBasic + + @OneToMany + @JoinColumn + @MapKeyColumn(name = "basic_key") + @JoinTable(name = "EntityOfMaps_o2m_basic", + joinColumns = @JoinColumn(name = "EntityOfMaps_o2m_basic_id1"), + inverseJoinColumns = @JoinColumn(name = "EntityOfMaps_o2m_basic_id2")) + public Map getOneToManyByBasic() { + return oneToManyByBasic; + } + + public void setOneToManyByBasic(Map oneToManyByBasic) { + this.oneToManyByBasic = oneToManyByBasic; + } + + public void addOneToManyByBasic(String key, SimpleEntity value) { + if ( oneToManyByBasic == null ) { + oneToManyByBasic = new HashMap<>(); + } + oneToManyByBasic.put( key, value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // basicByOneToMany + + @ElementCollection + @CollectionTable(name = "EntityOfMaps_basic_o2m") + @Column(name = "basic_val") + @MapKeyJoinColumn(name = "EntityOfMaps_basic_o2m_key") + public Map getBasicByOneToMany() { + return basicByOneToMany; + } + + public void setBasicByOneToMany(Map basicByOneToMany) { + this.basicByOneToMany = basicByOneToMany; + } + + public void addOneToManyByBasic(SimpleEntity key, String val) { + if ( basicByOneToMany == null ) { + basicByOneToMany = new HashMap<>(); + } + basicByOneToMany.put( key, val ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // manyToManyByBasic + + @ManyToMany + @MapKeyColumn(name = "basic_key") + @JoinTable(name = "EntityOfMaps_m2m_basic1", + joinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic1_id1"), + inverseJoinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic1_id2")) + public Map getManyToManyByBasic() { + return manyToManyByBasic; + } + + public void setManyToManyByBasic(Map manyToManyByBasic) { + this.manyToManyByBasic = manyToManyByBasic; + } + + public void addManyToManyByComponent(String key, SimpleEntity value) { + if ( manyToManyByBasic == null ) { + manyToManyByBasic = new HashMap<>(); + } + manyToManyByBasic.put( key, value ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // componentByBasicOrdered + + // NOTE : effectively the same as a natural-sorted map in terms of reading + + @ElementCollection + @MapKeyColumn( name = "ordered_component_key") + @OrderBy( clause = "ordered_component_key, ordered_component_key" ) + @CollectionTable(name = "EntityOfMaps_comp_basic2") + public Map getComponentByBasicOrdered() { + return componentByBasicOrdered; + } + + public void setComponentByBasicOrdered(Map componentByBasicOrdered) { + this.componentByBasicOrdered = componentByBasicOrdered; + } + + public void addComponentByBasicOrdered(String key, SimpleComponent value) { + if ( componentByBasicOrdered == null ) { + componentByBasicOrdered = new LinkedHashMap<>(); + } + componentByBasicOrdered.put( key, value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedManyToManyByBasic + + @ManyToMany + @SortNatural + @MapKeyColumn(name = "basic_key") + @JoinTable(name = "EntityOfMaps_m2m_basic2", + joinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic2_id1"), + inverseJoinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic2_id2")) + public SortedMap getSortedManyToManyByBasic() { + return sortedManyToManyByBasic; + } + + public void setSortedManyToManyByBasic(SortedMap sortedManyToManyByBasic) { + this.sortedManyToManyByBasic = sortedManyToManyByBasic; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedManyToManyByBasicWithComparator + + @ManyToMany + @SortComparator( SimpleBasicSortComparator.class ) + @JoinTable(name = "EntityOfMaps_m2m_basic3", + joinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic3_id1"), + inverseJoinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic3_id2")) + @MapKeyColumn(name = "basic_key") + public SortedMap getSortedManyToManyByBasicWithComparator() { + return sortedManyToManyByBasicWithComparator; + } + + public void setSortedManyToManyByBasicWithComparator(SortedMap sortedManyToManyByBasicWithComparator) { + this.sortedManyToManyByBasicWithComparator = sortedManyToManyByBasicWithComparator; + } + + public void addSortedManyToManyByBasicWithComparator(String key, SimpleEntity value) { + if ( sortedManyToManyByBasicWithComparator == null ) { + sortedManyToManyByBasicWithComparator = new TreeMap<>(); + } + sortedManyToManyByBasicWithComparator.put( key, value ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedManyToManyByBasicWithSortNaturalByDefault + + @ManyToMany + @MapKeyColumn(name = "basic_key") + @JoinTable(name = "EntityOfMaps_m2m_basic4", + joinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic4_id1"), + inverseJoinColumns = @JoinColumn(name = "EntityOfMaps_m2m_basic4_id2")) + public SortedMap getSortedManyToManyByBasicWithSortNaturalByDefault() { + return sortedManyToManyByBasicWithSortNaturalByDefault; + } + + public void setSortedManyToManyByBasicWithSortNaturalByDefault(SortedMap sortedManyToManyByBasicWithSortNaturalByDefault) { + this.sortedManyToManyByBasicWithSortNaturalByDefault = sortedManyToManyByBasicWithSortNaturalByDefault; + } + + public void addSortedManyToManyByBasicWithSortNaturalByDefault(String key, SimpleEntity value) { + if ( sortedManyToManyByBasicWithSortNaturalByDefault == null ) { + sortedManyToManyByBasicWithSortNaturalByDefault = new TreeMap<>(); + } + sortedManyToManyByBasicWithSortNaturalByDefault.put( key, value ); + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfSets.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfSets.java new file mode 100644 index 000000000000..27e81a701e05 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfSets.java @@ -0,0 +1,345 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.HashSet; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.hibernate.annotations.LazyCollection; +import org.hibernate.annotations.LazyCollectionOption; +import org.hibernate.annotations.SortComparator; +import org.hibernate.annotations.SortNatural; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +@Entity +@Table(name = "entity_containing_sets") +public class EntityOfSets { + @Id + private Integer id; + private String name; + + @ElementCollection() + @CollectionTable( name = "EntityOfSet_basic1") + @Column(name = "basic_val") + private Set setOfBasics; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Sorted + + @ElementCollection() + @CollectionTable( name = "EntityOfSet_basic2") + @Column(name = "basic_val") + private SortedSet sortedSetOfBasicsWithSortNaturalByDefault; + + @ElementCollection() + @CollectionTable( name = "EntityOfSet_basic3") + @SortNatural + @Column(name = "basic_val") + private SortedSet sortedSetOfBasics; + + @ElementCollection() + @CollectionTable( name = "EntityOfSet_basic4") + @SortComparator( SimpleBasicSortComparator.class ) + @Column(name = "basic_val") + private SortedSet sortedSetOfBasicsWithComparator; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Ordered + + @ElementCollection() + @CollectionTable( name = "EntityOfSet_basic5") + @OrderBy( "" ) + @Column(name = "basic_val") + private Set orderedSetOfBasics; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Enum elements + + @ElementCollection + @Enumerated(EnumType.STRING) + @CollectionTable(name = "EntityOfSet_enum1") + @Column(name = "enum_val") + private Set setOfEnums; + + @ElementCollection + @Convert(converter = EnumValueConverter.class) + @CollectionTable(name = "EntityOfSet_enum2") + @Column(name = "enum_val") + private Set setOfConvertedEnums; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Embeddables + + @ElementCollection + @CollectionTable( name = "EntityOfSet_comp1") + private Set setOfComponents; + + @ElementCollection + @LazyCollection( LazyCollectionOption.EXTRA ) + @CollectionTable( name = "EntityOfSet_comp2") + private Set extraLazySetOfComponents; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Entity associations + + @OneToMany + @CollectionTable( name = "EntityOfSet_o2m") + private Set setOfOneToMany; + + @ManyToMany + @CollectionTable( name = "EntityOfSet_m2m") + private Set setOfManyToMany; + + + public EntityOfSets() { + } + + public EntityOfSets(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // setOfBasics + + public Set getSetOfBasics() { + return setOfBasics; + } + + public void setSetOfBasics(Set setOfBasics) { + this.setOfBasics = setOfBasics; + } + + public void addBasic(String value) { + if ( setOfBasics == null ) { + setOfBasics = new HashSet<>(); + } + setOfBasics.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // orderedSetOfBasics + + public Set getOrderedSetOfBasics() { + return orderedSetOfBasics; + } + + public void setOrderedSetOfBasics(Set orderedSetOfBasics) { + this.orderedSetOfBasics = orderedSetOfBasics; + } + + public void addOrderedBasic(String value) { + if ( orderedSetOfBasics == null ) { + orderedSetOfBasics = new TreeSet<>(); + } + orderedSetOfBasics.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedSetOfBasics + + public SortedSet getSortedSetOfBasics() { + return sortedSetOfBasics; + } + + public void setSortedSetOfBasics(SortedSet sortedSetOfBasics) { + this.sortedSetOfBasics = sortedSetOfBasics; + } + + public void addSortedBasic(String value) { + if ( sortedSetOfBasics == null ) { + sortedSetOfBasics = new TreeSet<>(); + } + sortedSetOfBasics.add( value ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedSetOfBasicsWithComparator + + public SortedSet getSortedSetOfBasicsWithComparator() { + return sortedSetOfBasicsWithComparator; + } + + public void setSortedSetOfBasicsWithComparator(SortedSet sortedSetOfBasicsWithComparator) { + this.sortedSetOfBasicsWithComparator = sortedSetOfBasicsWithComparator; + } + + public void addSortedBasicWithComparator(String value) { + if ( sortedSetOfBasicsWithComparator == null ) { + sortedSetOfBasicsWithComparator = new TreeSet<>(); + } + sortedSetOfBasicsWithComparator.add( value ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // sortedSetOfBasicsWithSortNaturalByDefault + + public SortedSet getSortedSetOfBasicsWithSortNaturalByDefault() { + return sortedSetOfBasicsWithSortNaturalByDefault; + } + + public void setSortedSetOfBasicsWithSortNaturalByDefault(SortedSet sortedSetOfBasicsWithSortNaturalByDefault) { + this.sortedSetOfBasicsWithSortNaturalByDefault = sortedSetOfBasicsWithSortNaturalByDefault; + } + + public void addSortedBasicWithSortNaturalByDefault(String value) { + if ( sortedSetOfBasicsWithSortNaturalByDefault == null ) { + sortedSetOfBasicsWithSortNaturalByDefault = new TreeSet<>(); + } + sortedSetOfBasicsWithSortNaturalByDefault.add( value ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // setOfConvertedEnums + + public Set getSetOfConvertedEnums() { + return setOfConvertedEnums; + } + + public void setSetOfConvertedEnums(Set setOfConvertedEnums) { + this.setOfConvertedEnums = setOfConvertedEnums; + } + + public void addConvertedEnum(EnumValue value) { + if ( setOfConvertedEnums == null ) { + setOfConvertedEnums = new HashSet<>(); + } + setOfConvertedEnums.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // setOfEnums + + public Set getSetOfEnums() { + return setOfEnums; + } + + public void setSetOfEnums(Set setOfEnums) { + this.setOfEnums = setOfEnums; + } + + public void addEnum(EnumValue value) { + if ( setOfEnums == null ) { + setOfEnums = new HashSet<>(); + } + setOfEnums.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // setOfComponents + + public Set getSetOfComponents() { + return setOfComponents; + } + + public void setSetOfComponents(Set setOfComponents) { + this.setOfComponents = setOfComponents; + } + + public void addComponent(SimpleComponent value) { + if ( setOfComponents == null ) { + setOfComponents = new HashSet<>(); + } + setOfComponents.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // setOfExtraLazyComponents + + public Set getExtraLazySetOfComponents() { + return extraLazySetOfComponents; + } + + public void setExtraLazySetOfComponents(Set extraLazySetOfComponents) { + this.extraLazySetOfComponents = extraLazySetOfComponents; + } + + public void addExtraLazyComponent(SimpleComponent value) { + if ( extraLazySetOfComponents == null ) { + extraLazySetOfComponents = new HashSet<>(); + } + extraLazySetOfComponents.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // setOfOneToMany + + public Set getSetOfOneToMany() { + return setOfOneToMany; + } + + public void setSetOfOneToMany(Set setOfOneToMany) { + this.setOfOneToMany = setOfOneToMany; + } + + public void addOneToMany(SimpleEntity value) { + if ( setOfOneToMany == null ) { + setOfOneToMany = new HashSet<>(); + } + setOfOneToMany.add( value ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // setOfManyToMany + + public Set getSetOfManyToMany() { + return setOfManyToMany; + } + + public void setSetOfManyToMany(Set setOfManyToMany) { + this.setOfManyToMany = setOfManyToMany; + } + + public void addManyToMany(SimpleEntity value) { + if ( setOfManyToMany == null ) { + setOfManyToMany = new HashSet<>(); + } + setOfManyToMany.add( value ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithAggregateId.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithAggregateId.java new file mode 100644 index 000000000000..43688265c062 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithAggregateId.java @@ -0,0 +1,79 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.io.Serializable; + +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +public class EntityWithAggregateId { + private Key key; + private String data; + + public EntityWithAggregateId() { + } + + public EntityWithAggregateId(Key key, String data) { + this.key = key; + this.data = data; + } + + @EmbeddedId + public Key getKey() { + return key; + } + + public void setKey(Key key) { + this.key = key; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + + @Embeddable + public static class Key implements Serializable { + private String value1; + private String value2; + + public Key() { + } + + public Key(String value1, String value2) { + this.value1 = value1; + this.value2 = value2; + } + + public String getValue1() { + return value1; + } + + public void setValue1(String value1) { + this.value1 = value1; + } + + public String getValue2() { + return value2; + } + + public void setValue2(String value2) { + this.value2 = value2; + } + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyManyToOneSelfReference.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyManyToOneSelfReference.java new file mode 100644 index 000000000000..b0b7de5ed9c9 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyManyToOneSelfReference.java @@ -0,0 +1,95 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * @author Andrea Boriero + */ +@Entity +@Table(name = "entity_lm2o_selfref") +public class EntityWithLazyManyToOneSelfReference { + private Integer id; + + // alphabetical + private String name; + private EntityWithLazyManyToOneSelfReference other; + private Integer someInteger; + + EntityWithLazyManyToOneSelfReference() { + } + + public EntityWithLazyManyToOneSelfReference(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + public EntityWithLazyManyToOneSelfReference( + Integer id, + String name, + Integer someInteger, + EntityWithLazyManyToOneSelfReference other) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + this.other = other; + } + + public EntityWithLazyManyToOneSelfReference( + Integer id, + String name, + EntityWithLazyManyToOneSelfReference other, + Integer someInteger) { + this.id = id; + this.name = name; + this.other = other; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + public EntityWithLazyManyToOneSelfReference getOther() { + return other; + } + + public void setOther(EntityWithLazyManyToOneSelfReference other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} + diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyOneToOne.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyOneToOne.java new file mode 100644 index 000000000000..cd5464ebc2e5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyOneToOne.java @@ -0,0 +1,68 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToOne; + +/** + * @author Andrea Boriero + */ +@Entity +public class EntityWithLazyOneToOne { + private Integer id; + + // alphabetical + private String name; + private SimpleEntity other; + private Integer someInteger; + + public EntityWithLazyOneToOne() { + } + + public EntityWithLazyOneToOne(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToOne(fetch = FetchType.LAZY) + public SimpleEntity getOther() { + return other; + } + + public void setOther(SimpleEntity other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneJoinTable.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneJoinTable.java new file mode 100644 index 000000000000..69d8f54b0ad9 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneJoinTable.java @@ -0,0 +1,89 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToOne; + +/** + * @author Andrea Boriero + */ +@Entity +public class EntityWithManyToOneJoinTable { + private Integer id; + + // alphabetical + private String name; + private SimpleEntity other; + private Integer someInteger; + private BasicEntity lazyOther; + + public EntityWithManyToOneJoinTable() { + } + + public EntityWithManyToOneJoinTable(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToOne + @JoinTable(name = "ENTITY_OTHER", + joinColumns = { + @JoinColumn( name = "LHS_ID") + }, + inverseJoinColumns = { + @JoinColumn(name="RHS_ID") + } + ) + public SimpleEntity getOther() { + return other; + } + + public void setOther(SimpleEntity other) { + this.other = other; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinTable(name = "ENTITY_ANOTHER") + public BasicEntity getLazyOther() { + return lazyOther; + } + + public void setLazyOther(BasicEntity lazyOther) { + this.lazyOther = lazyOther; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneSelfReference.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneSelfReference.java new file mode 100644 index 000000000000..9d0cc97cf745 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneSelfReference.java @@ -0,0 +1,94 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity +@Table(name = "entity_m2o_selfref") +@SuppressWarnings("unused") +public class EntityWithManyToOneSelfReference { + private Integer id; + + // alphabetical + private String name; + private EntityWithManyToOneSelfReference other; + private Integer someInteger; + + EntityWithManyToOneSelfReference() { + } + + public EntityWithManyToOneSelfReference(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + public EntityWithManyToOneSelfReference( + Integer id, + String name, + Integer someInteger, + EntityWithManyToOneSelfReference other) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + this.other = other; + } + + public EntityWithManyToOneSelfReference( + Integer id, + String name, + EntityWithManyToOneSelfReference other, + Integer someInteger) { + this.id = id; + this.name = name; + this.other = other; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToOne + @JoinColumn + public EntityWithManyToOneSelfReference getOther() { + return other; + } + + public void setOther(EntityWithManyToOneSelfReference other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneWithoutJoinTable.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneWithoutJoinTable.java new file mode 100644 index 000000000000..075c43a97f7a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneWithoutJoinTable.java @@ -0,0 +1,55 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +/** + * @author Chris Cranford + */ +@Entity +public class EntityWithManyToOneWithoutJoinTable { + private Integer id; + private Integer someInteger; + private EntityWithOneToManyNotOwned owner; + + EntityWithManyToOneWithoutJoinTable() { + } + + public EntityWithManyToOneWithoutJoinTable(Integer id, Integer someInteger) { + this.id = id; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } + + @ManyToOne + public EntityWithOneToManyNotOwned getOwner() { + return owner; + } + + public void setOwner(EntityWithOneToManyNotOwned owner) { + this.owner = owner; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNonIdAttributeNamedId.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNonIdAttributeNamedId.java new file mode 100644 index 000000000000..88dd98761d45 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNonIdAttributeNamedId.java @@ -0,0 +1,37 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@Entity +@SuppressWarnings("unused") +public class EntityWithNonIdAttributeNamedId { + private Integer pk; + private String id; + + @Id + public Integer getPk() { + return pk; + } + + public void setPk(Integer pk) { + this.pk = pk; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNotAggregateId.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNotAggregateId.java new file mode 100644 index 000000000000..2d885db1f3e2 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNotAggregateId.java @@ -0,0 +1,99 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.io.Serializable; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; + +/** + * @author Andrea Boriero + */ +@Entity +@IdClass(EntityWithNotAggregateId.PK.class) +public class EntityWithNotAggregateId { + + @Id + private Integer value1; + + @Id + private String value2; + + private String data; + + public PK getId() { + return new PK( value1, value2 ); + } + + public void setId(PK id) { + this.value1 = id.getValue1(); + this.value2 = id.getValue2(); + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public static class PK implements Serializable { + private Integer value1; + private String value2; + + public PK() { + } + + public PK(Integer value1, String value2) { + this.value1 = value1; + this.value2 = value2; + } + + public Integer getValue1() { + return value1; + } + + public void setValue1(Integer value1) { + this.value1 = value1; + } + + public String getValue2() { + return value2; + } + + public void setValue2(String value2) { + this.value2 = value2; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + PK pk = (PK) o; + + if ( value1 != null ? !value1.equals( pk.value1 ) : pk.value1 != null ) { + return false; + } + return value2 != null ? value2.equals( pk.value2 ) : pk.value2 == null; + } + + @Override + public int hashCode() { + int result = value1 != null ? value1.hashCode() : 0; + result = 31 * result + ( value2 != null ? value2.hashCode() : 0 ); + return result; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToMany.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToMany.java new file mode 100644 index 000000000000..9542d9387580 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToMany.java @@ -0,0 +1,97 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.sql.Types; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.annotations.CollectionId; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Type; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +/** + * @author Andrea Boriero + */ +@Entity +@GenericGenerator(name="increment", strategy = "increment") +public class EntityWithOneToMany { + private Integer id; + + // alphabetical + private String name; + private Set others = new HashSet<>( ); + private List othersIdentifierBag = new ArrayList<>( ); + private Integer someInteger; + + public EntityWithOneToMany() { + } + + public EntityWithOneToMany(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToMany(fetch = FetchType.LAZY) + public Set getOthers() { + return others; + } + + public void setOthers(Set others) { + this.others = others; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } + + public void addOther(SimpleEntity other) { + others.add( other ); + } + + @OneToMany + @CollectionTable(name = "idbag") + @CollectionId( column = @Column(name = "BAG_ID"), generator = "increment", type = @Type( type = "big_integer" ) ) + public List getOthersIdentifierBag() { + return othersIdentifierBag; + } + + public void setOthersIdentifierBag(List othersIdentifierBag) { + this.othersIdentifierBag = othersIdentifierBag; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToManyNotOwned.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToManyNotOwned.java new file mode 100644 index 000000000000..01103bc477fb --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToManyNotOwned.java @@ -0,0 +1,46 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +/** + * @author Chris Cranford + */ +@Entity +public class EntityWithOneToManyNotOwned { + private Integer id; + private List children = new ArrayList<>(); + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @OneToMany(mappedBy = "owner") + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public void addChild(EntityWithManyToOneWithoutJoinTable child) { + child.setOwner( this ); + getChildren().add( child ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOne.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOne.java new file mode 100644 index 000000000000..e017ba092d26 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOne.java @@ -0,0 +1,67 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; + +/** + * @author Andrea Boriero + */ +@Entity +public class EntityWithOneToOne { + private Integer id; + + // alphabetical + private String name; + private SimpleEntity other; + private Integer someInteger; + + public EntityWithOneToOne() { + } + + public EntityWithOneToOne(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToOne + public SimpleEntity getOther() { + return other; + } + + public void setOther(SimpleEntity other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneJoinTable.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneJoinTable.java new file mode 100644 index 000000000000..e8eca7275762 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneJoinTable.java @@ -0,0 +1,71 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinTable; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +/** + * @author Andrea Boriero + */ +@Entity +@Table(name = "EntityWithOneToOneJoinTable") +public class EntityWithOneToOneJoinTable { + private Integer id; + + // alphabetical + private String name; + private SimpleEntity other; + private Integer someInteger; + + public EntityWithOneToOneJoinTable() { + } + + public EntityWithOneToOneJoinTable(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToOne + @JoinTable(name = "Entity_SimpleEntity") + public SimpleEntity getOther() { + return other; + } + + public void setOther(SimpleEntity other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneSharingPrimaryKey.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneSharingPrimaryKey.java new file mode 100644 index 000000000000..0706261b7e96 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneSharingPrimaryKey.java @@ -0,0 +1,71 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.Table; + +/** + * @author Andrea Boriero + */ +@Entity +@Table(name = "entity_o2o_sharepk") +public class EntityWithOneToOneSharingPrimaryKey { + private Integer id; + + // alphabetical + private String name; + private SimpleEntity other; + private Integer someInteger; + + public EntityWithOneToOneSharingPrimaryKey() { + } + + public EntityWithOneToOneSharingPrimaryKey(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToOne + @PrimaryKeyJoinColumn + public SimpleEntity getOther() { + return other; + } + + public void setOther(SimpleEntity other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EnumValue.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EnumValue.java new file mode 100644 index 000000000000..06097cf6ec9d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EnumValue.java @@ -0,0 +1,47 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +/** + * @author Steve Ebersole + */ +public enum EnumValue { + ONE( "first" ), + TWO( "second" ), + THREE( "third" ); + + private final String code; + + EnumValue(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public static EnumValue fromCode(String code) { + if ( code == null || code.isEmpty() ) { + return null; + } + + switch ( code ) { + case "first": { + return ONE; + } + case "second": { + return TWO; + } + case "third": { + return THREE; + } + default: { + throw new RuntimeException( "Could not convert enum code : " + code ); + } + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EnumValueConverter.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EnumValueConverter.java new file mode 100644 index 000000000000..c8899fd8eb21 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EnumValueConverter.java @@ -0,0 +1,24 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.AttributeConverter; + +/** + * @author Steve Ebersole + */ +public class EnumValueConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(EnumValue domainValue) { + return domainValue == null ? null : domainValue.getCode(); + } + + @Override + public EnumValue convertToEntityAttribute(String dbData) { + return EnumValue.fromCode( dbData ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/GambitDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/GambitDomainModel.java new file mode 100644 index 000000000000..966a29bebf5d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/GambitDomainModel.java @@ -0,0 +1,44 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import org.hibernate.testing.orm.domain.AbstractDomainModelDescriptor; + +/** + * @author Steve Ebersole + */ +public class GambitDomainModel extends AbstractDomainModelDescriptor { + public static final GambitDomainModel INSTANCE = new GambitDomainModel(); + + public GambitDomainModel() { + super( + BasicEntity.class, + VersionedEntity.class, + Component.class, + EmbeddedIdEntity.class, + EntityOfArrays.class, + EntityOfBasics.class, + EntityOfComposites.class, + EntityOfDynamicComponent.class, + EntityOfLists.class, + EntityOfMaps.class, + EntityOfSets.class, + EntityWithLazyManyToOneSelfReference.class, + EntityWithLazyOneToOne.class, + EntityWithManyToOneJoinTable.class, + EntityWithManyToOneSelfReference.class, + EntityWithNonIdAttributeNamedId.class, + EntityWithAggregateId.class, + EntityWithOneToMany.class, + EntityWithOneToOne.class, + EntityWithOneToOneJoinTable.class, + EntityWithOneToOneSharingPrimaryKey.class, + Shirt.class, + SimpleEntity.class + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/MutableValue.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/MutableValue.java new file mode 100644 index 000000000000..b933fdbb0e3d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/MutableValue.java @@ -0,0 +1,33 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.io.Serializable; + +/** + * A mutable (as in non-`@Immutable`) value. Mainly used for testing + * JPA AttributeConverter support for mutable domain values in regards + * to caching, dirty-checking, etc + */ +public class MutableValue implements Serializable { + private String state; + + public MutableValue() { + } + + public MutableValue(String state) { + this.state = state; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Shirt.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Shirt.java new file mode 100644 index 000000000000..7ff8e489e026 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Shirt.java @@ -0,0 +1,109 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.AttributeConverter; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; + +/** + * @author Chris Cranford + */ +@Entity +public class Shirt { + @Id + private Integer id; + + @Convert(converter = ShirtStringToIntegerConverter.class) + private String data; + + @Enumerated + @Column(name = "shirt_size") + private Size size; + + @Enumerated(EnumType.STRING) + private Color color; + + public enum Size { + SMALL, + MEDIUM, + LARGE, + XLARGE + } + + public enum Color { + WHITE, + GREY, + BLACK, + BLUE, + TAN + } + + public static class ShirtStringToIntegerConverter implements AttributeConverter { + @Override + public Integer convertToDatabaseColumn(String attribute) { + if ( attribute != null ) { + if ( attribute.equalsIgnoreCase( "X" ) ) { + return 1; + } + else if ( attribute.equalsIgnoreCase( "Y" ) ) { + return 2; + } + } + return null; + } + + @Override + public String convertToEntityAttribute(Integer dbData) { + if ( dbData != null ) { + switch ( Integer.valueOf( dbData ) ) { + case 1: + return "X"; + case 2: + return "Y"; + } + } + return null; + } + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public Size getSize() { + return size; + } + + public void setSize(Size size) { + this.size = size; + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleBasicSortComparator.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleBasicSortComparator.java new file mode 100644 index 000000000000..9849c3754e0a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleBasicSortComparator.java @@ -0,0 +1,20 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.Comparator; + +/** + * @author Nathan Xu + */ +public class SimpleBasicSortComparator implements Comparator { + + @Override + public int compare(String s1, String s2) { + return String.CASE_INSENSITIVE_ORDER.compare( s1, s2 ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleComponent.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleComponent.java new file mode 100644 index 000000000000..b586ee849b1e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleComponent.java @@ -0,0 +1,42 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Embeddable; + +/** + * @author Steve Ebersole + */ +@Embeddable +public class SimpleComponent { + private String anAttribute; + private String anotherAttribute; + + public SimpleComponent() { + } + + public SimpleComponent(String anAttribute, String anotherAttribute) { + this.anAttribute = anAttribute; + this.anotherAttribute = anotherAttribute; + } + + public String getAnAttribute() { + return anAttribute; + } + + public void setAnAttribute(String anAttribute) { + this.anAttribute = anAttribute; + } + + public String getAnotherAttribute() { + return anotherAttribute; + } + + public void setAnotherAttribute(String anotherAttribute) { + this.anotherAttribute = anotherAttribute; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleEntity.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleEntity.java new file mode 100644 index 000000000000..c6f062304501 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleEntity.java @@ -0,0 +1,119 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.time.Instant; +import java.util.Date; + +import org.hibernate.annotations.NaturalId; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +/** + * @author Steve Ebersole + */ +@Entity +@Table(name = "SIMPLE_ENTITY") +public class SimpleEntity { + private Integer id; + + // NOTE : alphabetical + private Date someDate; + private Instant someInstant; + private Integer someInteger; + private Long someLong; + private String someString; + + public SimpleEntity() { + } + + public SimpleEntity( + Integer id, + String someString) { + this.id = id; + this.someString = someString; + } + + public SimpleEntity( + Integer id, + String someString, + Long someLong) { + this.id = id; + this.someString = someString; + this.someLong = someLong; + } + + public SimpleEntity( + Integer id, + Date someDate, + Instant someInstant, + Integer someInteger, + Long someLong, + String someString) { + this.id = id; + this.someDate = someDate; + this.someInstant = someInstant; + this.someInteger = someInteger; + this.someLong = someLong; + this.someString = someString; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSomeString() { + return someString; + } + + public void setSomeString(String someString) { + this.someString = someString; + } + + @NaturalId + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } + + public Long getSomeLong() { + return someLong; + } + + public void setSomeLong(Long someLong) { + this.someLong = someLong; + } + + @Temporal( TemporalType.TIMESTAMP ) + public Date getSomeDate() { + return someDate; + } + + public void setSomeDate(Date someDate) { + this.someDate = someDate; + } + + public Instant getSomeInstant() { + return someInstant; + } + + public void setSomeInstant(Instant someInstant) { + this.someInstant = someInstant; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/VersionedEntity.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/VersionedEntity.java new file mode 100644 index 000000000000..68c8698b61cf --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/VersionedEntity.java @@ -0,0 +1,72 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.Objects; + +import org.hibernate.annotations.NaturalId; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Version; + +/** + * @author Chris Cranford + */ +@Entity +public class VersionedEntity { + @Id + private Integer id; + @Version + private Integer version; + @NaturalId + private String code; + private String data; + + public VersionedEntity() { + + } + + public VersionedEntity(Integer id, String data) { + this.id = id; + this.data = data; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + VersionedEntity that = (VersionedEntity) o; + return Objects.equals( id, that.id ) && + Objects.equals( data, that.data ); + } + + @Override + public int hashCode() { + return Objects.hash( id, data ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Account.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Account.java new file mode 100644 index 000000000000..1cc8b9d5ffee --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Account.java @@ -0,0 +1,99 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.helpdesk; + +import javax.persistence.AttributeConverter; +import javax.persistence.Convert; +import javax.persistence.Converter; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@Entity +public class Account { + private Integer id; + + private Status loginStatus; + private Status systemAccessStatus; + private Status serviceStatus; + + public Account() { + } + + public Account( + Integer id, + Status loginStatus, + Status systemAccessStatus, + Status serviceStatus) { + this.id = id; + this.loginStatus = loginStatus; + this.systemAccessStatus = systemAccessStatus; + this.serviceStatus = serviceStatus; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @Enumerated( EnumType.ORDINAL ) + public Status getLoginStatus() { + return loginStatus; + } + + public void setLoginStatus(Status loginStatus) { + this.loginStatus = loginStatus; + } + + @Enumerated( EnumType.STRING ) + public Status getSystemAccessStatus() { + return systemAccessStatus; + } + + public void setSystemAccessStatus(Status systemAccessStatus) { + this.systemAccessStatus = systemAccessStatus; + } + + @Convert( converter = ServiceStatusConverter.class ) + public Status getServiceStatus() { + return serviceStatus; + } + + public void setServiceStatus(Status serviceStatus) { + this.serviceStatus = serviceStatus; + } + + @Converter( autoApply = false ) + private static class ServiceStatusConverter implements AttributeConverter { + + @Override + public Integer convertToDatabaseColumn(Status attribute) { + if ( attribute == null ) { + return null; + } + + return attribute.getCode(); + } + + @Override + public Status convertToEntityAttribute(Integer dbData) { + if ( dbData == null ) { + return null; + } + + return Status.fromCode( dbData ); + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/HelpDeskDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/HelpDeskDomainModel.java new file mode 100644 index 000000000000..632d7eefa6a0 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/HelpDeskDomainModel.java @@ -0,0 +1,25 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.helpdesk; + +import org.hibernate.testing.orm.domain.AbstractDomainModelDescriptor; + +/** + * @author Steve Ebersole + */ +public class HelpDeskDomainModel extends AbstractDomainModelDescriptor { + public static final HelpDeskDomainModel INSTANCE = new HelpDeskDomainModel(); + + public HelpDeskDomainModel() { + super( + Status.class, + Account.class, + Ticket.class, + Incident.class + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Incident.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Incident.java new file mode 100644 index 000000000000..2cae1d2aee8a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Incident.java @@ -0,0 +1,99 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.helpdesk; + +import java.time.Instant; + +import javax.persistence.ColumnResult; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.SqlResultSetMapping; + +/** + * @author Steve Ebersole + */ +@Entity +@SqlResultSetMapping( + name = "incident_summary", + columns = { + @ColumnResult( name = "id" ), + @ColumnResult( name = "description" ), + @ColumnResult( name = "reported", type = Instant.class ) + } +) +public class Incident { + private Integer id; + private String description; + + private Instant reported; + + private Instant effectiveStart; + private Instant effectiveEnd; + + public Incident() { + } + + public Incident(Integer id, String description, Instant reported) { + this.id = id; + this.description = description; + this.reported = reported; + } + + public Incident( + Integer id, + String description, + Instant reported, + Instant effectiveStart, + Instant effectiveEnd) { + this.id = id; + this.description = description; + this.reported = reported; + this.effectiveStart = effectiveStart; + this.effectiveEnd = effectiveEnd; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Instant getReported() { + return reported; + } + + public void setReported(Instant reported) { + this.reported = reported; + } + + public Instant getEffectiveStart() { + return effectiveStart; + } + + public void setEffectiveStart(Instant effectiveStart) { + this.effectiveStart = effectiveStart; + } + + public Instant getEffectiveEnd() { + return effectiveEnd; + } + + public void setEffectiveEnd(Instant effectiveEnd) { + this.effectiveEnd = effectiveEnd; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Status.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Status.java new file mode 100644 index 000000000000..484a4c7aed34 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Status.java @@ -0,0 +1,31 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.helpdesk; + +public enum Status { + CREATED, + INITIALIZING, + ACTIVE, + INACTIVE; + + private final int code; + + Status() { + this.code = this.ordinal() + 1000; + } + + public int getCode() { + return code; + } + + public static Status fromCode(Integer code) { + if ( code == null ) { + return null; + } + return values()[ code - 1000 ]; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Ticket.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Ticket.java new file mode 100644 index 000000000000..1b4f6f5ab3dc --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Ticket.java @@ -0,0 +1,36 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.helpdesk; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@Entity +public class Ticket { + @Id + private Integer id; + + @Column(name = "ticket_key") + private String key; + + private String subject; + private String details; + +// private Incident associatedIncident; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CardPayment.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CardPayment.java new file mode 100644 index 000000000000..06a6e0e78df1 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CardPayment.java @@ -0,0 +1,35 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.money.MonetaryAmount; + +import javax.persistence.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +public class CardPayment extends Payment { + private Integer transactionId; + + public CardPayment() { + } + + public CardPayment(Integer id, Integer transactionId, MonetaryAmount amount) { + super( id,amount ); + this.transactionId = transactionId; + } + + public Integer getTransactionId() { + return transactionId; + } + + public void setTransactionId(Integer transactionId) { + this.transactionId = transactionId; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CashPayment.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CashPayment.java new file mode 100644 index 000000000000..d51c2affa54b --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CashPayment.java @@ -0,0 +1,24 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.money.MonetaryAmount; + +import javax.persistence.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +public class CashPayment extends Payment { + public CashPayment() { + } + + public CashPayment(Integer id, MonetaryAmount amount) { + super( id, amount ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/DomesticVendor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/DomesticVendor.java new file mode 100644 index 000000000000..e0be8c01cb63 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/DomesticVendor.java @@ -0,0 +1,24 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +@DiscriminatorValue( "domestic" ) +public class DomesticVendor extends Vendor { + public DomesticVendor() { + } + + public DomesticVendor(Integer id, String name, String billingEntity) { + super( id, name, billingEntity ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/ForeignVendor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/ForeignVendor.java new file mode 100644 index 000000000000..1639bfaf4103 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/ForeignVendor.java @@ -0,0 +1,24 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +@DiscriminatorValue( "foreign" ) +public class ForeignVendor extends Vendor { + public ForeignVendor() { + } + + public ForeignVendor(Integer id, String name, String billingEntity) { + super( id, name, billingEntity ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/LineItem.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/LineItem.java new file mode 100644 index 000000000000..2a85e9c6e3dc --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/LineItem.java @@ -0,0 +1,73 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.money.MonetaryAmount; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +/** + * @author Steve Ebersole + */ +@Entity +public class LineItem { + private Integer id; + private Product product; + + private int quantity; + private MonetaryAmount subTotal; + + private Order order; + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ManyToOne + @JoinColumn( name = "product_id" ) + public Product getProduct() { + return product; + } + + public void setProduct(Product product) { + this.product = product; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public MonetaryAmount getSubTotal() { + return subTotal; + } + + public void setSubTotal(MonetaryAmount subTotal) { + this.subTotal = subTotal; + } + + @ManyToOne + @JoinColumn( name = "order_id" ) + public Order getOrder() { + return order; + } + + public void setOrder(Order order) { + this.order = order; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Name.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Name.java new file mode 100644 index 000000000000..da5f4eff9f9f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Name.java @@ -0,0 +1,75 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.persistence.Embeddable; + +/** + * @author Steve Ebersole + */ +@Embeddable +@SuppressWarnings("unused") +public class Name { + private String familyName; + private String familiarName; + + private String prefix; + private String suffix; + + public Name() { + } + + public Name(String familyName, String familiarName) { + this.familyName = familyName; + this.familiarName = familiarName; + } + + public Name(String familyName, String familiarName, String suffix) { + this.familyName = familyName; + this.familiarName = familiarName; + this.suffix = suffix; + } + + public Name(String familyName, String familiarName, String prefix, String suffix) { + this.familyName = familyName; + this.familiarName = familiarName; + this.prefix = prefix; + this.suffix = suffix; + } + + public String getFamilyName() { + return familyName; + } + + public void setFamilyName(String familyName) { + this.familyName = familyName; + } + + public String getFamiliarName() { + return familiarName; + } + + public void setFamiliarName(String familiarName) { + this.familiarName = familiarName; + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String getSuffix() { + return suffix; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Order.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Order.java new file mode 100644 index 000000000000..aea13d0d92af --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Order.java @@ -0,0 +1,66 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.retail; + +import java.time.Instant; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity +@Table( name = "orders") +public class Order { + private Integer id; + private Instant transacted; + + private Payment payment; + private SalesAssociate salesAssociate; + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Instant getTransacted() { + return transacted; + } + + public void setTransacted(Instant transacted) { + this.transacted = transacted; + } + + @ManyToOne + @JoinColumn(name = "payment_id") + public Payment getPayment() { + return payment; + } + + public void setPayment(Payment payment) { + this.payment = payment; + } + + @ManyToOne + @JoinColumn(name = "associate_id") + public SalesAssociate getSalesAssociate() { + return salesAssociate; + } + + public void setSalesAssociate(SalesAssociate salesAssociate) { + this.salesAssociate = salesAssociate; + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Payment.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Payment.java new file mode 100644 index 000000000000..d4dcede7cadc --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Payment.java @@ -0,0 +1,51 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.money.MonetaryAmount; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity +@Inheritance(strategy = InheritanceType.JOINED) +@Table( name = "payments" ) +public abstract class Payment { + private Integer id; + private MonetaryAmount amount; + + public Payment() { + } + + public Payment(Integer id, MonetaryAmount amount) { + this.id = id; + this.amount = amount; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public MonetaryAmount getAmount() { + return amount; + } + + public void setAmount(MonetaryAmount amount) { + this.amount = amount; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Product.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Product.java new file mode 100644 index 000000000000..a59c61b3cd9b --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Product.java @@ -0,0 +1,82 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.retail; + +import java.util.UUID; +import javax.money.MonetaryAmount; + +import org.hibernate.annotations.NaturalId; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +/** + * @author Steve Ebersole + */ +@Entity +public class Product { + private Integer id; + private UUID sku; + + private Vendor vendor; + + private MonetaryAmount currentSellPrice; + + public Product() { + } + + public Product(Integer id, UUID sku, Vendor vendor) { + this.id = id; + this.sku = sku; + this.vendor = vendor; + } + + public Product(Integer id, UUID sku, Vendor vendor, MonetaryAmount currentSellPrice) { + this.id = id; + this.sku = sku; + this.vendor = vendor; + this.currentSellPrice = currentSellPrice; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ManyToOne + @JoinColumn + public Vendor getVendor() { + return vendor; + } + + public void setVendor(Vendor vendor) { + this.vendor = vendor; + } + + @NaturalId + public UUID getSku() { + return sku; + } + + public void setSku(UUID sku) { + this.sku = sku; + } + + public MonetaryAmount getCurrentSellPrice() { + return currentSellPrice; + } + + public void setCurrentSellPrice(MonetaryAmount currentSellPrice) { + this.currentSellPrice = currentSellPrice; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/RetailDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/RetailDomainModel.java new file mode 100644 index 000000000000..255f650ef0fa --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/RetailDomainModel.java @@ -0,0 +1,61 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.retail; + +import java.util.EnumSet; + +import org.hibernate.boot.MetadataSources; + +import org.hibernate.testing.orm.domain.AbstractDomainModelDescriptor; +import org.hibernate.testing.orm.domain.MappingFeature; +import org.hibernate.testing.orm.domain.MonetaryAmountConverter; + +import static org.hibernate.testing.orm.domain.MappingFeature.CONVERTER; +import static org.hibernate.testing.orm.domain.MappingFeature.EMBEDDABLE; +import static org.hibernate.testing.orm.domain.MappingFeature.JOINED_INHERIT; +import static org.hibernate.testing.orm.domain.MappingFeature.JOIN_COLUMN; +import static org.hibernate.testing.orm.domain.MappingFeature.MANY_ONE; +import static org.hibernate.testing.orm.domain.MappingFeature.SECONDARY_TABLE; + +/** + * @author Steve Ebersole + */ +public class RetailDomainModel extends AbstractDomainModelDescriptor { + public static final RetailDomainModel INSTANCE = new RetailDomainModel(); + + public RetailDomainModel() { + super( + MonetaryAmountConverter.class, + SalesAssociate.class, + Vendor.class, + DomesticVendor.class, + ForeignVendor.class, + Product.class, + Order.class, + LineItem.class, + Payment.class, + CashPayment.class, + CardPayment.class + ); + } + + public static void applyRetailModel(MetadataSources sources) { + INSTANCE.applyDomainModel( sources ); + } + + @Override + public EnumSet getMappingFeaturesUsed() { + return EnumSet.of( + CONVERTER, + EMBEDDABLE, + MANY_ONE, + JOIN_COLUMN, + SECONDARY_TABLE, + JOINED_INHERIT + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/SalesAssociate.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/SalesAssociate.java new file mode 100644 index 000000000000..6e22d4f0a752 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/SalesAssociate.java @@ -0,0 +1,47 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity +@Table( name = "ASSOCIATE") +public class SalesAssociate { + private Integer id; + + private Name name; + + public SalesAssociate() { + } + + public SalesAssociate(Integer id, Name name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Name getName() { + return name; + } + + public void setName(Name name) { + this.name = name; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Vendor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Vendor.java new file mode 100644 index 000000000000..2e499ec1e150 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Vendor.java @@ -0,0 +1,61 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.persistence.DiscriminatorColumn; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.SecondaryTable; + +/** + * @author Steve Ebersole + */ +@Entity +@Inheritance( strategy = InheritanceType.SINGLE_TABLE ) +@DiscriminatorColumn( name = "vendor_type" ) +@SecondaryTable(name = "vendor_supp") +public class Vendor { + private Integer id; + private String name; + private String billingEntity; + + public Vendor() { + } + + public Vendor(Integer id, String name, String billingEntity) { + this.id = id; + this.name = name; + this.billingEntity = billingEntity; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getBillingEntity() { + return billingEntity; + } + + public void setBillingEntity(String billingEntity) { + this.billingEntity = billingEntity; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitDescriptorAdapter.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitDescriptorAdapter.java new file mode 100644 index 000000000000..ac085485efb3 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitDescriptorAdapter.java @@ -0,0 +1,116 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.jpa; + +import java.net.URL; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import javax.sql.DataSource; + +import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; + +import javax.persistence.SharedCacheMode; +import javax.persistence.ValidationMode; +import javax.persistence.spi.PersistenceUnitTransactionType; + +/** + * @author Steve Ebersole + */ +public class PersistenceUnitDescriptorAdapter implements PersistenceUnitDescriptor { + private final String name = "persistenceUnitDescriptorAdapter@" + System.identityHashCode( this ); + private Properties properties; + + @Override + public String getName() { + return name; + } + + @Override + public boolean isUseQuotedIdentifiers() { + return false; + } + + @Override + public String getProviderClassName() { + return HibernatePersistenceProvider.class.getName(); + } + + @Override + public PersistenceUnitTransactionType getTransactionType() { + return null; + } + + @Override + public DataSource getJtaDataSource() { + return null; + } + + @Override + public DataSource getNonJtaDataSource() { + return null; + } + + @Override + public List getMappingFileNames() { + return Collections.emptyList(); + } + + @Override + public List getJarFileUrls() { + return Collections.emptyList(); + } + + @Override + public URL getPersistenceUnitRootUrl() { + return null; + } + + @Override + public List getManagedClassNames() { + return Collections.emptyList(); + } + + @Override + public boolean isExcludeUnlistedClasses() { + return false; + } + + @Override + public SharedCacheMode getSharedCacheMode() { + return null; + } + + @Override + public ValidationMode getValidationMode() { + return null; + } + + @Override + public Properties getProperties() { + if ( properties == null ) { + properties = new Properties(); + } + return properties; + } + + @Override + public ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + + @Override + public ClassLoader getTempClassLoader() { + return null; + } + + @Override + public void pushClassTransformer(EnhancementContext enhancementContext) { + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitInfoAdapter.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitInfoAdapter.java new file mode 100644 index 000000000000..36ecb8332305 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitInfoAdapter.java @@ -0,0 +1,105 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.jpa; + +import java.net.URL; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import javax.sql.DataSource; + +import org.hibernate.jpa.HibernatePersistenceProvider; + +import javax.persistence.SharedCacheMode; +import javax.persistence.ValidationMode; +import javax.persistence.spi.ClassTransformer; +import javax.persistence.spi.PersistenceUnitInfo; +import javax.persistence.spi.PersistenceUnitTransactionType; + +/** + * Implementation of {@link PersistenceUnitInfo} for testing use. + * + * Expected usage is to override methods relevant to their specific tests. + * + * See {@link PersistenceUnitInfoImpl} for a more bean-like implementation + * + * @author Steve Ebersole + */ +public class PersistenceUnitInfoAdapter implements PersistenceUnitInfo { + private final String name = "persistenceUnitInfoAdapter@" + System.identityHashCode( this ); + private Properties properties; + + public String getPersistenceUnitName() { + return name; + } + + public String getPersistenceProviderClassName() { + return HibernatePersistenceProvider.class.getName(); + } + + public PersistenceUnitTransactionType getTransactionType() { + return null; + } + + public DataSource getJtaDataSource() { + return null; + } + + public DataSource getNonJtaDataSource() { + return null; + } + + public List getMappingFileNames() { + return Collections.emptyList(); + } + + public List getJarFileUrls() { + return Collections.emptyList(); + } + + public URL getPersistenceUnitRootUrl() { + return null; + } + + public List getManagedClassNames() { + return Collections.emptyList(); + } + + public boolean excludeUnlistedClasses() { + return false; + } + + public SharedCacheMode getSharedCacheMode() { + return null; + } + + public ValidationMode getValidationMode() { + return null; + } + + public Properties getProperties() { + if ( properties == null ) { + properties = new Properties(); + } + return properties; + } + + public String getPersistenceXMLSchemaVersion() { + return null; + } + + public ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + + public void addTransformer(ClassTransformer transformer) { + } + + public ClassLoader getNewTempClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitInfoImpl.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitInfoImpl.java new file mode 100644 index 000000000000..4e5d452e7bdc --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jpa/PersistenceUnitInfoImpl.java @@ -0,0 +1,163 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.jpa; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import javax.sql.DataSource; + +import org.hibernate.jpa.HibernatePersistenceProvider; + +import javax.persistence.SharedCacheMode; +import javax.persistence.ValidationMode; +import javax.persistence.spi.ClassTransformer; +import javax.persistence.spi.PersistenceUnitInfo; +import javax.persistence.spi.PersistenceUnitTransactionType; + +/** + * Implementation of {@link PersistenceUnitInfo} for testing use. + * + * This implementation provides a bean-like contract for providing PU information. + * + * See {@link PersistenceUnitInfoAdapter} for an override-based solution + * + * @author Steve Ebersole + */ +public class PersistenceUnitInfoImpl implements PersistenceUnitInfo { + private final String name; + private final Properties properties = new Properties(); + + private PersistenceUnitTransactionType transactionType; + private SharedCacheMode cacheMode; + private ValidationMode validationMode; + + private List mappingFiles; + private List managedClassNames; + private boolean excludeUnlistedClasses; + + public PersistenceUnitInfoImpl(String name) { + this.name = name; + } + + @Override + public String getPersistenceUnitName() { + return name; + } + + @Override + public Properties getProperties() { + return properties; + } + + @Override + public String getPersistenceProviderClassName() { + return HibernatePersistenceProvider.class.getName(); + } + + @Override + public PersistenceUnitTransactionType getTransactionType() { + return transactionType; + } + + public void setTransactionType(PersistenceUnitTransactionType transactionType) { + this.transactionType = transactionType; + } + + @Override + public SharedCacheMode getSharedCacheMode() { + return cacheMode; + } + + public void setCacheMode(SharedCacheMode cacheMode) { + this.cacheMode = cacheMode; + } + + @Override + public ValidationMode getValidationMode() { + return validationMode; + } + + public void setValidationMode(ValidationMode validationMode) { + this.validationMode = validationMode; + } + + @Override + public List getMappingFileNames() { + return mappingFiles == null ? Collections.emptyList() : mappingFiles; + } + + public void applyMappingFiles(String... mappingFiles) { + if ( this.mappingFiles == null ) { + this.mappingFiles = new ArrayList<>(); + } + Collections.addAll( this.mappingFiles, mappingFiles ); + } + + @Override + public List getManagedClassNames() { + return managedClassNames == null ? Collections.emptyList() : managedClassNames; + } + + public void applyManagedClassNames(String... managedClassNames) { + if ( this.managedClassNames == null ) { + this.managedClassNames = new ArrayList<>(); + } + Collections.addAll( this.managedClassNames, managedClassNames ); + } + + @Override + public boolean excludeUnlistedClasses() { + return excludeUnlistedClasses; + } + + public void setExcludeUnlistedClasses(boolean excludeUnlistedClasses) { + this.excludeUnlistedClasses = excludeUnlistedClasses; + } + + @Override + public String getPersistenceXMLSchemaVersion() { + return null; + } + + @Override + public DataSource getJtaDataSource() { + return null; + } + + @Override + public DataSource getNonJtaDataSource() { + return null; + } + + @Override + public List getJarFileUrls() { + return null; + } + + @Override + public URL getPersistenceUnitRootUrl() { + return null; + } + + @Override + public ClassLoader getClassLoader() { + return null; + } + + @Override + public void addTransformer(ClassTransformer transformer) { + + } + + @Override + public ClassLoader getNewTempClassLoader() { + return null; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/AbstractEntityManagerFactoryScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/AbstractEntityManagerFactoryScope.java new file mode 100644 index 000000000000..a61c35e05b20 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/AbstractEntityManagerFactoryScope.java @@ -0,0 +1,158 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.resource.jdbc.spi.StatementInspector; + +import org.hibernate.testing.orm.transaction.TransactionUtil; +import org.junit.jupiter.api.extension.ExtensionContext; + +import org.jboss.logging.Logger; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; + +abstract class AbstractEntityManagerFactoryScope implements EntityManagerFactoryScope, ExtensionContext.Store.CloseableResource { + private static final Logger log = Logger.getLogger( EntityManagerFactoryScope.class ); + + protected EntityManagerFactory emf; + protected boolean active = true; + + @Override + public EntityManagerFactory getEntityManagerFactory() { + if ( emf == null ) { + if ( !active ) { + throw new IllegalStateException( "EntityManagerFactoryScope is no longer active" ); + } + + log.debug( "Creating EntityManagerFactory" ); + emf = createEntityManagerFactory(); + } + + return emf; + } + + protected abstract EntityManagerFactory createEntityManagerFactory(); + + @Override + public StatementInspector getStatementInspector() { + return getEntityManagerFactory().unwrap( SessionFactoryImplementor.class ) + .getSessionFactoryOptions() + .getStatementInspector(); + } + + @Override + public T getStatementInspector(Class type) { + //noinspection unchecked + return (T) getStatementInspector(); + } + + @Override + public void close() { + if ( !active ) { + return; + } + + log.debug( "Closing SessionFactoryScope" ); + + active = false; + releaseEntityManagerFactory(); + } + + public void releaseEntityManagerFactory() { + if ( emf != null ) { + log.debug( "Releasing SessionFactory" ); + + try { + emf.close(); + } + catch (Exception e) { + log.warn( "Error closing EMF", e ); + } + finally { + emf = null; + } + } + } + + @Override + public void inEntityManager(Consumer action) { + log.trace( "#inEntityManager(Consumer)" ); + + try (SessionImplementor session = getEntityManagerFactory().createEntityManager() + .unwrap( SessionImplementor.class )) { + log.trace( "EntityManager opened, calling action" ); + action.accept( session ); + } + finally { + log.trace( "EntityManager close - auto-close block" ); + } + } + + @Override + public T fromEntityManager(Function action) { + log.trace( "#fromEntityManager(Function)" ); + + try (SessionImplementor session = getEntityManagerFactory().createEntityManager() + .unwrap( SessionImplementor.class )) { + log.trace( "EntityManager opened, calling action" ); + return action.apply( session ); + } + finally { + log.trace( "EntityManager close - auto-close block" ); + } + } + + @Override + public void inTransaction(Consumer action) { + log.trace( "#inTransaction(Consumer)" ); + + try (SessionImplementor session = getEntityManagerFactory().createEntityManager() + .unwrap( SessionImplementor.class )) { + log.trace( "EntityManager opened, calling action" ); + inTransaction( session, action ); + } + finally { + log.trace( "EntityManager close - auto-close block" ); + } + } + + @Override + public T fromTransaction(Function action) { + log.trace( "#fromTransaction(Function)" ); + + try (SessionImplementor session = getEntityManagerFactory().createEntityManager() + .unwrap( SessionImplementor.class )) { + log.trace( "EntityManager opened, calling action" ); + return fromTransaction( session, action ); + } + finally { + log.trace( "EntityManager close - auto-close block" ); + } + } + + @Override + public void inTransaction(EntityManager entityManager, Consumer action) { + log.trace( "inTransaction(EntityManager,Consumer)" ); + TransactionUtil.inTransaction( entityManager, action ); + } + + @Override + public T fromTransaction(EntityManager entityManager, Function action) { + log.trace( "fromTransaction(EntityManager,Function)" ); + + final SessionImplementor session = entityManager.unwrap( SessionImplementor.class ); + return TransactionUtil.fromTransaction( session, action ); + } + +} + diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseSessionFactoryFunctionalTest.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseSessionFactoryFunctionalTest.java new file mode 100644 index 000000000000..05d7b6c50a3e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseSessionFactoryFunctionalTest.java @@ -0,0 +1,376 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; +import java.util.Locale; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.hibernate.Session; +import org.hibernate.SessionBuilder; +import org.hibernate.Transaction; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataBuilder; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.mapping.Collection; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.RootClass; +import org.hibernate.mapping.SimpleValue; + +import org.hibernate.testing.jdbc.SharedDriverManagerConnectionProviderImpl; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.jupiter.api.AfterEach; + +import org.jboss.logging.Logger; + +/** + * Template (GoF pattern) based abstract class for tests bridging the legacy + * approach of SessionFactory building as a test fixture + * + * @author Steve Ebersole + */ +@SessionFactoryFunctionalTesting +public abstract class BaseSessionFactoryFunctionalTest + implements ServiceRegistryProducer, ServiceRegistryScopeAware, + DomainModelProducer, DomainModelScopeAware, + SessionFactoryProducer, SessionFactoryScopeAware { + + protected static final Dialect DIALECT = DialectContext.getDialect(); + + protected static final Class[] NO_CLASSES = new Class[0]; + protected static final String[] NO_MAPPINGS = new String[0]; + + private static final Logger log = Logger.getLogger( BaseSessionFactoryFunctionalTest.class ); + + private ServiceRegistryScope registryScope; + private DomainModelScope modelScope; + private SessionFactoryScope sessionFactoryScope; + + private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + + protected SessionFactoryScope sessionFactoryScope() { + return sessionFactoryScope; + } + + protected SessionFactoryImplementor sessionFactory() { + return sessionFactoryScope.getSessionFactory(); + } + + protected MetadataImplementor getMetadata(){ + return modelScope.getDomainModel(); + } + + @Override + public StandardServiceRegistry produceServiceRegistry(StandardServiceRegistryBuilder ssrBuilder) { + ssrBuilder.applySetting( AvailableSettings.HBM2DDL_AUTO, exportSchema() ? "create-drop" : "none" ); + if ( !Environment.getProperties().containsKey( Environment.CONNECTION_PROVIDER ) ) { + ssrBuilder.applySetting( + AvailableSettings.CONNECTION_PROVIDER, + SharedDriverManagerConnectionProviderImpl.getInstance() + ); + } + applySettings( ssrBuilder ); + return ssrBuilder.build(); + } + + @Override + public void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder bsrb) { + + } + + protected boolean exportSchema() { + return true; + } + + protected void applySettings(StandardServiceRegistryBuilder builder) { + } + + @Override + public void injectServiceRegistryScope(ServiceRegistryScope registryScope) { + this.registryScope = registryScope; + } + + @Override + public MetadataImplementor produceModel(StandardServiceRegistry serviceRegistry) { + MetadataSources metadataSources = new MetadataSources( serviceRegistry ); + MetadataBuilder metadataBuilder = metadataSources.getMetadataBuilder(); + applyMetadataBuilder( metadataBuilder ); + applyMetadataSources( metadataSources ); + final MetadataImplementor metadata = (MetadataImplementor) metadataBuilder.build(); + if ( !overrideCacheStrategy() || getCacheConcurrencyStrategy() == null ) { + return metadata; + } + + applyCacheSettings( metadata ); + + return metadata; + } + + protected final void applyCacheSettings(Metadata metadata) { + for ( PersistentClass entityBinding : metadata.getEntityBindings() ) { + if ( entityBinding.isInherited() ) { + continue; + } + + boolean hasLob = false; + + final Iterator props = entityBinding.getPropertyClosureIterator(); + while ( props.hasNext() ) { + final Property prop = (Property) props.next(); + if ( prop.getValue().isSimpleValue() ) { + if ( isLob( (SimpleValue) prop.getValue() ) ) { + hasLob = true; + break; + } + } + } + + if ( !hasLob ) { + ( (RootClass) entityBinding ).setCacheConcurrencyStrategy( getCacheConcurrencyStrategy() ); + entityBinding.setCached( true ); + } + } + + for ( Collection collectionBinding : metadata.getCollectionBindings() ) { + boolean isLob = false; + + if ( collectionBinding.getElement().isSimpleValue() ) { + isLob = isLob( (SimpleValue) collectionBinding.getElement() ); + } + + if ( !isLob ) { + collectionBinding.setCacheConcurrencyStrategy( getCacheConcurrencyStrategy() ); + } + } + } + + protected boolean overrideCacheStrategy() { + return true; + } + + protected String getCacheConcurrencyStrategy() { + return null; + } + + protected void applyMetadataBuilder(MetadataBuilder metadataBuilder) { + + } + + protected void applyMetadataSources(MetadataSources metadataSources) { + + for ( Class annotatedClass : getAnnotatedClasses() ) { + metadataSources.addAnnotatedClass( annotatedClass ); + } + String[] xmlFiles = getOrmXmlFiles(); + if ( xmlFiles != null ) { + for ( String xmlFile : xmlFiles ) { + try ( InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream( xmlFile ) ) { + metadataSources.addInputStream( is ); + } + catch (IOException e) { + throw new IllegalArgumentException( e ); + } + } + } + } + + protected Class[] getAnnotatedClasses() { + return NO_CLASSES; + } + + protected String[] getOrmXmlFiles() { + return NO_MAPPINGS; + } + + @Override + public void injectTestModelScope(DomainModelScope modelScope) { + this.modelScope = modelScope; + } + + @Override + public SessionFactoryImplementor produceSessionFactory(MetadataImplementor model) { + log.trace( "Producing SessionFactory" ); + final SessionFactoryBuilder sfBuilder = model.getSessionFactoryBuilder(); + configure( sfBuilder ); + final SessionFactoryImplementor factory = (SessionFactoryImplementor) sfBuilder.build(); + sessionFactoryBuilt( factory ); + return factory; + } + + protected void configure(SessionFactoryBuilder builder) { + } + + protected void sessionFactoryBuilt(SessionFactoryImplementor factory) { + } + + @Override + public void injectSessionFactoryScope(SessionFactoryScope scope) { + sessionFactoryScope = scope; + } + + // there is a chicken-egg problem here where the +// @AfterAll +// public void dropDatabase() { +// final SchemaManagementToolCoordinator.ActionGrouping actions = SchemaManagementToolCoordinator.ActionGrouping.interpret( +// registry.getService( ConfigurationService.class ).getSettings() +// ); +// +// final boolean needsDropped = this.model != null && ( exportSchema() || actions.getDatabaseAction() != Action.NONE ); +// +// if ( needsDropped ) { +// // atm we do not expose the (runtime) DatabaseModel from the SessionFactory so we +// // need to recreate it from the boot model. +// // +// // perhaps we should expose it from SF? +// final DatabaseModel databaseModel = Helper.buildDatabaseModel( registry, model ); +// new SchemaExport( databaseModel, registry ).drop( EnumSet.of( TargetType.DATABASE ) ); +// } +// } + + @AfterEach + public final void afterTest() { + if ( isCleanupTestDataRequired() ) { + cleanupTestData(); + } + } + + protected boolean isCleanupTestDataRequired() { + return false; + } + + protected void cleanupTestData() { + inTransaction( + session -> + getMetadata().getEntityBindings().forEach( + entityType -> session.createQuery( "delete from " + entityType.getEntityName() ).executeUpdate() + ) + + ); + } + + protected void inTransaction(Consumer action) { + sessionFactoryScope().inTransaction( action ); + } + + protected T fromTransaction(Function action) { + return sessionFactoryScope().fromTransaction( action ); + } + + protected void inSession(Consumer action){ + sessionFactoryScope.inSession( action ); + } + + protected T fromSession(Function action){ + return sessionFactoryScope.fromSession( action ); + } + + protected Dialect getDialect(){ + return DialectContext.getDialect(); + } + + private static boolean isLob(SimpleValue value) { + final String typeName = value.getTypeName(); + if ( typeName != null ) { + String significantTypeNamePart = typeName.substring( typeName.lastIndexOf( '.' ) + 1 ) + .toLowerCase( Locale.ROOT ); + switch ( significantTypeNamePart ) { + case "blob": + case "blobtype": + case "clob": + case "clobtype": + case "nclob": + case "nclobtype": + return true; + } + } + return false; + } + + protected Future executeAsync(Runnable callable) { + return executorService.submit(callable); + } + + protected void executeSync(Runnable callable) { + try { + executeAsync( callable ).get(); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + catch (ExecutionException e) { + throw new RuntimeException( e.getCause() ); + } + } + + /** + * Execute function in a Hibernate transaction without return value + * + * @param sessionBuilderSupplier SessionFactory supplier + * @param function function + */ + public static void doInHibernateSessionBuilder( + Supplier sessionBuilderSupplier, + TransactionUtil.HibernateTransactionConsumer function) { + Session session = null; + Transaction txn = null; + try { + session = sessionBuilderSupplier.get().openSession(); + function.beforeTransactionCompletion(); + txn = session.beginTransaction(); + + function.accept( session ); + if ( !txn.getRollbackOnly() ) { + txn.commit(); + } + else { + try { + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + } + catch ( Throwable t ) { + if ( txn != null && txn.isActive() ) { + try { + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + throw t; + } + finally { + function.afterTransactionCompletion(); + if ( session != null ) { + session.close(); + } + } + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseUnitTest.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseUnitTest.java new file mode 100644 index 000000000000..394b5c914365 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseUnitTest.java @@ -0,0 +1,28 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +@Inherited +@Target( ElementType.TYPE ) +@Retention( RetentionPolicy.RUNTIME ) +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@ExtendWith( FailureExpectedExtension.class ) +@ExtendWith( ExpectedExceptionExtension.class ) +@ExtendWith( DialectFilterExtension.class ) +public @interface BaseUnitTest { + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BootstrapServiceRegistry.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BootstrapServiceRegistry.java new file mode 100644 index 000000000000..1b04d38649a6 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BootstrapServiceRegistry.java @@ -0,0 +1,42 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.integrator.spi.Integrator; + +/** + * Used to define the bootstrap ServiceRegistry to be used for testing. + */ +@Inherited +@Target( ElementType.TYPE ) +@Retention( RetentionPolicy.RUNTIME ) + +@ServiceRegistryFunctionalTesting +public @interface BootstrapServiceRegistry { + + Class[] integrators() default {}; + + JavaService[] javaServices() default {}; + + @interface JavaService { + /** + * Logically `?` is `T` + */ + Class role(); + /** + * Logically `?` is `S extends T` + */ + Class impl(); + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BootstrapServiceRegistryProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BootstrapServiceRegistryProducer.java new file mode 100644 index 000000000000..8556d81ddc87 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BootstrapServiceRegistryProducer.java @@ -0,0 +1,17 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.boot.registry.BootstrapServiceRegistry; +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; + +/** + * Producer of BootstrapServiceRegistry + */ +public interface BootstrapServiceRegistryProducer { + BootstrapServiceRegistry produceServiceRegistry(BootstrapServiceRegistryBuilder builder); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ClassLoadingIsolaterExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ClassLoadingIsolaterExtension.java new file mode 100644 index 000000000000..e4caab47c772 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ClassLoadingIsolaterExtension.java @@ -0,0 +1,56 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +import org.jboss.logging.Logger; + +public class ClassLoadingIsolaterExtension implements AfterEachCallback, BeforeEachCallback { + + private static final Logger log = Logger.getLogger( ClassLoadingIsolaterExtension.class ); + + private IsolatedClassLoaderProvider provider; + + public interface IsolatedClassLoaderProvider { + ClassLoader buildIsolatedClassLoader(); + + void releaseIsolatedClassLoader(ClassLoader isolatedClassLoader); + } + + + private ClassLoader isolatedClassLoader; + private ClassLoader originalTCCL; + + @Override + public void afterEach(ExtensionContext context) throws Exception { + assert Thread.currentThread().getContextClassLoader() == isolatedClassLoader; + log.infof( "Reverting TCCL [%s] -> [%s]", isolatedClassLoader, originalTCCL ); + + Thread.currentThread().setContextClassLoader( originalTCCL ); + provider.releaseIsolatedClassLoader( isolatedClassLoader ); + } + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + Object testInstance = context.getTestInstance().get(); + if ( !( testInstance instanceof IsolatedClassLoaderProvider ) ) { + throw new RuntimeException( + "Test @ExtendWith( ClassLoadingIsolaterExtension.class ) have to implement ClassLoadingIsolaterExtension.IsolatedClassLoaderProvider" ); + } + provider = (IsolatedClassLoaderProvider) testInstance; + originalTCCL = Thread.currentThread().getContextClassLoader(); + isolatedClassLoader = provider.buildIsolatedClassLoader(); + + log.infof( "Overriding TCCL [%s] -> [%s]", originalTCCL, isolatedClassLoader ); + + Thread.currentThread().setContextClassLoader( isolatedClassLoader ); + + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectContext.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectContext.java new file mode 100644 index 000000000000..997550274cbd --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectContext.java @@ -0,0 +1,62 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.reflect.Constructor; +import java.sql.Connection; +import java.sql.Driver; +import java.util.Properties; + +import org.hibernate.HibernateException; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.dialect.spi.DatabaseMetaDataDialectResolutionInfoAdapter; +import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.internal.util.ReflectHelper; + +/** + * @author Christian Beikov + */ +public final class DialectContext { + + private static Dialect dialect; + + static void init() { + final Properties properties = Environment.getProperties(); + final String dialectName = properties.getProperty( Environment.DIALECT ); + if ( dialectName == null ) { + throw new HibernateException( "The dialect was not set. Set the property hibernate.dialect." ); + } + try { + final Class dialectClass = ReflectHelper.classForName( dialectName ); + final Constructor constructor = dialectClass.getConstructor( DialectResolutionInfo.class ); + Driver driver = (Driver) Class.forName( properties.getProperty( Environment.DRIVER ) ).newInstance(); + Properties props = new Properties(); + props.setProperty( "user", properties.getProperty( Environment.USER ) ); + props.setProperty( "password", properties.getProperty( Environment.PASS ) ); + try (Connection connection = driver.connect( properties.getProperty( Environment.URL ), props )) { + dialect = constructor.newInstance( new DatabaseMetaDataDialectResolutionInfoAdapter( connection.getMetaData() ) ); + } + } + catch (ClassNotFoundException cnfe) { + throw new HibernateException( "Dialect class not found: " + dialectName, cnfe ); + } + catch (Exception e) { + throw new HibernateException( "Could not instantiate given dialect class: " + dialectName, e ); + } + } + + private DialectContext() { + } + + public static synchronized Dialect getDialect() { + if (dialect==null) { + init(); + } + return dialect; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureCheck.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureCheck.java new file mode 100644 index 000000000000..20e961e6c33e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureCheck.java @@ -0,0 +1,18 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.dialect.Dialect; + + +/** + * @author Andrea Boriero + */ +@FunctionalInterface +public interface DialectFeatureCheck { + boolean apply(Dialect dialect); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java new file mode 100644 index 000000000000..514b6315777e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java @@ -0,0 +1,320 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.dialect.AbstractHANADialect; +import org.hibernate.dialect.CockroachDB192Dialect; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.DerbyDialect; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.HSQLDialect; +import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.dialect.PostgreSQL95Dialect; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.dialect.SQLServerDialect; + +/** + * Container class for different implementation of the {@link DialectFeatureCheck} interface. + * + * @author Hardy Ferentschik + * @author Steve Ebersole + */ +abstract public class DialectFeatureChecks { + public static class SupportsSequences implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsSequences(); + } + } + + public static class SupportsExpectedLobUsagePattern implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsExpectedLobUsagePattern(); + } + } + + /** + * Does the database support nationalized data in any form + */ + public static class SupportsNationalizedData implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsNationalizedTypes(); + } + } + + public static class UsesInputStreamToInsertBlob implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.useInputStreamToInsertBlob(); + } + } + + public static class SupportsIdentityColumns implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.getIdentityColumnSupport().supportsIdentityColumns(); + } + } + + public static class SupportsColumnCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsColumnCheck(); + } + } + + public static class SupportsNoColumnInsert implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsNoColumnsInsert(); + } + } + + public static class SupportsResultSetPositioningOnForwardOnlyCursorCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsResultSetPositionQueryMethodsOnForwardOnlyCursor(); + } + } + + public static class SupportsCascadeDeleteCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsCascadeDelete(); + } + } + + public static class SupportsCircularCascadeDeleteCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsCircularCascadeDeleteConstraints(); + } + } + + public static class SupportsUnboundedLobLocatorMaterializationCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsExpectedLobUsagePattern() && dialect.supportsUnboundedLobLocatorMaterialization(); + } + } + + public static class SupportsSubqueryAsLeftHandSideInPredicate implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsSubselectAsInPredicateLHS(); + } + } + + public static class SupportLimitCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.getLimitHandler().supportsLimit(); + } + } + + public static class SupportLimitAndOffsetCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.getLimitHandler().supportsLimit() && dialect.getLimitHandler().supportsLimitOffset(); + } + } + + public static class SupportsParametersInInsertSelectCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsParametersInInsertSelect(); + } + } + + public static class HasSelfReferentialForeignKeyBugCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.hasSelfReferentialForeignKeyBug(); + } + } + + public static class SupportsRowValueConstructorSyntaxCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect instanceof AbstractHANADialect + || dialect instanceof CockroachDB192Dialect + || dialect instanceof MySQLDialect + || dialect instanceof PostgreSQLDialect; + } + } + + public static class SupportsJdbcDriverProxying implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return !( dialect instanceof DB2Dialect ) && !( dialect instanceof DerbyDialect ); + } + } + + public static class DoesReadCommittedCauseWritersToBlockReadersCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.doesReadCommittedCauseWritersToBlockReaders(); + } + } + + public static class DoesReadCommittedNotCauseWritersToBlockReadersCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return ! dialect.doesReadCommittedCauseWritersToBlockReaders(); + } + } + + public static class DoesRepeatableReadCauseReadersToBlockWritersCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.doesRepeatableReadCauseReadersToBlockWriters(); + } + } + + public static class SupportsExistsInSelectCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsExistsInSelect(); + } + } + + public static class SupportsLobValueChangePropogation implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsLobValueChangePropogation(); + } + } + + public static class SupportsLockTimeouts implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsLockTimeouts(); + } + } + + public static class SupportsSkipLocked implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsSkipLocked(); + } + } + + public static class DoubleQuoteQuoting implements DialectFeatureCheck { + @Override + public boolean apply(Dialect dialect) { + return '\"' == dialect.openQuote() && '\"' == dialect.closeQuote(); + } + } + + public static class SupportSchemaCreation implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.canCreateSchema(); + } + } + + public static class SupportCatalogCreation implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.canCreateCatalog(); + } + } + + public static class DoesNotSupportFollowOnLocking implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return !dialect.useFollowOnLocking(); + } + } + + public static class SupportPartitionBy implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsPartitionBy(); + } + } + + public static class SupportDropConstraints implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.dropConstraints(); + } + } + + public static class SupportsPadWithChar implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return !( dialect instanceof DerbyDialect ); + } + } + + public static class SupportsGroupByRollup implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect instanceof DB2Dialect + || dialect instanceof OracleDialect + || dialect instanceof PostgreSQL95Dialect + || dialect instanceof SQLServerDialect + || dialect instanceof MySQLDialect; + } + } + + public static class SupportsGroupByGroupingSets implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect instanceof DB2Dialect + || dialect instanceof OracleDialect + || dialect instanceof PostgreSQL95Dialect + || dialect instanceof SQLServerDialect; + } + } + + public static class SupportsUnion implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsUnionAll(); + } + } + + public static class SupportsCharCodeConversion implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + // Derby doesn't support the `ASCII` or `CHR` functions + return !( dialect instanceof DerbyDialect ); + } + } + + public static class SupportsReplace implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + // Derby doesn't support the `REPLACE` function + return !( dialect instanceof DerbyDialect ); + } + } + + public static class SupportNoWait implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsNoWait(); + } + } + + public static class DoesRepeatableReadNotCauseReadersToBlockWritersCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return ! dialect.doesRepeatableReadCauseReadersToBlockWriters(); + } + } + + public static class ForceLobAsLastValue implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.forceLobAsLastValue(); + } + } + + public static class SupportsStringAggregation implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect instanceof H2Dialect + || dialect instanceof HSQLDialect + || dialect instanceof MySQLDialect + || dialect instanceof PostgreSQLDialect + || dialect instanceof AbstractHANADialect + || dialect instanceof CockroachDB192Dialect + || dialect instanceof DB2Dialect + || dialect instanceof OracleDialect + || dialect instanceof SQLServerDialect; + } + } + + public static class SupportsInverseDistributionFunctions implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect instanceof AbstractHANADialect + || dialect instanceof CockroachDB192Dialect + || dialect instanceof DB2Dialect + || dialect instanceof OracleDialect + || dialect instanceof SQLServerDialect; + } + } + + public static class SupportsHypotheticalSetFunctions implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect instanceof PostgreSQLDialect + || dialect instanceof AbstractHANADialect + || dialect instanceof CockroachDB192Dialect + || dialect instanceof DB2Dialect + || dialect instanceof OracleDialect + || dialect instanceof SQLServerDialect; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFilterExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFilterExtension.java new file mode 100644 index 000000000000..d2a3bfada4dd --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFilterExtension.java @@ -0,0 +1,122 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.util.List; +import java.util.Locale; + +import org.hibernate.dialect.Dialect; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; + +import org.jboss.logging.Logger; + +/** + * JUnit 5 extension used to add {@link RequiresDialect} and {@link SkipForDialect} + * handling + * + * @author Steve Ebersole + */ +public class DialectFilterExtension implements ExecutionCondition { + private static final Logger log = Logger.getLogger( DialectFilterExtension.class ); + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + final Dialect dialect = getDialect( context ); + if ( dialect == null ) { + throw new RuntimeException( "#getDialect returned null" ); + } + + log.debugf( "Checking Dialect [%s] - context = %s", dialect, context.getDisplayName() ); + + final List effectiveRequiresDialects = TestingUtil.findEffectiveRepeatingAnnotation( + context, + RequiresDialect.class, + RequiresDialects.class + ); + + if ( !effectiveRequiresDialects.isEmpty() ) { + StringBuilder requiredDialects = new StringBuilder( ); + + for ( RequiresDialect requiresDialect : effectiveRequiresDialects ) { + requiredDialects.append( requiresDialect.value() ); + requiredDialects.append( " " ); + + if ( ! requiresDialect.value().isInstance( dialect ) ) { + continue; + } + + if ( requiresDialect.matchSubTypes() || requiresDialect.value().equals( dialect.getClass() ) ) { + return evaluateSkipConditions( context, dialect, "Matched @RequiresDialect" ); + } + } + + return ConditionEvaluationResult.disabled( + String.format( + Locale.ROOT, + "Failed @RequiresDialect(dialect=%s) check - found %s]", + requiredDialects, + dialect.getClass().getName() + ) + ); + } + + return evaluateSkipConditions( context, dialect, "Passed all @SkipForDialects" ); + } + + private ConditionEvaluationResult evaluateSkipConditions(ExtensionContext context, Dialect dialect, String enabledResult) { + final List effectiveSkips = TestingUtil.findEffectiveRepeatingAnnotation( + context, + SkipForDialect.class, + SkipForDialectGroup.class + ); + + for ( SkipForDialect effectiveSkipForDialect : effectiveSkips ) { + if ( effectiveSkipForDialect.matchSubTypes() ) { + if ( effectiveSkipForDialect.dialectClass().isInstance( dialect ) ) { + return ConditionEvaluationResult.disabled( "Matched @SkipForDialect" ); + } + } + else { + if ( effectiveSkipForDialect.dialectClass().equals( dialect.getClass() ) ) { + return ConditionEvaluationResult.disabled( "Matched @SkipForDialect" ); + } + } + } + + List effectiveRequiresDialectFeatures = TestingUtil.findEffectiveRepeatingAnnotation( + context, + RequiresDialectFeature.class, + RequiresDialectFeatureGroup.class + ); + + for ( RequiresDialectFeature effectiveRequiresDialectFeature : effectiveRequiresDialectFeatures ) { + try { + final DialectFeatureCheck dialectFeatureCheck = effectiveRequiresDialectFeature.feature() + .newInstance(); + if ( !dialectFeatureCheck.apply( dialect ) ) { + return ConditionEvaluationResult.disabled( + String.format( + Locale.ROOT, + "Failed @RequiresDialectFeature [%s]", + effectiveRequiresDialectFeature.feature() + ) ); + } + } + catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException( "Unable to instantiate DialectFeatureCheck class", e ); + } + } + return ConditionEvaluationResult.enabled( enabledResult ); + } + + private Dialect getDialect(ExtensionContext context) { + return DialectContext.getDialect(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModel.java new file mode 100644 index 000000000000..558c176bbcae --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModel.java @@ -0,0 +1,111 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.boot.model.TypeContributor; +import org.hibernate.cache.spi.access.AccessType; + +import org.hibernate.testing.orm.domain.DomainModelDescriptor; +import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +import javax.persistence.SharedCacheMode; + +/** + * @asciidoc + * + * Used to define the test model ({@link org.hibernate.boot.spi.MetadataImplementor}) + * to be used for testing. + * + * Can be used by itself, along with {@link DomainModelScopeAware}, to test the MetadataImplementor. E.g. + * + * [source, JAVA, indent=0] + * ---- + * @TestDomain ( ... ) + * class MyTest implements TestDomainAware { + * + * @Test + * public void doTheTest() { + * // use the injected MetadataImplementor + * } + * + * private MetadataImplementor model; + * + * @Override + * public void injectTestModelScope(MetadataImplementor model) { + * this.model = model; + * } + * } + * ---- + * + * + * Can optionally be used with {@link ServiceRegistry} to define the ServiceRegistry used to + * build the MetadataImplementor (passed to + * {@link org.hibernate.boot.MetadataSources#MetadataSources(org.hibernate.service.ServiceRegistry)}). + * + * [source, JAVA, indent=0] + * ---- + * @ServiceRegistry ( ... ) + * @TestDomain ( ... ) + * class MyTest implements TestDomainAware { + * + * @Test + * public void doTheTest() { + * // use the injected MetadataImplementor + * } + * + * private MetadataImplementor model; + * + * @Override + * public void injectTestModelScope(MetadataImplementor model) { + * this.model = model; + * } + * } + * ---- + * + * It can also be used in conjunction with {@link SessionFactory} + * + * @see DomainModelScopeAware + * + * @author Steve Ebersole + */ +@Inherited +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@ExtendWith( FailureExpectedExtension.class ) +@ExtendWith( ServiceRegistryExtension.class ) +@ExtendWith( ServiceRegistryParameterResolver.class ) + +@ExtendWith( DomainModelExtension.class ) +@ExtendWith( DomainModelParameterResolver.class ) +public @interface DomainModel { + StandardDomainModel[] standardModels() default {}; + Class[] modelDescriptorClasses() default {}; + Class[] annotatedClasses() default {}; + String[] annotatedClassNames() default {}; + String[] annotatedPackageNames() default {}; + String[] xmlMappings() default {}; + + SharedCacheMode sharedCacheMode() default SharedCacheMode.ENABLE_SELECTIVE; + + boolean overrideCacheStrategy() default true; + String concurrencyStrategy() default ""; + + AccessType accessType() default AccessType.READ_WRITE; + + Class[] typeContributors() default {}; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelExtension.java new file mode 100644 index 000000000000..4bb40bb961df --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelExtension.java @@ -0,0 +1,295 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.util.Iterator; +import java.util.Locale; +import java.util.Optional; + +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.internal.MetadataBuilderImpl; +import org.hibernate.boot.model.TypeContributor; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.mapping.Collection; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.RootClass; +import org.hibernate.mapping.SimpleValue; +import org.hibernate.resource.beans.spi.ManagedBeanRegistry; + +import org.hibernate.testing.orm.domain.DomainModelDescriptor; +import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.platform.commons.support.AnnotationSupport; + +/** + * hibernate-testing implementation of a few JUnit5 contracts to support SessionFactory-based testing, + * including argument injection (or see {@link DomainModelScopeAware}) + * + * @see ServiceRegistryScope + * @see DomainModelExtension + * + * @author Steve Ebersole + */ +public class DomainModelExtension + implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler { + + private static final String MODEL_KEY = MetadataImplementor.class.getName(); + + private static ExtensionContext.Store locateExtensionStore(Object testInstance, ExtensionContext context) { + return JUnitHelper.locateExtensionStore( ServiceRegistryExtension.class, context, testInstance ); + } + + public static DomainModelScope findDomainModelScope(Object testInstance, ExtensionContext context) { + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final DomainModelScope existing = (DomainModelScope) store.get( MODEL_KEY ); + if ( existing != null ) { + return existing; + } + + + final ServiceRegistryScope serviceRegistryScope = ServiceRegistryExtension.findServiceRegistryScope( + testInstance, + context + ); + + final DomainModelProducer modelProducer; + + if ( testInstance instanceof DomainModelProducer ) { + modelProducer = (DomainModelProducer) testInstance; + } + else { + modelProducer = serviceRegistry -> { + if ( !context.getElement().isPresent() ) { + throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() ); + } + + final Optional domainModelAnnotationWrapper = AnnotationSupport.findAnnotation( + context.getElement().get(), + DomainModel.class + ); + + if ( !domainModelAnnotationWrapper.isPresent() ) { + throw new RuntimeException( "Could not locate @DomainModel annotation : " + context.getDisplayName() ); + } + + final DomainModel domainModelAnnotation = domainModelAnnotationWrapper.get(); + + final MetadataSources metadataSources = new MetadataSources( serviceRegistry ); + final ManagedBeanRegistry managedBeanRegistry = serviceRegistry.getService( ManagedBeanRegistry.class ); + + for ( String annotatedPackageName : domainModelAnnotation.annotatedPackageNames() ) { + metadataSources.addPackage( annotatedPackageName ); + } + + for ( StandardDomainModel standardDomainModel : domainModelAnnotation.standardModels() ) { + standardDomainModel.getDescriptor().applyDomainModel( metadataSources ); + } + + for ( Class modelDescriptorClass : domainModelAnnotation.modelDescriptorClasses() ) { + try { + final DomainModelDescriptor modelDescriptor = modelDescriptorClass.newInstance(); + modelDescriptor.applyDomainModel( metadataSources ); + } + catch (IllegalAccessException | InstantiationException e) { + throw new RuntimeException( "Error instantiating DomainModelDescriptor - " + modelDescriptorClass.getName(), e ); + } + } + + for ( Class annotatedClass : domainModelAnnotation.annotatedClasses() ) { + metadataSources.addAnnotatedClass( annotatedClass ); + } + + for ( String annotatedClassName : domainModelAnnotation.annotatedClassNames() ) { + metadataSources.addAnnotatedClassName( annotatedClassName ); + } + + for ( String xmlMapping : domainModelAnnotation.xmlMappings() ) { + metadataSources.addResource( xmlMapping ); + } + + final MetadataBuilderImpl metadataBuilder = (MetadataBuilderImpl) metadataSources.getMetadataBuilder(); + + for ( Class contributorType : domainModelAnnotation.typeContributors() ) { + final TypeContributor contributor = managedBeanRegistry.getBean( contributorType ).getBeanInstance(); + contributor.contribute( metadataBuilder, serviceRegistry ); + } + + MetadataImplementor metadataImplementor = metadataBuilder.build(); + applyCacheSettings( + metadataImplementor, + domainModelAnnotation.overrideCacheStrategy(), + domainModelAnnotation.concurrencyStrategy() + ); + + return metadataImplementor; + }; + } + + final DomainModelScopeImpl scope = new DomainModelScopeImpl( serviceRegistryScope, modelProducer ); + + if ( testInstance instanceof DomainModelScopeAware ) { + ( (DomainModelScopeAware) testInstance ).injectTestModelScope( scope ); + } + + locateExtensionStore( testInstance, context ).put( MODEL_KEY, scope ); + + return scope; + } + + protected static final void applyCacheSettings(Metadata metadata, boolean overrideCacheStrategy, String cacheConcurrencyStrategy) { + if ( !overrideCacheStrategy ) { + return; + } + + if ( cacheConcurrencyStrategy.equals( "" ) ) { + return; + } + + for ( PersistentClass entityBinding : metadata.getEntityBindings() ) { + if ( entityBinding.isInherited() ) { + continue; + } + + boolean hasLob = false; + + final Iterator props = entityBinding.getPropertyClosureIterator(); + while ( props.hasNext() ) { + final Property prop = (Property) props.next(); + if ( prop.getValue().isSimpleValue() ) { + if ( isLob( (SimpleValue) prop.getValue() ) ) { + hasLob = true; + break; + } + } + } + + if ( !hasLob ) { + ( (RootClass) entityBinding ).setCacheConcurrencyStrategy( cacheConcurrencyStrategy ); + entityBinding.setCached( true ); + } + } + + for ( Collection collectionBinding : metadata.getCollectionBindings() ) { + boolean isLob = false; + + if ( collectionBinding.getElement().isSimpleValue() ) { + isLob = isLob( (SimpleValue) collectionBinding.getElement() ); + } + + if ( !isLob ) { + collectionBinding.setCacheConcurrencyStrategy( cacheConcurrencyStrategy ); + } + } + } + + private static boolean isLob(SimpleValue value) { + final String typeName = value.getTypeName(); + if ( typeName != null ) { + String significantTypeNamePart = typeName.substring( typeName.lastIndexOf( '.' ) + 1 ) + .toLowerCase( Locale.ROOT ); + switch ( significantTypeNamePart ) { + case "blob": + case "blobtype": + case "clob": + case "clobtype": + case "nclob": + case "nclobtype": + return true; + } + } + return false; + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + findDomainModelScope( testInstance, context ); + } + + @Override + public void afterAll(ExtensionContext context) { + final ExtensionContext.Store store = locateExtensionStore( context.getRequiredTestInstance(), context ); + final DomainModelScopeImpl scope = (DomainModelScopeImpl) store.remove( MODEL_KEY ); + + if ( scope != null ) { + scope.close(); + } + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + final ExtensionContext.Store store = locateExtensionStore( context.getRequiredTestInstance(), context ); + final DomainModelScopeImpl scope = (DomainModelScopeImpl) store.get( MODEL_KEY ); + + if ( scope != null ) { + scope.releaseModel(); + } + + throw throwable; + } + + public static class DomainModelScopeImpl implements DomainModelScope, ExtensionContext.Store.CloseableResource { + private final ServiceRegistryScope serviceRegistryScope; + private final DomainModelProducer producer; + + private MetadataImplementor model; + private boolean active = true; + + public DomainModelScopeImpl( + ServiceRegistryScope serviceRegistryScope, + DomainModelProducer producer) { + this.serviceRegistryScope = serviceRegistryScope; + this.producer = producer; + + this.model = createDomainModel(); + } + + private MetadataImplementor createDomainModel() { + verifyActive(); + + final StandardServiceRegistry registry = serviceRegistryScope.getRegistry(); + model = producer.produceModel( registry ); + return model; + } + + @Override + public MetadataImplementor getDomainModel() { + verifyActive(); + + if ( model == null ) { + model = createDomainModel(); + } + return model; + } + + private void verifyActive() { + if ( !active ) { + throw new RuntimeException( "DomainModelScope no longer active" ); + } + } + + + @Override + public void close() { + active = false; + releaseModel(); + } + + public void releaseModel() { + model = null; + } + } + + protected void afterMetadataBuilt(Metadata metadata) { + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelFunctionalTesting.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelFunctionalTesting.java new file mode 100644 index 000000000000..9a847b462e96 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelFunctionalTesting.java @@ -0,0 +1,38 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +//@ExtendWith( ServiceRegistryExtension.class ) +//@ExtendWith( ServiceRegistryParameterResolver.class ) + +@ServiceRegistryFunctionalTesting + +@ExtendWith( ExpectedExceptionExtension.class ) +@ExtendWith( DialectFilterExtension.class ) + +@ExtendWith( DomainModelExtension.class ) +@ExtendWith( DomainModelParameterResolver.class ) +public @interface DomainModelFunctionalTesting { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelParameterResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelParameterResolver.java new file mode 100644 index 000000000000..6393ec0ad98b --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelParameterResolver.java @@ -0,0 +1,48 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.boot.spi.MetadataImplementor; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @author Steve Ebersole + */ +public class DomainModelParameterResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return JUnitHelper.supportsParameterInjection( parameterContext, MetadataImplementor.class, DomainModelScope.class ); + } + + @Override + public Object resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + final DomainModelScope modelScope = DomainModelExtension.findDomainModelScope( + extensionContext.getRequiredTestInstance(), + extensionContext + ); + + final Class parameterType = parameterContext.getParameter().getType(); + + if ( parameterType.isAssignableFrom( DomainModelScope.class ) ) { + return modelScope; + } + + if ( parameterType.isAssignableFrom( MetadataImplementor.class ) ) { + return modelScope.getDomainModel(); + } + + throw new IllegalStateException( "Unsupported parameter type : " + parameterType.getName() ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelProducer.java new file mode 100644 index 000000000000..c85fcdf2bba5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelProducer.java @@ -0,0 +1,17 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.spi.MetadataImplementor; + +/** + * @author Steve Ebersole + */ +public interface DomainModelProducer { + MetadataImplementor produceModel(StandardServiceRegistry serviceRegistry); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScope.java new file mode 100644 index 000000000000..34211eddf16e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScope.java @@ -0,0 +1,55 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.util.Locale; +import java.util.function.Consumer; + +import org.hibernate.UnknownEntityTypeException; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.RootClass; + +/** + * @author Steve Ebersole + */ +public interface DomainModelScope { + MetadataImplementor getDomainModel(); + + default void visitHierarchies(Consumer action) { + getDomainModel().getEntityBindings().forEach( + persistentClass -> { + if ( persistentClass instanceof RootClass ) { + action.accept( (RootClass) persistentClass ); + } + } + ); + } + + default void withHierarchy(Class rootType, Consumer action) { + withHierarchy( rootType.getName(), action ); + } + + default void withHierarchy(String rootTypeName, Consumer action) { + final PersistentClass entityBinding = getDomainModel().getEntityBinding( rootTypeName ); + + if ( entityBinding == null ) { + throw new UnknownEntityTypeException( + String.format( + Locale.ROOT, + "Could not resolve `%s` as an entity type", + rootTypeName + ) + ); + } + + action.accept( entityBinding.getRootClass() ); + } + + + // ... +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScopeAware.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScopeAware.java new file mode 100644 index 000000000000..393c6fea06cf --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScopeAware.java @@ -0,0 +1,14 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +/** + * @author Steve Ebersole + */ +public interface DomainModelScopeAware { + void injectTestModelScope(DomainModelScope modelScope); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryExtension.java new file mode 100644 index 000000000000..2e2a01541c29 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryExtension.java @@ -0,0 +1,287 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.hibernate.SessionFactoryObserver; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; +import org.hibernate.tool.schema.Action; +import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator; +import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.ActionGrouping; + +import org.hibernate.testing.jdbc.SharedDriverManagerConnectionProviderImpl; +import org.hibernate.testing.orm.domain.DomainModelDescriptor; +import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.hibernate.testing.orm.jpa.PersistenceUnitInfoImpl; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.platform.commons.support.AnnotationSupport; + +import org.jboss.logging.Logger; + +import javax.persistence.spi.PersistenceUnitInfo; + +/** + * hibernate-testing implementation of a few JUnit5 contracts to support SessionFactory-based testing, + * including argument injection (or see {@link SessionFactoryScopeAware}) + * + * @author Steve Ebersole + * + * @see DomainModelExtension + * @see SessionFactoryExtension + */ +public class EntityManagerFactoryExtension + implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler { + + private static final Logger log = Logger.getLogger( EntityManagerFactoryExtension.class ); + private static final String EMF_KEY = EntityManagerFactoryScope.class.getName(); + + private static ExtensionContext.Store locateExtensionStore(Object testInstance, ExtensionContext context) { + return JUnitHelper.locateExtensionStore( EntityManagerFactoryExtension.class, context, testInstance ); + } + + public static EntityManagerFactoryScope findEntityManagerFactoryScope( + Object testInstance, + ExtensionContext context) { + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final EntityManagerFactoryScope existing = (EntityManagerFactoryScope) store.get( EMF_KEY ); + if ( existing != null ) { + return existing; + } + + if ( !context.getElement().isPresent() ) { + throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() ); + } + + final Optional emfAnnWrapper = AnnotationSupport.findAnnotation( + context.getElement().get(), + Jpa.class + ); + final Jpa emfAnn = emfAnnWrapper.orElseThrow( () -> new RuntimeException( "Could not locate @EntityManagerFactory" ) ); + + final PersistenceUnitInfoImpl pui = new PersistenceUnitInfoImpl( emfAnn.persistenceUnitName() ); + + pui.setTransactionType( emfAnn.transactionType() ); + pui.setCacheMode( emfAnn.sharedCacheMode() ); + pui.setValidationMode( emfAnn.validationMode() ); + pui.setExcludeUnlistedClasses( emfAnn.excludeUnlistedClasses() ); + + // JpaCompliance + pui.getProperties().put( AvailableSettings.JPA_QUERY_COMPLIANCE, emfAnn.queryComplianceEnabled() ); + pui.getProperties().put( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, emfAnn.transactionComplianceEnabled() ); + pui.getProperties().put( AvailableSettings.JPA_LIST_COMPLIANCE, emfAnn.listMappingComplianceEnabled() ); + pui.getProperties().put( AvailableSettings.JPA_CLOSED_COMPLIANCE, emfAnn.closedComplianceEnabled() ); + pui.getProperties().put( AvailableSettings.JPA_PROXY_COMPLIANCE, emfAnn.proxyComplianceEnabled() ); + pui.getProperties().put( AvailableSettings.JPA_CACHING_COMPLIANCE, emfAnn.cacheComplianceEnabled() ); + pui.getProperties().put( AvailableSettings.JPA_ID_GENERATOR_GLOBAL_SCOPE_COMPLIANCE, emfAnn.generatorScopeComplianceEnabled() ); + + final Setting[] properties = emfAnn.properties(); + for ( int i = 0; i < properties.length; i++ ) { + final Setting property = properties[i]; + pui.getProperties().setProperty( property.name(), property.value() ); + } + + pui.getProperties().setProperty( + AvailableSettings.GENERATE_STATISTICS, + Boolean.toString( emfAnn.generateStatistics() ) + ); + + if ( emfAnn.exportSchema() ) { + pui.getProperties().setProperty( + AvailableSettings.HBM2DDL_DATABASE_ACTION, + Action.CREATE_DROP.getExternalHbm2ddlName() + ); + } + + if ( emfAnn.annotatedPackageNames().length > 0 ) { + pui.applyManagedClassNames( emfAnn.annotatedPackageNames() ); + } + + if ( emfAnn.annotatedClassNames().length > 0 ) { + pui.applyManagedClassNames( emfAnn.annotatedClassNames() ); + } + + if ( emfAnn.annotatedClasses().length > 0 ) { + for ( int i = 0; i < emfAnn.annotatedClasses().length; i++ ) { + pui.applyManagedClassNames( emfAnn.annotatedClasses()[i].getName() ); + } + } + + if ( emfAnn.xmlMappings().length > 0 ) { + pui.applyMappingFiles( emfAnn.xmlMappings() ); + } + + if ( emfAnn.standardModels().length > 0 ) { + for ( int i = 0; i < emfAnn.standardModels().length; i++ ) { + final StandardDomainModel standardDomainModel = emfAnn.standardModels()[i]; + for ( int i1 = 0; i1 < standardDomainModel.getDescriptor().getAnnotatedClasses().length; i1++ ) { + final Class annotatedClass = standardDomainModel.getDescriptor().getAnnotatedClasses()[i1]; + pui.applyManagedClassNames( annotatedClass.getName() ); + } + } + } + + if ( emfAnn.modelDescriptorClasses().length > 0 ) { + for ( int i = 0; i < emfAnn.modelDescriptorClasses().length; i++ ) { + final Class modelDescriptorClass = emfAnn.modelDescriptorClasses()[i]; + final DomainModelDescriptor domainModelDescriptor = instantiateDomainModelDescriptor( + modelDescriptorClass ); + for ( int i1 = 0; i1 < domainModelDescriptor.getAnnotatedClasses().length; i1++ ) { + final Class annotatedClass = domainModelDescriptor.getAnnotatedClasses()[i1]; + pui.applyManagedClassNames( annotatedClass.getName() ); + } + } + } + + final Map integrationSettings = new HashMap<>(); + + ( (Map) Environment.getProperties() ).forEach( + (key, value) -> + integrationSettings.put( (String) key, value ) + ); + + if ( !integrationSettings.containsKey( Environment.CONNECTION_PROVIDER ) ) { + integrationSettings.put( + AvailableSettings.CONNECTION_PROVIDER, + SharedDriverManagerConnectionProviderImpl.getInstance() + ); + } + for ( int i = 0; i < emfAnn.integrationSettings().length; i++ ) { + final Setting setting = emfAnn.integrationSettings()[i]; + integrationSettings.put( setting.name(), setting.value() ); + } + + for ( SettingProvider providerAnn : emfAnn.settingProviders() ) { + final Class> providerImpl = providerAnn.provider(); + try { + final SettingProvider.Provider provider = providerImpl.getConstructor().newInstance(); + integrationSettings.put( providerAnn.settingName(), provider.getSetting() ); + } + catch (Exception e) { + log.error( "Error obtaining setting provider for " + providerImpl.getName(), e ); + } + } + + final EntityManagerFactoryScopeImpl scope = new EntityManagerFactoryScopeImpl( pui, integrationSettings ); + + locateExtensionStore( testInstance, context ).put( EMF_KEY, scope ); + + return scope; + } + + private static DomainModelDescriptor instantiateDomainModelDescriptor(Class modelDescriptorClass) { + // first, see if it has a static singleton reference and use that if so + try { + final Field[] declaredFields = modelDescriptorClass.getDeclaredFields(); + for ( int i = 0; i < declaredFields.length; i++ ) { + final Field field = declaredFields[i]; + if ( ReflectHelper.isStaticField( field ) ) { + final Object value = field.get( null ); + if ( value instanceof DomainModelDescriptor ) { + return (DomainModelDescriptor) value; + } + } + } + } + catch (IllegalAccessException e) { + throw new RuntimeException( + "Problem accessing DomainModelDescriptor fields : " + modelDescriptorClass.getName(), + e + ); + } + + // no singleton field, try to instantiate it via reflection + try { + return modelDescriptorClass.getConstructor( null ).newInstance( null ); + } + catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException( + "Problem instantiation DomainModelDescriptor : " + modelDescriptorClass.getName(), + e + ); + } + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + log.tracef( "#postProcessTestInstance(%s, %s)", testInstance, context.getDisplayName() ); + + findEntityManagerFactoryScope( testInstance, context ); + } + + @Override + public void afterAll(ExtensionContext context) { + log.tracef( "#afterAll(%s)", context.getDisplayName() ); + + final Object testInstance = context.getRequiredTestInstance(); + + if ( testInstance instanceof SessionFactoryScopeAware ) { + ( (SessionFactoryScopeAware) testInstance ).injectSessionFactoryScope( null ); + } + + final EntityManagerFactoryScopeImpl removed = (EntityManagerFactoryScopeImpl) locateExtensionStore( + testInstance, + context + ).remove( EMF_KEY ); + if ( removed != null ) { + removed.close(); + } + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + log.tracef( "#handleTestExecutionException(%s, %s)", context.getDisplayName(), throwable ); + + try { + final Object testInstance = context.getRequiredTestInstance(); + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final EntityManagerFactoryScopeImpl scope = (EntityManagerFactoryScopeImpl) store.get( EMF_KEY ); + scope.releaseEntityManagerFactory(); + } + catch (Exception ignore) { + } + + throw throwable; + } + + private static class EntityManagerFactoryScopeImpl extends AbstractEntityManagerFactoryScope { + private final PersistenceUnitInfo persistenceUnitInfo; + private final Map integrationSettings; + + private EntityManagerFactoryScopeImpl( + PersistenceUnitInfo persistenceUnitInfo, + Map integrationSettings) { + this.persistenceUnitInfo = persistenceUnitInfo; + this.integrationSettings = integrationSettings; + } + + protected javax.persistence.EntityManagerFactory createEntityManagerFactory() { + final EntityManagerFactoryBuilder emfBuilder = Bootstrap.getEntityManagerFactoryBuilder( + new PersistenceUnitInfoDescriptor( persistenceUnitInfo ), + integrationSettings + ); + + return emfBuilder.build(); + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryParameterResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryParameterResolver.java new file mode 100644 index 000000000000..a722bf80077c --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryParameterResolver.java @@ -0,0 +1,49 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +import javax.persistence.EntityManagerFactory; + +import static org.hibernate.testing.orm.junit.EntityManagerFactoryExtension.findEntityManagerFactoryScope; + +/** + * @author Steve Ebersole + */ +public class EntityManagerFactoryParameterResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return JUnitHelper.supportsParameterInjection( + parameterContext, + EntityManagerFactory.class, + EntityManagerFactoryScope.class + ); + } + + @Override + public Object resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + final EntityManagerFactoryScope scope = findEntityManagerFactoryScope( + extensionContext.getRequiredTestInstance(), + extensionContext + ); + + if ( EntityManagerFactoryScope.class.isAssignableFrom( parameterContext.getParameter().getType() ) ) { + return scope; + } + + assert EntityManagerFactory.class.isAssignableFrom( parameterContext.getParameter().getType() ); + return scope.getEntityManagerFactory(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryProducer.java new file mode 100644 index 000000000000..a836d5aa3b49 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryProducer.java @@ -0,0 +1,29 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import javax.persistence.EntityManagerFactory; + +/** + * Contract for something that can build a SessionFactory. + * + * Used by SessionFactoryScopeExtension to create the + * SessionFactoryScope. + * + * Generally speaking, a test class would implement SessionFactoryScopeContainer + * and return the SessionFactoryProducer to be used for those tests. + * The SessionFactoryProducer is then used to build the SessionFactoryScope + * which is injected back into the SessionFactoryScopeContainer + * + * @see EntityManagerFactoryExtension + * @see EntityManagerFactoryScope + * + * @author Steve Ebersole + */ +public interface EntityManagerFactoryProducer { + EntityManagerFactory produceEntityManagerFactory(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScope.java new file mode 100644 index 000000000000..674e76d0730f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScope.java @@ -0,0 +1,36 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.resource.jdbc.spi.StatementInspector; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; + +/** + * @author Steve Ebersole + */ +public interface EntityManagerFactoryScope { + EntityManagerFactory getEntityManagerFactory(); + void releaseEntityManagerFactory(); + + StatementInspector getStatementInspector(); + T getStatementInspector(Class type); + + void inEntityManager(Consumer action); + void inTransaction(Consumer action); + void inTransaction(EntityManager entityManager, Consumer action); + + T fromEntityManager(Function action); + T fromTransaction(Function action); + T fromTransaction(EntityManager entityManager, Function action); + + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScopeContainer.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScopeContainer.java new file mode 100644 index 000000000000..53aa8397d72f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScopeContainer.java @@ -0,0 +1,28 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +/** + * The keystone in EntityManagerFactoryScopeExtension support. + * + * This is how the extensions know how to build an EntityManagerFactory (scope) + * and how to inject that EntityManagerFactory (scope) back into the test. + * + * @author Chris Cranford + */ +public interface EntityManagerFactoryScopeContainer { + /** + * Callback to inject the EntityManagerFactoryScope into the container. + */ + void injectEntityManagerFactoryScope(EntityManagerFactoryScope scope); + + /** + * Obtain the {@link EntityManagerFactoryProducer}. Quite often this is also + * implemented by the container itself. + */ + EntityManagerFactoryProducer getEntityManagerFactoryProducer(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScopeExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScopeExtension.java new file mode 100644 index 000000000000..e1eefbdc4b0a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryScopeExtension.java @@ -0,0 +1,116 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.util.Optional; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; + +import org.jboss.logging.Logger; + +import javax.persistence.EntityManagerFactory; + +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.create; + +/** + * The thing that actually manages lifecycle of the EntityManagerFactory related to a test class. + * Work in conjunction with EntityManagerFactoryScope and EntityManagerFactoryScopeContainer. + * + * @see EntityManagerFactoryScope + * @see EntityManagerFactoryScopeContainer + * @see EntityManagerFactoryProducer + * + * @author Chris Cranford + */ +public class EntityManagerFactoryScopeExtension + implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler { + + private static final Logger log = Logger.getLogger( EntityManagerFactoryScopeExtension.class ); + + public static ExtensionContext.Namespace namespace(Object testInstance) { + return create( EntityManagerFactoryScopeExtension.class.getName(), testInstance ); + } + + public static Optional findEntityManagerFactoryScope(ExtensionContext context) { + final Optional entityManagerFactoryScope = Optional.ofNullable( + context.getStore( namespace( context.getRequiredTestInstance() ) ) + .get( ENTITYMANAGER_FACTORY_KEY ) + ); + return entityManagerFactoryScope; + } + + public static final Object ENTITYMANAGER_FACTORY_KEY = "ENTITYMANAGER_FACTORY"; + + public EntityManagerFactoryScopeExtension() { + log.trace( "EntityManagerFactoryScopeExtension#" ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // TestInstancePostProcessor + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + log.trace( "EntityManagerFactoryScopeExtension#postProcessTestInstance" ); + if ( EntityManagerFactoryScopeContainer.class.isInstance( testInstance ) ) { + final EntityManagerFactoryScopeContainer scopeContainer = EntityManagerFactoryScopeContainer.class.cast( + testInstance ); + final EntityManagerFactoryScope scope = new EntityManagerFactoryScopeImpl( + scopeContainer.getEntityManagerFactoryProducer() + ); + context.getStore( namespace( testInstance ) ).put( ENTITYMANAGER_FACTORY_KEY, scope ); + + scopeContainer.injectEntityManagerFactoryScope( scope ); + } + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // AfterAllCallback + + @Override + public void afterAll(ExtensionContext context) { + final EntityManagerFactoryScope scope = (EntityManagerFactoryScope) + context.getStore( namespace( context.getRequiredTestInstance() ) ).remove( ENTITYMANAGER_FACTORY_KEY ); + if ( scope != null ) { + scope.releaseEntityManagerFactory(); + } + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // TestExecutionExceptionHandler + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + final Optional scopeOptional = findEntityManagerFactoryScope( context ); + if ( ! scopeOptional.isPresent() ) { + log.debug( "Could not locate EntityManagerFactoryScope on exception" ); + } + else { + scopeOptional.get().releaseEntityManagerFactory(); + } + + throw throwable; + } + + private static class EntityManagerFactoryScopeImpl extends AbstractEntityManagerFactoryScope { + + private final EntityManagerFactoryProducer producer; + + public EntityManagerFactoryScopeImpl(EntityManagerFactoryProducer producer) { + log.trace( "EntityManagerFactoryScope#" ); + this.producer = producer; + } + + @Override + protected EntityManagerFactory createEntityManagerFactory() { + return producer.produceEntityManagerFactory(); + } + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedException.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedException.java new file mode 100644 index 000000000000..302c77edf3d1 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedException.java @@ -0,0 +1,33 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Annotation that can be used, in conjunction with {@link ExpectedExceptionExtension}, + * to indicate that a specific test is expected to fail in a particular way + * (throw the specified exception) as its "success condition". + * + * @see ExpectedExceptionExtension + * + * @author Steve Ebersole + */ +@Inherited +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) + +@ExtendWith( ExpectedExceptionExtension.class ) +public @interface ExpectedException { + Class value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedExceptionExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedExceptionExtension.java new file mode 100644 index 000000000000..8453660b819a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedExceptionExtension.java @@ -0,0 +1,44 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; + +import org.jboss.logging.Logger; + +/** + * TestExecutionExceptionHandler used in conjunction with {@link ExpectedException} + * to support annotating tests with a specific exception that indicates a + * success (we are expecting that exception in that tested condition). + * + * @see ExpectedException + * + * @author Steve Ebersole + */ +public class ExpectedExceptionExtension implements TestExecutionExceptionHandler { + private static final Logger log = Logger.getLogger( ExpectedExceptionExtension.class ); + + @Override + public void handleTestExecutionException( + ExtensionContext context, + Throwable throwable) throws Throwable { + final ExpectedException annotation = context.getRequiredTestMethod().getAnnotation( ExpectedException.class ); + if ( annotation != null ) { + if ( annotation.value().isInstance( throwable ) ) { + log.debugf( + "Test [%s] threw exception [%s] which matched @ExpectedException : swallowing exception", + context.getDisplayName(), + throwable + ); + return; + } + } + + throw throwable; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExtraAssertions.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExtraAssertions.java new file mode 100644 index 000000000000..b3be71f1c03d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExtraAssertions.java @@ -0,0 +1,82 @@ +/* + * 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 . + */ +package org.hibernate.testing.orm.junit; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.sql.Types; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Steve Ebersole + */ +public final class ExtraAssertions { + private ExtraAssertions() { + } + + public static void assertClassAssignability(Class expected, Class actual) { + if ( !expected.isAssignableFrom( actual ) ) { + fail( "Expected class [" + expected.getName() + "] was not assignable from actual [" + actual.getName() + "]" ); + } + } + + @SuppressWarnings("unchecked") + public static T assertTyping(Class expectedType, Object value) { + if ( !expectedType.isInstance( value ) ) { + fail( + String.format( + "Expecting value of type [%s], but found [%s]", + expectedType.getName(), + value == null ? "" : value + ) + ); + } + return (T) value; + } + + public static void assertJdbcTypeCode(int expected, int actual) { + if ( expected != actual ) { + final String message = String.format( + "JDBC type codes did not match...\n" + + "Expected: %s (%s)\n" + + "Actual : %s (%s)", + jdbcTypeCodeMap().get( expected ), + expected, + jdbcTypeCodeMap().get( actual ), + actual + ); + fail( message ); + } + } + + private static Map jdbcTypeCodeMap; + + private static synchronized Map jdbcTypeCodeMap() { + if ( jdbcTypeCodeMap == null ) { + jdbcTypeCodeMap = generateJdbcTypeCache(); + } + return jdbcTypeCodeMap; + } + + private static Map generateJdbcTypeCache() { + final Field[] fields = Types.class.getFields(); + Map cache = new HashMap<>( (int) ( fields.length * .75 ) + 1 ); + for ( Field field : fields ) { + if ( Modifier.isStatic( field.getModifiers() ) ) { + try { + cache.put( (Integer) field.get( null ), field.getName() ); + } + catch (Throwable ignore) { + } + } + } + return cache; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpected.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpected.java new file mode 100644 index 000000000000..419abc7fe094 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpected.java @@ -0,0 +1,50 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Marks a test method or class as being expected to fail. + * + * @see FailureExpectedExtension + * + * @author Steve Ebersole + */ +@Inherited +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable( FailureExpectedGroup.class ) + +@ExtendWith( FailureExpectedExtension.class ) +public @interface FailureExpected { + /** + * Setting used to indicate that FailureExpected tests should be run and + * that we should validate they still fail. Note that in this "validation + * mode", a test failure is interpreted as a success which is the main + * difference from JUnit's support. + */ + String VALIDATE_FAILURE_EXPECTED = "hibernate.test.validatefailureexpected"; + + /** + * A reason why the failure is expected + */ + String reason() default ""; + + /** + * The key of a JIRA issue which covers this expected failure. + * @return The jira issue key + */ + String jiraKey() default ""; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedExtension.java new file mode 100644 index 000000000000..064eb077e895 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedExtension.java @@ -0,0 +1,165 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.util.Locale; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; + +import org.jboss.logging.Logger; + +/** + * JUnit 5 extension used to support {@link FailureExpected} handling + * + * @author Steve Ebersole + */ +public class FailureExpectedExtension + implements ExecutionCondition, BeforeEachCallback, AfterEachCallback, TestExecutionExceptionHandler { + + private static final Logger log = Logger.getLogger( FailureExpectedExtension.class ); + + private static final String IS_MARKED_STORE_KEY = "IS_MARKED"; + private static final String EXPECTED_FAILURE_STORE_KEY = "EXPECTED_FAILURE"; + + + public static final boolean failureExpectedValidation; + + static { + failureExpectedValidation = Boolean.getBoolean( FailureExpected.VALIDATE_FAILURE_EXPECTED ); + log.debugf( "FailureExpectedExtension#failureExpectedValidation = %s", failureExpectedValidation ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // ExecutionCondition + // - used to disable tests that are an `@ExpectedFailure` when + // failureExpectedValidation == false which is the default. + // + // When failureExpectedValidation == true, the test is allowed to + // run and we validate that the test does in fact fail. + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + log.tracef( "#evaluateExecutionCondition(%s)", context.getDisplayName() ); + + if ( !context.getElement().isPresent() ) { + throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() ); + } + + log.debugf( "Evaluating context - %s [failureExpectedValidation = %s]", context.getDisplayName(), failureExpectedValidation ); + + if ( TestingUtil.hasEffectiveAnnotation( context, FailureExpected.class ) + || TestingUtil.hasEffectiveAnnotation( context, FailureExpectedGroup.class ) ) { + // The test is marked as `FailureExpected`... + if ( failureExpectedValidation ) { + log.debugf( "Executing test marked with `@FailureExpected` for validation" ); + return ConditionEvaluationResult.enabled( "@ExpectedFailure validation" ); + } + else { + return ConditionEvaluationResult.disabled( "Disabled : @ExpectedFailure" ); + } + } + + return ConditionEvaluationResult.enabled( "No @ExpectedFailure" ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // BeforeEachCallback + // - used to determine whether a test is considered as an expected + // failure. If so, + + @Override + public void beforeEach(ExtensionContext context) { + log.tracef( "#beforeEach(%s)", context.getDisplayName() ); + + final boolean markedExpectedFailure = TestingUtil.hasEffectiveAnnotation( context, FailureExpected.class ) + || TestingUtil.hasEffectiveAnnotation( context, FailureExpectedGroup.class ); + + log.debugf( "Checking for @FailureExpected [%s] - %s", context.getDisplayName(), markedExpectedFailure ); + + final ExtensionContext.Namespace namespace = generateNamespace( context ); + context.getStore( namespace ).put( IS_MARKED_STORE_KEY, markedExpectedFailure ); + } + + private ExtensionContext.Namespace generateNamespace(ExtensionContext context) { + return ExtensionContext.Namespace.create( + getClass().getName(), + context.getRequiredTestMethod().getClass(), + context.getRequiredTestMethod().getName() + ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // AfterEachCallback - used to interpret the outcome of the test depending + // on whether it was marked as an `@ExpectedFailure` + + + + @Override + public void afterEach(ExtensionContext context) { + log.tracef( "#afterEach(%s)", context.getDisplayName() ); + + final ExtensionContext.Store store = context.getStore( generateNamespace( context ) ); + + final Boolean isMarked = (Boolean) store.remove( IS_MARKED_STORE_KEY ); + log.debugf( "Post-handling for @FailureExpected [%s] - %s", context.getDisplayName(), isMarked ); + + if ( isMarked == Boolean.TRUE ) { + final Throwable expectedFailure = (Throwable) store.remove( EXPECTED_FAILURE_STORE_KEY ); + log.debugf( " >> Captured exception - %s", expectedFailure ); + + if ( expectedFailure == null ) { + // even though we expected a failure, the test did not fail + throw new ExpectedFailureDidNotFail( context ); + } + } + } + + private static class ExpectedFailureDidNotFail extends RuntimeException { + ExpectedFailureDidNotFail(ExtensionContext context) { + super( + String.format( + Locale.ROOT, + "`%s#%s` was marked as `@ExpectedFailure`, but did not fail", + context.getRequiredTestClass().getName(), + context.getRequiredTestMethod().getName() + ) + ); + } + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + log.tracef( "#handleTestExecutionException(%s, %s)", context.getDisplayName(), throwable.getClass().getName() ); + + final ExtensionContext.Store store = context.getStore( generateNamespace( context ) ); + + final Boolean isMarked = (Boolean) store.get( IS_MARKED_STORE_KEY ); + log.debugf( "Handling test exception [%s]; marked @FailureExcepted = %s", context.getDisplayName(), isMarked ); + + if ( isMarked == Boolean.TRUE ) { + // test is marked as an `@ExpectedFailure`: + + // 1) add the exception to the store + store.put( EXPECTED_FAILURE_STORE_KEY, throwable ); + log.debugf( " >> Stored expected failure - %s", throwable ); + + // 2) eat the failure + return; + } + + // otherwise, re-throw + throw throwable; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedGroup.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedGroup.java new file mode 100644 index 000000000000..fd76cfbefa64 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedGroup.java @@ -0,0 +1,27 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @author Steve Ebersole + */ +@Inherited +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) + +@ExtendWith( FailureExpectedExtension.class ) +public @interface FailureExpectedGroup { + FailureExpected[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FunctionalEntityManagerFactoryTesting.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FunctionalEntityManagerFactoryTesting.java new file mode 100644 index 000000000000..8bc9f851505a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FunctionalEntityManagerFactoryTesting.java @@ -0,0 +1,42 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Composite annotation for functional tests that require + * a functioning EntityManagerFactory. + * + * @apiNote Logically this should also include + * `@TestInstance( TestInstance.Lifecycle.PER_CLASS )` + * but that annotation is not conveyed (is that the + * right word? its not applied to the thing using this annotation). + * Test classes should apply that themselves. + * + * @see EntityManagerFactoryScopeExtension + * @see DialectFilterExtension + * @see FailureExpectedExtension + * + * @author Chris Cranford + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@TestInstance(TestInstance.Lifecycle.PER_CLASS ) +@ExtendWith(EntityManagerFactoryScopeExtension.class) +@ExtendWith(DialectFilterExtension.class) +@ExtendWith(FailureExpectedExtension.class) +public @interface FunctionalEntityManagerFactoryTesting { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JUnitHelper.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JUnitHelper.java new file mode 100644 index 000000000000..d2978f3bacd2 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JUnitHelper.java @@ -0,0 +1,44 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; + +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.create; + +/** + * @author Steve Ebersole + */ +public class JUnitHelper { + public static ExtensionContext.Store locateExtensionStore( + Class extensionClass, + ExtensionContext context, + Object scopeObject) { + return context.getStore( create( extensionClass.getName(), scopeObject ) ); + } + + public static ExtensionContext.Store locateExtensionStore( + ExtensionContext context, + Object... scopeRefs) { + return context.getStore( create( scopeRefs ) ); + } + + private JUnitHelper() { + } + + public static boolean supportsParameterInjection(ParameterContext parameterContext, Class... supportedTypes) { + for ( Class supportedType : supportedTypes ) { + if ( parameterContext.getParameter().getType().isAssignableFrom( supportedType ) ) { + return true; + } + } + + return false; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKey.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKey.java new file mode 100644 index 000000000000..01a495a6ae70 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKey.java @@ -0,0 +1,31 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Identifies the JIRA issue associated with a test. Is repeatable, so + * multiple JIRA issues can be indicated. + * + * @see JiraKeyGroup + * + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Repeatable( JiraKeyGroup.class ) +public @interface JiraKey { + /** + * The key for the referenced Jira issue (e.g., HHH-99999) + */ + String value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKeyGroup.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKeyGroup.java new file mode 100644 index 000000000000..85106fb8571c --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKeyGroup.java @@ -0,0 +1,25 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Grouping annotation for `@JiraKey` + * + * @see JiraKey + * + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface JiraKeyGroup { + JiraKey[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Jpa.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Jpa.java new file mode 100644 index 000000000000..4572f9ba53d4 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Jpa.java @@ -0,0 +1,103 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.jpa.spi.JpaCompliance; + +import org.hibernate.testing.orm.domain.DomainModelDescriptor; +import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +import javax.persistence.SharedCacheMode; +import javax.persistence.ValidationMode; +import javax.persistence.spi.PersistenceUnitTransactionType; + + + +/** + * @author Steve Ebersole + */ +@Inherited +@Target( ElementType.TYPE ) +@Retention( RetentionPolicy.RUNTIME ) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@ExtendWith( EntityManagerFactoryExtension.class ) +@ExtendWith( EntityManagerFactoryParameterResolver.class ) + +@ExtendWith( FailureExpectedExtension.class ) +public @interface Jpa { + String persistenceUnitName() default "test-pu"; + + /** + * Used to mimic container integration + */ + Setting[] integrationSettings() default {}; + + // todo : multiple persistence units? + + /** + * Persistence unit properties + */ + Setting[] properties() default {}; + + SettingProvider[] settingProviders() default {}; + + boolean generateStatistics() default false; + boolean exportSchema() default true; + + PersistenceUnitTransactionType transactionType() default PersistenceUnitTransactionType.RESOURCE_LOCAL; + SharedCacheMode sharedCacheMode() default SharedCacheMode.UNSPECIFIED; + ValidationMode validationMode() default ValidationMode.NONE; + + /** + * @see JpaCompliance#isJpaQueryComplianceEnabled() + */ + boolean queryComplianceEnabled() default false; + + /** + * @see JpaCompliance#isJpaTransactionComplianceEnabled() + */ + boolean transactionComplianceEnabled() default false; + + /** + * @see JpaCompliance#isJpaClosedComplianceEnabled() + */ + boolean closedComplianceEnabled() default false; + + /** + * @see JpaCompliance#isJpaProxyComplianceEnabled() + */ + boolean proxyComplianceEnabled() default false; + + /** + * @see JpaCompliance#isJpaCacheComplianceEnabled() + */ + boolean cacheComplianceEnabled() default false; + + /** + * @see JpaCompliance#isGlobalGeneratorScopeEnabled() + */ + boolean generatorScopeComplianceEnabled() default false; + + boolean excludeUnlistedClasses() default false; + + StandardDomainModel[] standardModels() default {}; + Class[] modelDescriptorClasses() default {}; + Class[] annotatedClasses() default {}; + String[] annotatedClassNames() default {}; + String[] annotatedPackageNames() default {}; + String[] xmlMappings() default {}; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Logger.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Logger.java new file mode 100644 index 000000000000..fa8c714bed3e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Logger.java @@ -0,0 +1,30 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +/** + * Must specify one of {@link #loggerNameClass} or {@link #loggerName()} + */ +public @interface Logger { + // I think we can actually look up the "bare" Logger and still get the same + // capability in terms of register listeners + //Class messageLoggerClass() default CoreMessageLogger.class; + + /** + * The `Class` used as the base for the logger name. + * + * @see org.jboss.logging.Logger#getLogger(Class) + */ + Class loggerNameClass() default void.class; + + /** + * The `Class` used as the base for the logger name. + * + * @see org.jboss.logging.Logger#getLogger(Class) + */ + String loggerName() default ""; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspections.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspections.java new file mode 100644 index 000000000000..1309131df58a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspections.java @@ -0,0 +1,48 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Injects the ability to watch multiple for log messages being triggered. + * + * Only available at the class-level + * + * For watching a single message-key, {@link MessageKeyInspection} is a + * better option. + */ +@Inherited +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) + +@ExtendWith( LoggingInspectionsExtension.class ) +@ExtendWith( LoggingInspectionsScopeResolver.class ) +public @interface LoggingInspections { + Message[] messages() default {}; + + @interface Message { + /** + * The message-key to watch for. The message-key is the combination of + * {@link org.jboss.logging.annotations.MessageLogger#projectCode()} + * and {@link org.jboss.logging.annotations.Message#id()} used by + * JBoss Logging to prefix each messaged log event + */ + String messageKey(); + + /** + * Descriptor of the log messages to watch for + */ + Logger[] loggers() default {}; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsExtension.java new file mode 100644 index 000000000000..711ed7c6982f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsExtension.java @@ -0,0 +1,60 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; + +/** + * @author Steve Ebersole + */ +public class LoggingInspectionsExtension implements TestInstancePostProcessor, BeforeEachCallback { + private static final String KEY = LoggingInspectionsExtension.class.getName(); + + // todo (6.0) : have this implement `AfterEachCallback` support to reset after each test? + + @Override + public void postProcessTestInstance( + Object testInstance, + ExtensionContext context) { + resolveLoggingInspectionScope( testInstance, context ); + } + + @Override + public void beforeEach(ExtensionContext context) { + final Store extensionStore = locateExtensionStore( context.getRequiredTestInstance(), context ); + final LoggingInspectionsScope existing = (LoggingInspectionsScope) extensionStore.get( KEY ); + if ( existing != null ) { + existing.resetWatchers(); + } + } + + public static LoggingInspectionsScope resolveLoggingInspectionScope(Object testInstance, ExtensionContext context) { + final Store extensionStore = locateExtensionStore( testInstance, context ); + final Object existing = extensionStore.get( KEY ); + if ( existing != null ) { + return (LoggingInspectionsScope) existing; + } + + // we'll need to create it... + + // find the annotation + final LoggingInspections loggingInspections = testInstance.getClass().getAnnotation( LoggingInspections.class ); + + // Create the scope and add to context store + final LoggingInspectionsScope scope = new LoggingInspectionsScope( loggingInspections, context ); + extensionStore.put( KEY, scope ); + + return scope; + } + + private static Store locateExtensionStore(Object testInstance, ExtensionContext context) { + return JUnitHelper.locateExtensionStore( EntityManagerFactoryExtension.class, context, testInstance ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsScope.java new file mode 100644 index 000000000000..3ab2390754fb --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsScope.java @@ -0,0 +1,73 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Manages all of the MessageKeyWatcher defined by LoggingInspectionsScope + */ +public class LoggingInspectionsScope { + private final Map> watcherMap = new HashMap<>(); + + + public LoggingInspectionsScope(LoggingInspections loggingInspections, ExtensionContext context) { + for ( int i = 0; i < loggingInspections.messages().length; i++ ) { + final LoggingInspections.Message message = loggingInspections.messages()[ i ]; + + final String messageKey = message.messageKey().trim(); + assert ! messageKey.isEmpty(); + + if ( message.loggers().length == 0 ) { + return; + } + + final Map messageKeyWatcherMap; + final Map existingMessageKeyWatcherMap = watcherMap.get( messageKey ); + if ( existingMessageKeyWatcherMap != null ) { + messageKeyWatcherMap = existingMessageKeyWatcherMap; + } + else { + messageKeyWatcherMap = new HashMap<>(); + watcherMap.put( messageKey, messageKeyWatcherMap ); + } + + for ( Logger logger : message.loggers() ) { + final String loggerKey = MessageKeyWatcherImpl.loggerKey( logger ); + final MessageKeyWatcherImpl watcher; + final MessageKeyWatcherImpl existingWatcher = messageKeyWatcherMap.get( loggerKey ); + if ( existingWatcher != null ) { + watcher = existingWatcher; + } + else { + watcher = new MessageKeyWatcherImpl( messageKey ); + messageKeyWatcherMap.put( loggerKey, watcher ); + } + watcher.addLogger( logger ); + } + } + } + + public void resetWatchers() { + watcherMap.forEach( + (messageKey,loggerMap) -> loggerMap.forEach( (logger,watcher) -> watcher.reset() ) + ); + } + + public MessageKeyWatcher getWatcher(String messageKey, String loggerName) { + final Map messageKeyWatcherMap = watcherMap.get( messageKey ); + return messageKeyWatcherMap.get( loggerName ); + } + + public MessageKeyWatcher getWatcher(String messageKey, Class loggerNameClass) { + final Map messageKeyWatcherMap = watcherMap.get( messageKey ); + return messageKeyWatcherMap.get( loggerNameClass.getName() ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsScopeResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsScopeResolver.java new file mode 100644 index 000000000000..f7a473e91567 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/LoggingInspectionsScopeResolver.java @@ -0,0 +1,37 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * ParameterResolver implementation for resolving + * {@link LoggingInspectionsScope} ParameterResolver + */ +public class LoggingInspectionsScopeResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) { + return LoggingInspectionsScope.class.isAssignableFrom( + parameterContext.getParameter().getType() + ); + } + + @Override + public LoggingInspectionsScope resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return LoggingInspectionsExtension.resolveLoggingInspectionScope( + extensionContext.getRequiredTestInstance(), + extensionContext + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyInspection.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyInspection.java new file mode 100644 index 000000000000..843b155eebd5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyInspection.java @@ -0,0 +1,41 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Injects the ability to watch for a log messages being triggered. + * + * For watching a multiple message-keys, see {@link LoggingInspections} + */ +@Inherited +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) + +@ExtendWith( MessageKeyInspectionExtension.class ) +@ExtendWith( MessageKeyWatcherResolver.class ) +public @interface MessageKeyInspection { + /** + * The message key to look for. + * + * @apiNote This is effectively a starts-with check. We simply check + * that the logged message starts with the value from here + */ + String messageKey(); + + /** + * The logger to watch on + */ + Logger logger(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyInspectionExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyInspectionExtension.java new file mode 100644 index 000000000000..1ae6aade4877 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyInspectionExtension.java @@ -0,0 +1,97 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.reflect.Method; + +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; + +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.create; + +/** + * @author Steve Ebersole + */ +public class MessageKeyInspectionExtension implements TestInstancePostProcessor, BeforeEachCallback { + public static final String KEY = LoggingInspectionsExtension.class.getName(); + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + // Process the MessageKeyInspection annotation that happens at the class-level + + final ExtensionContext.Store instanceStore = resolveInstanceStore( testInstance, context ); + + final Object existing = instanceStore.get( KEY ); + if ( existing != null ) { + // odd, but there would be nothing to do + return; + } + + // find the annotation, create the watcher and add it to the context + final MessageKeyInspection inspection = testInstance.getClass().getAnnotation( MessageKeyInspection.class ); + final MessageKeyWatcherImpl watcher = new MessageKeyWatcherImpl( inspection.messageKey() ); + watcher.addLogger( inspection.logger() ); + + instanceStore.put( KEY, watcher ); + } + + public static ExtensionContext.Store resolveInstanceStore(Object testInstance, ExtensionContext context) { + final ExtensionContext.Namespace instanceStoreNamespace = create( testInstance ); + return context.getStore( instanceStoreNamespace ); + } + + @Override + public void beforeEach(ExtensionContext context) { + final Method method = context.getRequiredTestMethod(); + + final ExtensionContext.Store methodStore = resolveMethodStore( context ); + final MessageKeyWatcher existing = (MessageKeyWatcher) methodStore.get( KEY ); + if ( existing != null ) { + prepareForUse( existing ); + // already there - nothing to do + return; + } + + // if the test-method is annotated, use a one-off watcher for that message + final MessageKeyInspection inspectionAnn = method.getAnnotation( MessageKeyInspection.class ); + if ( inspectionAnn != null ) { + final MessageKeyWatcherImpl watcher = new MessageKeyWatcherImpl( inspectionAnn.messageKey() ); + watcher.addLogger( inspectionAnn.logger() ); + methodStore.put( KEY, watcher ); + prepareForUse( watcher ); + return; + } + + // look for a class/instance-level watcher + final ExtensionContext.Store instanceStore = resolveInstanceStore( context.getRequiredTestInstance(), context ); + final MessageKeyWatcher instanceLevelWatcher = (MessageKeyWatcher) instanceStore.get( KEY ); + if ( instanceLevelWatcher != null ) { + methodStore.put( KEY, instanceLevelWatcher ); + prepareForUse( instanceLevelWatcher ); + } + } + + private void prepareForUse(MessageKeyWatcher watcher) { + watcher.reset(); + } + + public static ExtensionContext.Store resolveMethodStore(ExtensionContext context) { + final ExtensionContext.Namespace instanceStoreNamespace = create( context.getRequiredTestMethod() ); + return context.getStore( instanceStoreNamespace ); + } + + public static MessageKeyWatcher getWatcher(ExtensionContext context) { + final ExtensionContext.Store methodStore = resolveMethodStore( context ); + final Object ref = methodStore.get( KEY ); + if ( ref == null ) { + throw new IllegalStateException( "No watcher available" ); + } + return (MessageKeyWatcher) ref; + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcher.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcher.java new file mode 100644 index 000000000000..68e08b62aa0d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcher.java @@ -0,0 +1,24 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.util.List; + +/** + * @author Steve Ebersole + */ +public interface MessageKeyWatcher { + String getMessageKey(); + + boolean wasTriggered(); + + List getTriggeredMessages(); + + String getFirstTriggeredMessage(); + + void reset(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcherImpl.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcherImpl.java new file mode 100644 index 000000000000..a5e4f12c41d7 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcherImpl.java @@ -0,0 +1,114 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.testing.logger.LogInspectionHelper; +import org.hibernate.testing.logger.LogListener; + +import org.jboss.logging.Logger; + +/** + * MessageIdWatcher implementation + */ +public class MessageKeyWatcherImpl implements MessageKeyWatcher, LogListener { + private final String messageKey; + private final List loggerNames = new ArrayList<>(); + private final List triggeredMessages = new ArrayList<>(); + + public MessageKeyWatcherImpl(String messageKey) { + this.messageKey = messageKey; + } + + public void addLoggerName(String name) { + loggerNames.add( name ); + } + + public void addLogger(org.hibernate.testing.orm.junit.Logger loggerAnn) { + final Logger logger; + if ( loggerAnn.loggerNameClass() != void.class ) { + logger = Logger.getLogger( loggerAnn.loggerNameClass() ); + } + else if ( ! "".equals( loggerAnn.loggerName().trim() ) ) { + logger = Logger.getLogger( loggerAnn.loggerName().trim() ); + } + else { + throw new IllegalStateException( + "@LoggingInspections for prefix '" + messageKey + + "' did not specify proper Logger name. Use `@LoggingInspections#loggerName" + + " or `@LoggingInspections#loggerNameClass`" + ); + } + + LogInspectionHelper.registerListener( this, logger ); + } + + public static String loggerKey(org.hibernate.testing.orm.junit.Logger loggerAnn) { + final Logger logger; + if ( loggerAnn.loggerNameClass() != void.class ) { + logger = Logger.getLogger( loggerAnn.loggerNameClass() ); + } + else if ( ! "".equals( loggerAnn.loggerName().trim() ) ) { + logger = Logger.getLogger( loggerAnn.loggerName().trim() ); + } + else { + throw new IllegalArgumentException( + "`@Logger` must specify either `#loggerNameClass` or `#loggerName`" + ); + } + + return logger.getName(); + } + + public List getLoggerNames() { + return loggerNames; + } + + @Override + public String getMessageKey() { + return messageKey; + } + + @Override + public boolean wasTriggered() { + return ! triggeredMessages.isEmpty(); + } + + @Override + public List getTriggeredMessages() { + return triggeredMessages; + } + + @Override + public String getFirstTriggeredMessage() { + return triggeredMessages.isEmpty() ? null : triggeredMessages.get( 0 ); + } + + @Override + public void reset() { + triggeredMessages.clear(); + } + + @Override + public void loggedEvent(Logger.Level level, String renderedMessage, Throwable thrown) { + if ( renderedMessage != null ) { + if ( renderedMessage.startsWith( messageKey ) ) { + triggeredMessages.add( renderedMessage ); + } + } + } + + @Override + public String toString() { + return "MessageIdWatcherImpl{" + + "messageKey='" + messageKey + '\'' + + ", loggerNames=" + loggerNames + + '}'; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcherResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcherResolver.java new file mode 100644 index 000000000000..3c07332e268d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/MessageKeyWatcherResolver.java @@ -0,0 +1,27 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @author Steve Ebersole + */ +public class MessageKeyWatcherResolver implements ParameterResolver { + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return MessageKeyWatcher.class.isAssignableFrom( parameterContext.getParameter().getType() ); + } + + @Override + public MessageKeyWatcher resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return MessageKeyInspectionExtension.getWatcher( extensionContext ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialect.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialect.java new file mode 100644 index 000000000000..bef07cf121ae --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialect.java @@ -0,0 +1,49 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.dialect.Dialect; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Indicates that the annotated test class/method should only + * be run when the indicated Dialect is being used. + * + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Repeatable( RequiresDialects.class ) + +@ExtendWith( DialectFilterExtension.class ) +public @interface RequiresDialect { + /** + * The Dialect class to match. + */ + Class value(); + + /** + * Should subtypes of {@link #value()} be matched? + */ + boolean matchSubTypes() default true; + + /** + * Comment describing the reason why the dialect is required. + * + * @return The comment + */ + String comment() default ""; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeature.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeature.java new file mode 100644 index 000000000000..dbb5dab02ece --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeature.java @@ -0,0 +1,54 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Annotation used to indicate that a test should be run only when the current dialect supports the + * specified feature. + * + * @author Andrea Boriero + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Repeatable( RequiresDialectFeatureGroup.class ) + +@ExtendWith( DialectFilterExtension.class ) +public @interface RequiresDialectFeature { + /** + * @return Class which checks the necessary dialect feature + */ + Class feature(); + + /** + * @return Whether the decision of {@link #feature()} is reversed + */ + boolean reverse() default false; + + /** + * Comment describing the reason why the feature is required. + * + * @return The comment + */ + String comment() default ""; + + /** + * The key of a JIRA issue which relates this this feature requirement. + * + * @return The jira issue key + */ + String jiraKey() default ""; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeatureGroup.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeatureGroup.java new file mode 100644 index 000000000000..425be3e67737 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeatureGroup.java @@ -0,0 +1,37 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Annotation used to indicate that a test should be run only when the current dialect supports the + * specified feature. + * + * @author Hardy Ferentschik + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) + +@ExtendWith( DialectFilterExtension.class ) +public @interface RequiresDialectFeatureGroup { + RequiresDialectFeature[] value(); + + /** + * The key of a JIRA issue which relates this this feature requirement. + * + * @return The jira issue key + */ + String jiraKey() default ""; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialects.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialects.java new file mode 100644 index 000000000000..263c505bfd3c --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialects.java @@ -0,0 +1,28 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @author Andrea Boriero + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) + +@ExtendWith( DialectFilterExtension.class ) +public @interface RequiresDialects { + RequiresDialect[] value(); +} + diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistry.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistry.java new file mode 100644 index 000000000000..dc01a9b2350c --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistry.java @@ -0,0 +1,98 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.service.spi.ServiceContributor; + +/** + * @asciidoc + * + * Used to define the ServiceRegistry to be used for testing. Can be used alone: + * + * [source, JAVA, indent=0] + * ---- + * @ServiceRegistry ( ... ) + * class MyTest extends ServiceRegistryAware { + * @Test + * public void doTheTest() { + * // use the injected registry... + * + * ... + * } + * + * private StandardServiceRegistry registry; + * + * @Override + * public void injectServiceRegistryScope(StandardServiceRegistry registry) { + * this.registry = registry; + * } + * } + * ---- + * + * It can also be used as the basis for building a + * {@link org.hibernate.boot.spi.MetadataImplementor} via {@link DomainModel} + * or {@link SessionFactoryImplementor} via {@link SessionFactory}, + * with or without {@link ServiceRegistryScopeAware}. E.g. + * + * [source, JAVA, indent=0] + * ---- + * @ServiceRegistry ( ... ) + * @TestDomain ( ... ) + * class MyTest ... { + * } + * ---- + * + * Here, the managed ServiceRegistry is used to create the + * {@link org.hibernate.boot.spi.MetadataImplementor} + * + * @see ServiceRegistryScopeAware + * + * @author Steve Ebersole + */ +@Inherited +@Target( ElementType.TYPE ) +@Retention( RetentionPolicy.RUNTIME ) + +@ServiceRegistryFunctionalTesting +public @interface ServiceRegistry { + Class[] serviceContributors() default {}; + + Class[] initiators() default {}; + + Service[] services() default {}; + JavaService[] javaServices() default {}; + + Setting[] settings() default {}; + + SettingProvider[] settingProviders() default {}; + + /** + * A Hibernate Service registration + */ + @interface Service { + Class role(); + Class impl(); + } + + /** + * A Java service loadable via {@link java.util.ServiceLoader} + */ + @interface JavaService { + Class role(); + Class[] impls(); + } + + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryExtension.java new file mode 100644 index 000000000000..af64ea459416 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryExtension.java @@ -0,0 +1,336 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; +import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.integrator.spi.Integrator; +import org.hibernate.service.spi.ServiceContributor; + +import org.hibernate.testing.boot.ExtraJavaServicesClassLoaderService; +import org.hibernate.testing.boot.ExtraJavaServicesClassLoaderService.JavaServiceDescriptor; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.platform.commons.support.AnnotationSupport; + +import org.jboss.logging.Logger; + +/** + * JUnit extension used to manage the StandardServiceRegistry used by a test including + * creating the StandardServiceRegistry and releasing it afterwards + * + * @author Steve Ebersole + */ +public class ServiceRegistryExtension + implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler { + private static final Logger log = Logger.getLogger( ServiceRegistryExtension.class ); + private static final String REGISTRY_KEY = ServiceRegistryScope.class.getName(); + + public static StandardServiceRegistry findServiceRegistry( + Object testInstance, + ExtensionContext context) { + return findServiceRegistryScope( testInstance, context ).getRegistry(); + } + + private static ExtensionContext.Store locateExtensionStore( + Object testInstance, + ExtensionContext context) { + return JUnitHelper.locateExtensionStore( ServiceRegistryExtension.class, context, testInstance ); + } + + public static ServiceRegistryScope findServiceRegistryScope(Object testInstance, ExtensionContext context) { + log.tracef( "#findServiceRegistryScope(%s, %s)", testInstance, context.getDisplayName() ); + + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + + ServiceRegistryScopeImpl existingScope = (ServiceRegistryScopeImpl) store.get( REGISTRY_KEY ); + + if ( existingScope == null ) { + log.debugf( "Creating ServiceRegistryScope - %s", context.getDisplayName() ); + + final BootstrapServiceRegistryProducer bsrProducer; + + final Optional bsrAnnWrapper = AnnotationSupport.findAnnotation( + context.getElement().get(), + BootstrapServiceRegistry.class + ); + + if ( bsrAnnWrapper.isPresent() ) { + bsrProducer = bsrBuilder -> { + final BootstrapServiceRegistry bsrAnn = bsrAnnWrapper.get(); + configureJavaServices( bsrAnn, bsrBuilder ); + configureIntegrators( bsrAnn, bsrBuilder ); + + return bsrBuilder.enableAutoClose().build(); + }; + } + else { + bsrProducer = BootstrapServiceRegistryBuilder::build; + } + + final ServiceRegistryProducer ssrProducer; + + if ( testInstance instanceof ServiceRegistryProducer ) { + ssrProducer = (ServiceRegistryProducer) testInstance; + } + else { + ssrProducer = new ServiceRegistryProducerImpl(context); + } + + final ServiceRegistryScopeImpl scope = new ServiceRegistryScopeImpl( bsrProducer, ssrProducer ); + scope.getRegistry(); + + locateExtensionStore( testInstance, context ).put( REGISTRY_KEY, scope ); + + if ( testInstance instanceof ServiceRegistryScopeAware ) { + ( (ServiceRegistryScopeAware) testInstance ).injectServiceRegistryScope( scope ); + } + return scope; + } + + return existingScope; + } + + private static class ServiceRegistryProducerImpl implements ServiceRegistryProducer{ + private final ExtensionContext context; + public ServiceRegistryProducerImpl(ExtensionContext context) { + this.context = context; + if ( !context.getElement().isPresent() ) { + throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() ); + } + } + + @Override + public StandardServiceRegistry produceServiceRegistry(StandardServiceRegistryBuilder ssrb) { + // set some baseline test settings + ssrb.applySetting( AvailableSettings.STATEMENT_INSPECTOR, org.hibernate.testing.jdbc.SQLStatementInspector.class ); + + final Optional ssrAnnWrapper = AnnotationSupport.findAnnotation( + context.getElement().get(), + ServiceRegistry.class + ); + + if ( ssrAnnWrapper.isPresent() ) { + final ServiceRegistry serviceRegistryAnn = ssrAnnWrapper.get(); + configureServices( serviceRegistryAnn, ssrb ); + } + + return ssrb.build(); + } + + @Override + public void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder bsrb) { + + } + } + + private static void configureIntegrators( + BootstrapServiceRegistry bsrAnn, + final BootstrapServiceRegistryBuilder bsrBuilder) { + final Class[] integrators = bsrAnn.integrators(); + if ( integrators.length == 0 ) { + return; + } + + for ( Class integratorImpl : integrators ) { + assert integratorImpl != null; + + try { + final Constructor constructor = integratorImpl.getDeclaredConstructor(); + + final Integrator integrator = constructor.newInstance(); + bsrBuilder.applyIntegrator( integrator ); + } + catch (NoSuchMethodException e) { + throw new IllegalArgumentException( "Could not find no-arg constructor for Integrator : " + integratorImpl.getName(), e ); + } + catch (IllegalAccessException e) { + throw new IllegalArgumentException( "Unable to access no-arg constructor for Integrator : " + integratorImpl.getName(), e ); + } + catch (InstantiationException | InvocationTargetException e) { + throw new IllegalArgumentException( "Unable to instantiate Integrator : " + integratorImpl.getName(), e ); + } + } + } + + private static void configureJavaServices(BootstrapServiceRegistry bsrAnn, BootstrapServiceRegistryBuilder bsrBuilder) { + final BootstrapServiceRegistry.JavaService[] javaServiceAnns = bsrAnn.javaServices(); + if ( javaServiceAnns.length == 0 ) { + return; + } + + final List> javaServiceDescriptors = new ArrayList<>( javaServiceAnns.length ); + for ( int i = 0; i < javaServiceAnns.length; i++ ) { + final BootstrapServiceRegistry.JavaService javaServiceAnn = javaServiceAnns[ i ]; + javaServiceDescriptors.add( + new JavaServiceDescriptor( + javaServiceAnn.role(), + javaServiceAnn.impl() + ) + ); + } + final ExtraJavaServicesClassLoaderService cls = new ExtraJavaServicesClassLoaderService( javaServiceDescriptors ); + bsrBuilder.applyClassLoaderService( cls ); + } + + private static void configureServices(ServiceRegistry serviceRegistryAnn, StandardServiceRegistryBuilder ssrb) { + try { + for ( Setting setting : serviceRegistryAnn.settings() ) { + ssrb.applySetting( setting.name(), setting.value() ); + } + + for ( SettingProvider providerAnn : serviceRegistryAnn.settingProviders() ) { + final Class providerImpl = providerAnn.provider(); + final SettingProvider.Provider provider = providerImpl.getConstructor().newInstance(); + ssrb.applySetting( providerAnn.settingName(), provider.getSetting() ); + } + + for ( Class contributorClass : serviceRegistryAnn.serviceContributors() ) { + final ServiceContributor serviceContributor = contributorClass.newInstance(); + serviceContributor.contribute( ssrb ); + } + + for ( Class initiatorClass : serviceRegistryAnn.initiators() ) { + ssrb.addInitiator( initiatorClass.newInstance() ); + } + + for ( ServiceRegistry.Service service : serviceRegistryAnn.services() ) { + ssrb.addService( (Class) service.role(), service.impl().newInstance() ); + } + } + catch (Exception e) { + throw new RuntimeException( "Could not configure StandardServiceRegistryBuilder", e ); + } + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + log.tracef( "#postProcessTestInstance(%s, %s)", testInstance, context.getDisplayName() ); + findServiceRegistryScope( testInstance, context ); + } + + @Override + public void afterAll(ExtensionContext context) { + log.tracef( "#afterAll(%s)", context.getDisplayName() ); + + final Object testInstance = context.getRequiredTestInstance(); + + if ( testInstance instanceof ServiceRegistryScopeAware ) { + ( (ServiceRegistryScopeAware) testInstance ).injectServiceRegistryScope( null ); + } + + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final ServiceRegistryScopeImpl scope = (ServiceRegistryScopeImpl) store.remove( REGISTRY_KEY ); + if ( scope != null ) { + scope.close(); + } + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + log.tracef( "#handleTestExecutionException(%s, %s)", context.getDisplayName(), throwable ); + + final Object testInstance = context.getRequiredTestInstance(); + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final ServiceRegistryScopeImpl scope = (ServiceRegistryScopeImpl) store.get( REGISTRY_KEY ); + scope.releaseRegistry(); + + throw throwable; + } + + private static class ServiceRegistryScopeImpl implements ServiceRegistryScope, ExtensionContext.Store.CloseableResource { + private BootstrapServiceRegistryProducer bsrProducer; + private ServiceRegistryProducer ssrProducer; + + private StandardServiceRegistry registry; + private boolean active = true; + + public ServiceRegistryScopeImpl(BootstrapServiceRegistryProducer bsrProducer, ServiceRegistryProducer ssrProducer) { + this.bsrProducer = bsrProducer; + this.ssrProducer = ssrProducer; + } + + private StandardServiceRegistry createRegistry() { + BootstrapServiceRegistryBuilder bsrb = new BootstrapServiceRegistryBuilder().enableAutoClose(); + ssrProducer.prepareBootstrapRegistryBuilder(bsrb); + + final org.hibernate.boot.registry.BootstrapServiceRegistry bsr = bsrProducer.produceServiceRegistry( bsrb ); + try { + final StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder( bsr ); + // we will close it ourselves explicitly. + ssrb.disableAutoClose(); + + return registry = ssrProducer.produceServiceRegistry( ssrb ); + } + catch (Throwable t) { + bsr.close(); + throw t; + } + } + + private void verifyActive() { + if ( !active ) { + throw new IllegalStateException( "ServiceRegistryScope no longer active" ); + } + } + + @Override + public StandardServiceRegistry getRegistry() { + verifyActive(); + + if ( registry == null ) { + registry = createRegistry(); + } + + return registry; + } + + @Override + public void close() { + if ( ! active ) { + return; + } + + log.debugf( "Closing ServiceRegistryScope" ); + + active = false; + + if ( registry != null ) { + releaseRegistry(); + registry = null; + } + } + + private void releaseRegistry() { + if ( registry == null ) { + return; + } + + try { + log.tracef( "#releaseRegistry" ); + StandardServiceRegistryBuilder.destroy( registry ); + } + catch (Exception e) { + log.warn( "Unable to release StandardServiceRegistry", e ); + } + finally { + registry = null; + } + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryFunctionalTesting.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryFunctionalTesting.java new file mode 100644 index 000000000000..65ebd558ce92 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryFunctionalTesting.java @@ -0,0 +1,34 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Composite annotation for applying extensions needed for managing + * a StandardServiceRegistry as part of the test lifecycle. + * + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@ExtendWith( FailureExpectedExtension.class ) +@ExtendWith( ServiceRegistryExtension.class ) +@ExtendWith( ServiceRegistryParameterResolver.class ) +public @interface ServiceRegistryFunctionalTesting { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryParameterResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryParameterResolver.java new file mode 100644 index 000000000000..9a833928c5d7 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryParameterResolver.java @@ -0,0 +1,52 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.boot.registry.StandardServiceRegistry; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @author Steve Ebersole + */ +public class ServiceRegistryParameterResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return JUnitHelper.supportsParameterInjection( + parameterContext, + StandardServiceRegistry.class, + ServiceRegistryScope.class + ); + } + + @Override + public Object resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + final ServiceRegistryScope scope = ServiceRegistryExtension.findServiceRegistryScope( + extensionContext.getRequiredTestInstance(), + extensionContext + ); + + final Class paramType = parameterContext.getParameter().getType(); + if ( paramType.isAssignableFrom( ServiceRegistryScope.class ) ) { + return scope; + } + else if ( paramType.isAssignableFrom( StandardServiceRegistry.class ) ) { + return scope.getRegistry(); + } + + throw new IllegalStateException( + "Unexpected parameter type [" + paramType.getName() + "] for service-registry injection" + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryProducer.java new file mode 100644 index 000000000000..ff835136f7c8 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryProducer.java @@ -0,0 +1,20 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; + +/** + * @author Steve Ebersole + */ +public interface ServiceRegistryProducer { + StandardServiceRegistry produceServiceRegistry(StandardServiceRegistryBuilder builder); + + void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder bsrb); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScope.java new file mode 100644 index 000000000000..0379a170a237 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScope.java @@ -0,0 +1,55 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.service.Service; + +/** + * @author Steve Ebersole + */ +public interface ServiceRegistryScope { + /** + * Generalized support for running exception-safe code using a ServiceRegistry to + * ensure proper shutdown + */ + static void using(Supplier ssrProducer, Consumer action) { + try (final StandardServiceRegistry ssr = ssrProducer.get()) { + action.accept( () -> ssr ); + } + } + + StandardServiceRegistry getRegistry(); + + default void withService(Class role, Consumer action) { + assert role != null; + + final S service = getRegistry().getService( role ); + + if ( service == null ) { + throw new IllegalArgumentException( "Could not locate requested service - " + role.getName() ); + } + + action.accept( service ); + } + + default R fromService(Class role, Function action) { + assert role != null; + + final S service = getRegistry().getService( role ); + + if ( service == null ) { + throw new IllegalArgumentException( "Could not locate requested service - " + role.getName() ); + } + + return action.apply( service ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScopeAware.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScopeAware.java new file mode 100644 index 000000000000..a0ae2ec4c707 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScopeAware.java @@ -0,0 +1,14 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +/** + * @author Steve Ebersole + */ +public interface ServiceRegistryScopeAware { + void injectServiceRegistryScope(ServiceRegistryScope registryScope); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactory.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactory.java new file mode 100644 index 000000000000..70593594e1d3 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactory.java @@ -0,0 +1,59 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.Interceptor; +import org.hibernate.resource.jdbc.spi.StatementInspector; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @author Steve Ebersole + */ +@Inherited +@Target( ElementType.TYPE ) +@Retention( RetentionPolicy.RUNTIME ) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@ExtendWith( FailureExpectedExtension.class ) +@ExtendWith( ServiceRegistryExtension.class ) +@ExtendWith( ServiceRegistryParameterResolver.class ) + +@ExtendWith( DomainModelExtension.class ) +@ExtendWith( DomainModelParameterResolver.class ) + +@ExtendWith( SessionFactoryExtension.class ) +@ExtendWith( SessionFactoryParameterResolver.class ) +@ExtendWith( SessionFactoryScopeParameterResolver.class ) +public @interface SessionFactory { + String sessionFactoryName() default ""; + + boolean generateStatistics() default false; + boolean exportSchema() default true; + + boolean createSecondarySchemas() default false; + + Class interceptorClass() default Interceptor.class; + + Class statementInspectorClass() default StatementInspector.class; + + /** + * Short hand for {@code statementInspectorClass = org.hibernate.testing.jdbc.SQLStatementInspector.class} + * + * @see SQLStatementInspector + */ + boolean useCollectingStatementInspector() default false; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java new file mode 100644 index 000000000000..ed6b86897af0 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java @@ -0,0 +1,401 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.Interceptor; +import org.hibernate.SessionFactoryObserver; +import org.hibernate.StatelessSession; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.resource.jdbc.spi.StatementInspector; +import org.hibernate.tool.schema.Action; +import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator; +import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.ActionGrouping; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.transaction.TransactionUtil; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.platform.commons.support.AnnotationSupport; + +import org.jboss.logging.Logger; + +/** + * hibernate-testing implementation of a few JUnit5 contracts to support SessionFactory-based testing, + * including argument injection (or see {@link SessionFactoryScopeAware}) + * + * @see SessionFactoryScope + * @see DomainModelExtension + * + * @author Steve Ebersole + */ +public class SessionFactoryExtension + implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler { + + private static final Logger log = Logger.getLogger( SessionFactoryExtension.class ); + private static final String SESSION_FACTORY_KEY = SessionFactoryScope.class.getName(); + + private static ExtensionContext.Store locateExtensionStore(Object testInstance, ExtensionContext context) { + return JUnitHelper.locateExtensionStore( SessionFactoryExtension.class, context, testInstance ); + } + + public static SessionFactoryScope findSessionFactoryScope(Object testInstance, ExtensionContext context) { + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final SessionFactoryScope existing = (SessionFactoryScope) store.get( SESSION_FACTORY_KEY ); + if ( existing != null ) { + return existing; + } + + SessionFactoryProducer producer = null; + + final DomainModelScope domainModelScope = DomainModelExtension.findDomainModelScope( testInstance, context ); + + if ( testInstance instanceof SessionFactoryProducer ) { + producer = (SessionFactoryProducer) testInstance; + } + else if ( ! context.getElement().isPresent() ) { + throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() ); + } + else { + final Optional sfAnnWrappper = AnnotationSupport.findAnnotation( + context.getElement().get(), + SessionFactory.class + ); + + if ( sfAnnWrappper.isPresent() ) { + final SessionFactory sessionFactoryConfig = sfAnnWrappper.get(); + + producer = model -> { + try { + final SessionFactoryBuilder sessionFactoryBuilder = model.getSessionFactoryBuilder(); + if ( StringHelper.isNotEmpty( sessionFactoryConfig.sessionFactoryName() ) ) { + sessionFactoryBuilder.applyName( sessionFactoryConfig.sessionFactoryName() ); + } + + if ( sessionFactoryConfig.generateStatistics() ) { + sessionFactoryBuilder.applyStatisticsSupport( true ); + } + + if ( ! sessionFactoryConfig.interceptorClass().equals( Interceptor.class ) ) { + sessionFactoryBuilder.applyInterceptor( sessionFactoryConfig.interceptorClass().newInstance() ); + } + + final Class explicitInspectorClass = sessionFactoryConfig.statementInspectorClass(); + if ( sessionFactoryConfig.useCollectingStatementInspector() ) { + sessionFactoryBuilder.applyStatementInspector( new SQLStatementInspector() ); + } + else if ( ! explicitInspectorClass.equals( StatementInspector.class ) ) { + sessionFactoryBuilder.applyStatementInspector( explicitInspectorClass.getConstructor().newInstance() ); + } + + final SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor) sessionFactoryBuilder.build(); + + if ( sessionFactoryConfig.exportSchema() ) { + prepareSchemaExport( sessionFactory, model, sessionFactoryConfig.createSecondarySchemas() ); + } + + return sessionFactory; + } + catch (Exception e) { + throw new RuntimeException( "Could not build SessionFactory", e ); + } + }; + } + } + + if ( producer == null ) { + throw new IllegalStateException( "Could not determine SessionFactory producer" ); + } + + final SessionFactoryScopeImpl sfScope = new SessionFactoryScopeImpl( + domainModelScope, + producer + ); + + locateExtensionStore( testInstance, context ).put( SESSION_FACTORY_KEY, sfScope ); + + if ( testInstance instanceof SessionFactoryScopeAware ) { + ( (SessionFactoryScopeAware) testInstance ).injectSessionFactoryScope( sfScope ); + } + + return sfScope; + } + + private static void prepareSchemaExport( + SessionFactoryImplementor sessionFactory, + MetadataImplementor model, + boolean createSecondarySchemas) { + final Map baseProperties = sessionFactory.getProperties(); + + final ActionGrouping grouping = ActionGrouping.interpret( baseProperties ); + if ( grouping.getDatabaseAction() == Action.NONE && grouping.getScriptAction() == Action.NONE ) { + return; + } + + final HashMap settings = new HashMap<>( baseProperties ); + settings.put( AvailableSettings.HBM2DDL_DATABASE_ACTION, Action.CREATE_DROP ); + if ( createSecondarySchemas ) { + if ( !( model.getDatabase().getDialect().canCreateSchema() ) ) { + throw new UnsupportedOperationException( + model.getDatabase().getDialect() + " does not support schema creation" ); + } + settings.put( AvailableSettings.HBM2DDL_CREATE_SCHEMAS, true ); + } + + final StandardServiceRegistry serviceRegistry = model.getMetadataBuildingOptions().getServiceRegistry(); + + SchemaManagementToolCoordinator.process( + model, + serviceRegistry, + settings, + action -> sessionFactory.addObserver( + new SessionFactoryObserver() { + @Override + public void sessionFactoryClosing(org.hibernate.SessionFactory factory) { + action.perform( serviceRegistry ); + } + } + ) + ); + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + log.tracef( "#postProcessTestInstance(%s, %s)", testInstance, context.getDisplayName() ); + + findSessionFactoryScope( testInstance, context ); + } + + @Override + public void afterAll(ExtensionContext context) { + log.tracef( "#afterAll(%s)", context.getDisplayName() ); + + final Object testInstance = context.getRequiredTestInstance(); + + if ( testInstance instanceof SessionFactoryScopeAware ) { + ( (SessionFactoryScopeAware) testInstance ).injectSessionFactoryScope( null ); + } + + final SessionFactoryScopeImpl removed = (SessionFactoryScopeImpl) locateExtensionStore( testInstance, context ).remove( SESSION_FACTORY_KEY ); + if ( removed != null ) { + removed.close(); + } + + if ( testInstance instanceof SessionFactoryScopeAware ) { + ( (SessionFactoryScopeAware) testInstance ).injectSessionFactoryScope( null ); + } + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + log.tracef( "#handleTestExecutionException(%s, %s)", context.getDisplayName(), throwable ); + + try { + final Object testInstance = context.getRequiredTestInstance(); + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final SessionFactoryScopeImpl scope = (SessionFactoryScopeImpl) store.get( SESSION_FACTORY_KEY ); + scope.releaseSessionFactory(); + } + catch (Exception ignore) { + } + + throw throwable; + } + + private static class SessionFactoryScopeImpl implements SessionFactoryScope, ExtensionContext.Store.CloseableResource { + private final DomainModelScope modelScope; + private final SessionFactoryProducer producer; + + private SessionFactoryImplementor sessionFactory; + private boolean active = true; + + private SessionFactoryScopeImpl( + DomainModelScope modelScope, + SessionFactoryProducer producer) { + this.modelScope = modelScope; + this.producer = producer; + } + + @Override + public SessionFactoryImplementor getSessionFactory() { + if ( sessionFactory == null ) { + sessionFactory = createSessionFactory(); + } + + return sessionFactory; + } + + @Override + public MetadataImplementor getMetadataImplementor() { + return modelScope.getDomainModel(); + } + + @Override + public StatementInspector getStatementInspector() { + return getSessionFactory().getSessionFactoryOptions().getStatementInspector(); + } + + @Override + public T getStatementInspector(Class type) { + //noinspection unchecked + return (T) getStatementInspector(); + } + + @Override + public SQLStatementInspector getCollectingStatementInspector() { + return getStatementInspector( SQLStatementInspector.class ); + } + + @Override + public void close() { + if ( !active ) { + return; + } + + log.debug( "Closing SessionFactoryScope" ); + + active = false; + releaseSessionFactory(); + } + + public void releaseSessionFactory() { + if ( sessionFactory != null ) { + log.debug( "Releasing SessionFactory" ); + + try { + sessionFactory.close(); + } + catch (Exception e) { + log.warn( "Error closing SF", e ); + } + finally { + sessionFactory = null; + } + } + } + + private SessionFactoryImplementor createSessionFactory() { + if ( !active ) { + throw new IllegalStateException( "SessionFactoryScope is no longer active" ); + } + + log.debug( "Creating SessionFactory" ); + + return producer.produceSessionFactory( modelScope.getDomainModel() ); + } + + public void inSession(Consumer action) { + log.trace( "#inSession(Consumer)" ); + + try (SessionImplementor session = (SessionImplementor) getSessionFactory().openSession()) { + log.trace( "Session opened, calling action" ); + action.accept( session ); + } + finally { + log.trace( "Session close - auto-close block" ); + } + } + + @Override + public T fromSession(Function action) { + log.trace( "#fromSession(Function)" ); + + try (SessionImplementor session = (SessionImplementor) getSessionFactory().openSession()) { + log.trace( "Session opened, calling action" ); + return action.apply( session ); + } + finally { + log.trace( "Session close - auto-close block" ); + } + } + + @Override + public void inTransaction(Consumer action) { + log.trace( "#inTransaction(Consumer)" ); + + try (SessionImplementor session = (SessionImplementor) getSessionFactory().openSession()) { + log.trace( "Session opened, calling action" ); + inTransaction( session, action ); + } + finally { + log.trace( "Session close - auto-close block" ); + } + } + + @Override + public T fromTransaction(Function action) { + log.trace( "#fromTransaction(Function)" ); + + try (SessionImplementor session = (SessionImplementor) getSessionFactory().openSession()) { + log.trace( "Session opened, calling action" ); + return fromTransaction( session, action ); + } + finally { + log.trace( "Session close - auto-close block" ); + } + } + + @Override + public void inTransaction(SessionImplementor session, Consumer action) { + log.trace( "inTransaction(Session,Consumer)" ); + TransactionUtil.inTransaction( session, action ); + } + + @Override + public T fromTransaction(SessionImplementor session, Function action) { + log.trace( "fromTransaction(Session,Function)" ); + return TransactionUtil.fromTransaction( session, action ); + } + + @Override + public void inStatelessSession(Consumer action) { + log.trace( "#inStatelessSession(Consumer)" ); + + try ( final StatelessSession statelessSession = getSessionFactory().openStatelessSession() ) { + log.trace( "StatelessSession opened, calling action" ); + action.accept( statelessSession ); + } + finally { + log.trace( "StatelessSession close - auto-close block" ); + } + } + + @Override + public void inStatelessTransaction(Consumer action) { + log.trace( "#inStatelessTransaction(Consumer)" ); + + try ( final StatelessSession statelessSession = getSessionFactory().openStatelessSession() ) { + log.trace( "StatelessSession opened, calling action" ); + inStatelessTransaction( statelessSession, action ); + } + finally { + log.trace( "StatelessSession close - auto-close block" ); + } + } + + @Override + public void inStatelessTransaction(StatelessSession session, Consumer action) { + log.trace( "inStatelessTransaction(StatelessSession,Consumer)" ); + + TransactionUtil.inTransaction( session, action ); + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryFunctionalTesting.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryFunctionalTesting.java new file mode 100644 index 000000000000..138ad4af1dfc --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryFunctionalTesting.java @@ -0,0 +1,46 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Composite annotation for functional tests that require a functioning SessionFactory. + * + * @apiNote Applies support for SessionFactory-based testing. Up to the test to define + * configuration (via {@link ServiceRegistry}), mappings (via {@link DomainModel}) and/or + * SessionFactory-options (via {@link SessionFactory}). Rather than using these other + * annotations, tests could just implement building those individual pieces via + * {@link ServiceRegistryProducer}, {@link DomainModelProducer} and/or {@link SessionFactoryProducer} + * instead. + * + * @see SessionFactoryExtension + * @see DialectFilterExtension + * @see FailureExpectedExtension + * + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@DomainModelFunctionalTesting +@ExtendWith( FailureExpectedExtension.class ) + + +@ExtendWith( SessionFactoryExtension.class ) +public @interface SessionFactoryFunctionalTesting { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryParameterResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryParameterResolver.java new file mode 100644 index 000000000000..6ee64636610d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryParameterResolver.java @@ -0,0 +1,33 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.engine.spi.SessionFactoryImplementor; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @author Steve Ebersole + */ +public class SessionFactoryParameterResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return JUnitHelper.supportsParameterInjection( parameterContext, SessionFactoryImplementor.class ); + } + + @Override + public Object resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return SessionFactoryExtension.findSessionFactoryScope( extensionContext.getRequiredTestInstance(), extensionContext ).getSessionFactory(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryProducer.java new file mode 100644 index 000000000000..9a6d16c8cf7e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryProducer.java @@ -0,0 +1,30 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.engine.spi.SessionFactoryImplementor; + +/** + * Contract for something that can build a SessionFactory. + * + * Used by SessionFactoryScopeExtension to create the + * SessionFactoryScope. + * + * Generally speaking, a test class would implement SessionFactoryScopeContainer + * and return the SessionFactoryProducer to be used for those tests. + * The SessionFactoryProducer is then used to build the SessionFactoryScope + * which is injected back into the SessionFactoryScopeContainer + * + * @see SessionFactoryExtension + * @see SessionFactoryScope + * + * @author Steve Ebersole + */ +public interface SessionFactoryProducer { + SessionFactoryImplementor produceSessionFactory(MetadataImplementor model); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScope.java new file mode 100644 index 000000000000..a3ef58448bbf --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScope.java @@ -0,0 +1,41 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.StatelessSession; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.resource.jdbc.spi.StatementInspector; + +import org.hibernate.testing.jdbc.SQLStatementInspector; + +/** + * @author Steve Ebersole + */ +public interface SessionFactoryScope { + SessionFactoryImplementor getSessionFactory(); + MetadataImplementor getMetadataImplementor(); + StatementInspector getStatementInspector(); + T getStatementInspector(Class type); + SQLStatementInspector getCollectingStatementInspector(); + + void inSession(Consumer action); + void inTransaction(Consumer action); + void inTransaction(SessionImplementor session, Consumer action); + + T fromSession(Function action); + T fromTransaction(Function action); + T fromTransaction(SessionImplementor session, Function action); + + void inStatelessSession(Consumer action); + void inStatelessTransaction(Consumer action); + void inStatelessTransaction(StatelessSession session, Consumer action); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeAware.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeAware.java new file mode 100644 index 000000000000..6ac1dad4a372 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeAware.java @@ -0,0 +1,17 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +/** + * @author Steve Ebersole + */ +public interface SessionFactoryScopeAware { + /** + * Callback to inject the SessionFactoryScope into the container + */ + void injectSessionFactoryScope(SessionFactoryScope scope); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeParameterResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeParameterResolver.java new file mode 100644 index 000000000000..c7f4bd2486b3 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeParameterResolver.java @@ -0,0 +1,31 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @author Steve Ebersole + */ +public class SessionFactoryScopeParameterResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return JUnitHelper.supportsParameterInjection( parameterContext, SessionFactoryScope.class ); + } + + @Override + public Object resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return SessionFactoryExtension.findSessionFactoryScope( extensionContext.getRequiredTestInstance(), extensionContext ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Setting.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Setting.java new file mode 100644 index 000000000000..76f5f0482234 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Setting.java @@ -0,0 +1,22 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +/** + * A setting for use in other annotations to define settings for various things. + */ +public @interface Setting { + /** + * The setting name. Often a constant from {@link org.hibernate.cfg.AvailableSettings} + */ + String name(); + + /** + * The setting value + */ + String value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SettingProvider.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SettingProvider.java new file mode 100644 index 000000000000..51c2befaefca --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SettingProvider.java @@ -0,0 +1,19 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +/** + * @author Steve Ebersole + */ +public @interface SettingProvider { + interface Provider { + S getSetting(); + } + + String settingName(); + Class> provider(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialect.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialect.java new file mode 100644 index 000000000000..736de3150b66 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialect.java @@ -0,0 +1,46 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.dialect.Dialect; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Indicates that the annotated test class/method should be skipped + * when the indicated Dialect is being used. + * + * It is a repeatable annotation + * + * @see SkipForDialectGroup + * + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Repeatable( SkipForDialectGroup.class ) + +@ExtendWith( DialectFilterExtension.class ) +public @interface SkipForDialect { + Class dialectClass(); + boolean matchSubTypes() default false; + String reason() default ""; + + int majorVersion() default -1; + + int minorVersion() default -1; + + int microVersion() default -1; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialectGroup.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialectGroup.java new file mode 100644 index 000000000000..9de623d1bfa4 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialectGroup.java @@ -0,0 +1,29 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Grouping annotation for {@link SkipForDialect} + * + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.METHOD}) + +@ExtendWith( DialectFilterExtension.class ) +public @interface SkipForDialectGroup { + SkipForDialect[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/TestingUtil.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/TestingUtil.java new file mode 100644 index 000000000000..fd464d82ba3c --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/TestingUtil.java @@ -0,0 +1,99 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.support.AnnotationSupport; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +public class TestingUtil { + + private TestingUtil() { + } + + public static Optional findEffectiveAnnotation( + ExtensionContext context, + Class annotationType) { + if ( !context.getElement().isPresent() ) { + return Optional.empty(); + } + + final AnnotatedElement annotatedElement = context.getElement().get(); + + final Optional direct = AnnotationSupport.findAnnotation( annotatedElement, annotationType ); + if ( direct.isPresent() ) { + return direct; + } + + if ( context.getTestInstance().isPresent() ) { + return AnnotationSupport.findAnnotation( context.getRequiredTestInstance().getClass(), annotationType ); + } + + return Optional.empty(); + } + + public static List findEffectiveRepeatingAnnotation( + ExtensionContext context, + Class annotationType, + Class groupAnnotationType) { + if ( !context.getElement().isPresent() ) { + return Collections.emptyList(); + } + + final Optional effectiveAnnotation = findEffectiveAnnotation( context, annotationType ); + final Optional effectiveGroupingAnnotation = findEffectiveAnnotation( + context, + groupAnnotationType + ); + + if ( effectiveAnnotation.isPresent() || effectiveGroupingAnnotation.isPresent() ) { + if ( !effectiveGroupingAnnotation.isPresent() ) { + return Collections.singletonList( effectiveAnnotation.get() ); + } + + final List list = new ArrayList<>(); + effectiveAnnotation.ifPresent( list::add ); + + final Method valueMethod; + try { + valueMethod = groupAnnotationType.getDeclaredMethod( "value", null ); + + Collections.addAll( list, (A[]) valueMethod.invoke( effectiveGroupingAnnotation.get() ) ); + } + catch (Exception e) { + throw new RuntimeException( "Could not locate repeated/grouped annotations", e ); + } + + return list; + } + + return Collections.emptyList(); + } + + public static boolean hasEffectiveAnnotation(ExtensionContext context, Class annotationType) { + return findEffectiveAnnotation( context, annotationType ).isPresent(); + } + + @SuppressWarnings("unchecked") + public static T cast(Object thing, Class type) { + assertThat( thing, instanceOf( type ) ); + return type.cast( thing ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/transaction/TransactionUtil.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/transaction/TransactionUtil.java new file mode 100644 index 000000000000..42f04e44e610 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/transaction/TransactionUtil.java @@ -0,0 +1,147 @@ +/* + * 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 + */ +package org.hibernate.testing.orm.transaction; + +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.SharedSessionContract; +import org.hibernate.StatelessSession; +import org.hibernate.Transaction; +import org.hibernate.engine.spi.SessionImplementor; + +import org.jboss.logging.Logger; + +import javax.persistence.EntityManager; + +public abstract class TransactionUtil { + private static final Logger log = Logger.getLogger( TransactionUtil.class ); + + public static void inTransaction(SessionImplementor session, Consumer action) { + wrapInTransaction( session, session, action ); + } + + public static void inTransaction(EntityManager entityManager, Consumer action) { + wrapInTransaction( (SharedSessionContract) entityManager, entityManager, action ); + } + + public static void inTransaction(StatelessSession session, Consumer action) { + wrapInTransaction( session, session, action ); + } + + public static R fromTransaction(SessionImplementor session, Function action) { + return wrapInTransaction( session, session, action ); + } + + public static R fromTransaction(EntityManager entityManager, Function action) { + return wrapInTransaction( (SharedSessionContract) entityManager, entityManager, action ); + } + + private static void wrapInTransaction(SharedSessionContract session, T actionInput, Consumer action) { + final Transaction txn = session.beginTransaction(); + log.trace( "Started transaction" ); + + try { + log.trace( "Calling action in txn" ); + action.accept( actionInput ); + log.trace( "Called action - in txn" ); + + if ( !txn.getRollbackOnly() ) { + log.trace( "Committing transaction" ); + txn.commit(); + log.trace( "Committed transaction" ); + } + else { + try { + log.trace( "Rollback transaction marked for rollback only" ); + txn.rollback(); + } + catch (Exception e) { + log.error( "Rollback failure", e ); + } + } + } + catch (Exception e) { + log.tracef( + "Error calling action: %s (%s) - rolling back", + e.getClass().getName(), + e.getMessage() + ); + try { + txn.rollback(); + } + catch (Exception ignore) { + log.trace( "Was unable to roll back transaction" ); + // really nothing else we can do here - the attempt to + // rollback already failed and there is nothing else + // to clean up. + } + + throw e; + } + catch (AssertionError t) { + try { + txn.rollback(); + } + catch (Exception ignore) { + log.trace( "Was unable to roll back transaction" ); + // really nothing else we can do here - the attempt to + // rollback already failed and there is nothing else + // to clean up. + } + throw t; + } + } + + + private static R wrapInTransaction(SharedSessionContract session, T actionInput, Function action) { + log.trace( "Started transaction" ); + Transaction txn = session.beginTransaction(); + try { + log.trace( "Calling action in txn" ); + final R result = action.apply( actionInput ); + log.trace( "Called action - in txn" ); + + log.trace( "Committing transaction" ); + txn.commit(); + log.trace( "Committed transaction" ); + + return result; + } + catch (Exception e) { + log.tracef( + "Error calling action: %s (%s) - rolling back", + e.getClass().getName(), + e.getMessage() + ); + try { + txn.rollback(); + } + catch (Exception ignore) { + log.trace( "Was unable to roll back transaction" ); + // really nothing else we can do here - the attempt to + // rollback already failed and there is nothing else + // to clean up. + } + + throw e; + } + catch (AssertionError t) { + try { + txn.rollback(); + } + catch (Exception ignore) { + log.trace( "Was unable to roll back transaction" ); + // really nothing else we can do here - the attempt to + // rollback already failed and there is nothing else + // to clean up. + } + throw t; + } + } + +} From 82815cc09a0c68ddeb1545fdcb1a0872b12de77c Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Mon, 14 Feb 2022 08:15:48 -0600 Subject: [PATCH 010/201] back-port JUnit5 based testing support --- .../orm/junit/EntityManagerFactoryExtension.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryExtension.java index 2e2a01541c29..604561f62dc2 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryExtension.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryExtension.java @@ -11,21 +11,15 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.Set; +import javax.persistence.spi.PersistenceUnitInfo; -import org.hibernate.SessionFactoryObserver; -import org.hibernate.boot.registry.StandardServiceRegistry; -import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Environment; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor; import org.hibernate.jpa.boot.spi.Bootstrap; import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; import org.hibernate.tool.schema.Action; -import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator; -import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.ActionGrouping; import org.hibernate.testing.jdbc.SharedDriverManagerConnectionProviderImpl; import org.hibernate.testing.orm.domain.DomainModelDescriptor; @@ -39,8 +33,6 @@ import org.jboss.logging.Logger; -import javax.persistence.spi.PersistenceUnitInfo; - /** * hibernate-testing implementation of a few JUnit5 contracts to support SessionFactory-based testing, * including argument injection (or see {@link SessionFactoryScopeAware}) @@ -89,7 +81,6 @@ public static EntityManagerFactoryScope findEntityManagerFactoryScope( // JpaCompliance pui.getProperties().put( AvailableSettings.JPA_QUERY_COMPLIANCE, emfAnn.queryComplianceEnabled() ); pui.getProperties().put( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, emfAnn.transactionComplianceEnabled() ); - pui.getProperties().put( AvailableSettings.JPA_LIST_COMPLIANCE, emfAnn.listMappingComplianceEnabled() ); pui.getProperties().put( AvailableSettings.JPA_CLOSED_COMPLIANCE, emfAnn.closedComplianceEnabled() ); pui.getProperties().put( AvailableSettings.JPA_PROXY_COMPLIANCE, emfAnn.proxyComplianceEnabled() ); pui.getProperties().put( AvailableSettings.JPA_CACHING_COMPLIANCE, emfAnn.cacheComplianceEnabled() ); From 582faaa2aca04e6f6563024c01e29f471a0e47cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 17 Feb 2022 14:49:55 +0100 Subject: [PATCH 011/201] HHH-15082 Abort JDBC batches on runtime exceptions as well as SQLException --- .../engine/jdbc/batch/internal/BatchingBatch.java | 4 ++++ .../engine/jdbc/batch/internal/NonBatchingBatch.java | 2 +- .../persister/entity/AbstractEntityPersister.java | 8 ++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java index c63f5f1aa651..79d7ac030e5f 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java @@ -82,6 +82,10 @@ public void addToBatch() { LOG.debugf( "SQLException escaped proxy", e ); throw sqlExceptionHelper().convert( e, "could not perform addBatch", currentStatementSql ); } + catch (RuntimeException e) { + abortBatch(); + throw e; + } statementPosition++; if ( statementPosition >= getKey().getBatchedStatementCount() ) { batchPosition++; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java index 224866b295bb..2e3f4fcff7b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java @@ -52,7 +52,7 @@ public void addToBatch() { abortBatch(); throw sqlExceptionHelper().convert( e, "could not execute non-batched batch statement", statementSQL ); } - catch (JDBCException e) { + catch (RuntimeException e) { abortBatch(); throw e; } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 366450c3048b..e3132353276a 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -3382,7 +3382,7 @@ public void insert( ); } } - catch (SQLException | JDBCException e) { + catch (SQLException | RuntimeException e) { if ( useBatch ) { session.getJdbcCoordinator().abortBatch(); } @@ -3583,7 +3583,7 @@ else if ( isAllOrDirtyOptLocking() && oldFields != null ) { } } - catch (SQLException e) { + catch (SQLException | RuntimeException e) { if ( useBatch ) { session.getJdbcCoordinator().abortBatch(); } @@ -3709,11 +3709,11 @@ else if ( isAllOrDirtyOptLocking() && loadedState != null ) { } } - catch (SQLException sqle) { + catch (SQLException | RuntimeException e) { if ( useBatch ) { session.getJdbcCoordinator().abortBatch(); } - throw sqle; + throw e; } finally { if ( !useBatch ) { From 8f5c0b76103209c59a1ad4886f94e2820fb1ba40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 17 Feb 2022 15:58:17 +0100 Subject: [PATCH 012/201] HHH-15082 Test that batch statements are aborted if a RuntimeException is thrown by Batch#addToBatch Such an exception can be thrown if an expectation (org.hibernate.jdbc.Expectation) is not met, for example if an update statement for a given entity affects 0 rows (e.g. because of a concurrent update). --- .../batch/FailingAddToBatchTest.java | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/FailingAddToBatchTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/FailingAddToBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/FailingAddToBatchTest.java new file mode 100644 index 000000000000..068d9e280858 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/FailingAddToBatchTest.java @@ -0,0 +1,213 @@ +/* + * 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 + */ +package org.hibernate.jpa.test.transaction.batch; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.junit.jupiter.api.Assertions.fail; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator; +import org.hibernate.engine.jdbc.batch.internal.BatchingBatch; +import org.hibernate.engine.jdbc.batch.spi.Batch; +import org.hibernate.engine.jdbc.batch.spi.BatchKey; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.testing.orm.junit.Setting; +import org.hibernate.testing.orm.junit.SettingProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@TestForIssue(jiraKey = "HHH-15082") +@Jpa( + annotatedClasses = { + FailingAddToBatchTest.MyEntity.class + }, + integrationSettings = { + @Setting(name = AvailableSettings.STATEMENT_BATCH_SIZE, value = "50") + }, + settingProviders = { + @SettingProvider( + settingName = BatchBuilderInitiator.BUILDER, + provider = FailingAddToBatchTest.BatchBuilderSettingProvider.class + ) + } +) +public class FailingAddToBatchTest { + + private static TestBatch testBatch; + + @BeforeEach + public void setup() { + TestBatch.nextAddToBatchFailure.set( null ); + } + + @Test + public void testInsert(EntityManagerFactoryScope scope) { + RuntimeException simulatedAddToBatchFailure = new RuntimeException( "Simulated RuntimeException" ); + + scope.inTransaction( em -> { + assertThatThrownBy( () -> { + MyEntity entity = new MyEntity(); + entity.setText( "initial" ); + TestBatch.nextAddToBatchFailure.set( simulatedAddToBatchFailure ); + em.persist( entity ); + em.flush(); + } ) + .isSameAs( simulatedAddToBatchFailure ); + + assertAllStatementsAreClosed( testBatch.createdStatements ); + } ); + } + + @Test + public void testUpdate(EntityManagerFactoryScope scope) { + Long id = scope.fromTransaction( em -> { + MyEntity entity = new MyEntity(); + entity.setText( "initial" ); + em.persist( entity ); + return entity.getId(); + } ); + + RuntimeException simulatedAddToBatchFailure = new RuntimeException( "Simulated RuntimeException" ); + + scope.inTransaction( em -> { + assertThatThrownBy( () -> { + MyEntity entity = em.find( MyEntity.class, id ); + TestBatch.nextAddToBatchFailure.set( simulatedAddToBatchFailure ); + entity.setText( "updated" ); + em.flush(); + } ) + .isSameAs( simulatedAddToBatchFailure ); + + assertAllStatementsAreClosed( testBatch.createdStatements ); + } ); + } + + @Test + public void testRemove(EntityManagerFactoryScope scope) { + Long id = scope.fromTransaction( em -> { + MyEntity entity = new MyEntity(); + entity.setText( "initial" ); + em.persist( entity ); + return entity.getId(); + } ); + + RuntimeException simulatedAddToBatchFailure = new RuntimeException( "Simulated RuntimeException" ); + + scope.inTransaction( em -> { + assertThatThrownBy( () -> { + MyEntity entity = em.find( MyEntity.class, id ); + TestBatch.nextAddToBatchFailure.set( simulatedAddToBatchFailure ); + em.remove( entity ); + em.flush(); + } ) + .isSameAs( simulatedAddToBatchFailure ); + + assertAllStatementsAreClosed( testBatch.createdStatements ); + } ); + } + + protected void assertAllStatementsAreClosed(List statements) { + statements.forEach( statement -> { + try { + assertThat( "A PreparedStatement has not been closed", statement.isClosed(), is( true ) ); + } + catch (SQLException e) { + fail( e.getMessage() ); + } + } ); + } + + @Entity(name = "MyEntity") + public static class MyEntity { + @Id + @GeneratedValue + private Long id; + private String text; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } + + public static class BatchBuilderSettingProvider implements SettingProvider.Provider { + @Override + public String getSetting() { + return TestBatchBuilder.class.getName(); + } + } + + public static class TestBatch extends BatchingBatch { + private static final AtomicReference nextAddToBatchFailure = new AtomicReference<>(); + + private final List createdStatements = new ArrayList<>(); + + public TestBatch(BatchKey key, JdbcCoordinator jdbcCoordinator, int batchSize) { + super( key, jdbcCoordinator, batchSize ); + } + + @Override + public void addToBatch() { + RuntimeException failure = nextAddToBatchFailure.getAndSet( null ); + if ( failure != null ) { + throw failure; + // Implementations really should call abortBatch() before propagating an exception. + // Purposely skipping the call to abortBatch() to ensure that Hibernate works properly when + // an implementation does not call abortBatch(). + } + super.addToBatch(); + } + + @Override + public PreparedStatement getBatchStatement(String sql, boolean callable) { + PreparedStatement batchStatement = super.getBatchStatement( sql, callable ); + createdStatements.add( batchStatement ); + return batchStatement; + } + } + + public static class TestBatchBuilder extends BatchBuilderImpl { + + @Override + public Batch buildBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) { + return buildBatchTest( key, jdbcCoordinator, getJdbcBatchSize() ); + } + + protected BatchingBatch buildBatchTest(BatchKey key, JdbcCoordinator jdbcCoordinator, int jdbcBatchSize) { + testBatch = new TestBatch( key, jdbcCoordinator, jdbcBatchSize ); + return testBatch; + } + } +} From 5febc7013442c7cdc614a064a4e9a27f9524fc5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 17 Feb 2022 14:52:40 +0100 Subject: [PATCH 013/201] HHH-15082 Correctly propagate the original exception when aborting a JDBC batch fails Not strictly necessary, but it's related to these changes and I think it's a good idea. --- .../engine/jdbc/batch/internal/AbstractBatchImpl.java | 9 +++++++-- .../engine/jdbc/batch/internal/BatchingBatch.java | 8 ++++---- .../engine/jdbc/batch/internal/NonBatchingBatch.java | 4 ++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java index bef49a41eee7..c9d5714e2c97 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/AbstractBatchImpl.java @@ -93,8 +93,13 @@ protected SqlStatementLogger sqlStatementLogger() { return sqlStatementLogger; } - protected void abortBatch() { - jdbcCoordinator.abortBatch(); + protected void abortBatch(Exception cause) { + try { + jdbcCoordinator.abortBatch(); + } + catch (RuntimeException e) { + cause.addSuppressed( e ); + } } /** diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java index 79d7ac030e5f..46cd7f6c6a8f 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java @@ -78,12 +78,12 @@ public void addToBatch() { currentStatement.addBatch(); } catch ( SQLException e ) { - abortBatch(); + abortBatch( e ); LOG.debugf( "SQLException escaped proxy", e ); throw sqlExceptionHelper().convert( e, "could not perform addBatch", currentStatementSql ); } catch (RuntimeException e) { - abortBatch(); + abortBatch( e ); throw e; } statementPosition++; @@ -130,12 +130,12 @@ private void performExecution() { checkRowCounts( rowCounts, statement, sql ); } catch ( SQLException e ) { - abortBatch(); + abortBatch( e ); LOG.unableToExecuteBatch( e, sql ); throw sqlExceptionHelper().convert( e, "could not execute batch", sql ); } catch ( RuntimeException re ) { - abortBatch(); + abortBatch( re ); LOG.unableToExecuteBatch( re, sql ); throw re; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java index 2e3f4fcff7b6..8385ea0384b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/batch/internal/NonBatchingBatch.java @@ -49,11 +49,11 @@ public void addToBatch() { jdbcCoordinator.afterStatementExecution(); } catch ( SQLException e ) { - abortBatch(); + abortBatch( e ); throw sqlExceptionHelper().convert( e, "could not execute non-batched batch statement", statementSQL ); } catch (RuntimeException e) { - abortBatch(); + abortBatch( e ); throw e; } } From 1f84125e44a0de5c322fa3468d84ddced1351f3c Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 3 Feb 2022 14:14:40 -0600 Subject: [PATCH 014/201] HHH-15060 - Associations with @NotFound should always be left joined when de-referenced in HQL/Criteria - `@NotFound` no longer exports a physical foreign-key --- .../org/hibernate/cfg/AnnotationBinder.java | 18 ++++++++++++++---- .../notfound/NotFoundLogicalOneToOneTest.java | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index 3881e9912105..db8e15eb6a18 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -3462,10 +3462,20 @@ public static void bindForeignKeyNameAndDefinition( JoinColumns joinColumns, MetadataBuildingContext context) { final boolean noConstraintByDefault = context.getBuildingOptions().isNoConstraintByDefault(); - if ( ( joinColumn != null && ( joinColumn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT - || joinColumn.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) - || ( joinColumns != null && ( joinColumns.foreignKey().value() == ConstraintMode.NO_CONSTRAINT - || joinColumns.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) ) { + + final NotFound notFoundAnn= property.getAnnotation( NotFound.class ); + if ( notFoundAnn != null ) { + // supersedes all others + value.setForeignKeyName( "none" ); + } + else if ( joinColumn != null && ( + joinColumn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT + || ( joinColumn.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) ) { + value.setForeignKeyName( "none" ); + } + else if ( joinColumns != null && ( + joinColumns.foreignKey().value() == ConstraintMode.NO_CONSTRAINT + || ( joinColumns.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) ) { value.setForeignKeyName( "none" ); } else { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/NotFoundLogicalOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/NotFoundLogicalOneToOneTest.java index c940b3faecc6..8352fd95e8d4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/NotFoundLogicalOneToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/notfound/NotFoundLogicalOneToOneTest.java @@ -91,7 +91,7 @@ public void setName(String name) { } @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) +// @JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) @NotFound(action = NotFoundAction.IGNORE) public Currency getCurrency() { return currency; From a297a62432b19f174c26fae680f59f0162d9398e Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Fri, 4 Feb 2022 12:32:32 -0600 Subject: [PATCH 015/201] HHH-15060 - Associations with @NotFound should always be left joined when de-referenced in HQL/Criteria - `@NotFound` no longer exports a physical foreign-key - tests showing bugs and inconsistencies wrt `@NotFound` handling --- .../NotFoundExceptionLogicalOneToOneTest.java | 221 ++++++++++++++ .../NotFoundExceptionManyToOneTest.java | 281 ++++++++++++++++++ .../ignore/NotFoundIgnoreManyToOneTest.java | 215 ++++++++++++++ .../ignore/NotFoundIgnoreOneToOneTest.java | 210 +++++++++++++ 4 files changed, 927 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java new file mode 100644 index 000000000000..333c9bbc519c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java @@ -0,0 +1,221 @@ +/* + * 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 . + */ +package org.hibernate.orm.test.notfound.exception; + +import java.io.Serializable; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToOne; + +import org.hibernate.Hibernate; +import org.hibernate.ObjectNotFoundException; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import org.assertj.core.api.Assertions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +/** + * Tests for `@OneToOne @NotFound(EXCEPTION)` + * + * NOTES:
    + *
  1. `@NotFound` should force the association to be eager - `Coin#currency` should be loaded immediately
  2. + *
  3. When loading the `Coin#currency`, `EXCEPTION` should trigger an exception since the particular `Coin#currency` fk is broken
  4. + *
+ * + * @author Steve Ebersole + */ +public class NotFoundExceptionLogicalOneToOneTest extends BaseCoreFunctionalTestCase { + @Test + public void testProxy() { + inTransaction( (session) -> { + // the non-existent Child + final Currency proxy = session.byId( Currency.class ).getReference( 1 ); + try { + Hibernate.initialize( proxy ); + Assertions.fail( "Expecting ObjectNotFoundException" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).endsWith( "Currency" ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + } + + @Test + @FailureExpected( + jiraKey = "HHH-15060", + message = "We return a proxy here for `Coin#currency`, which violates NOTE #1. The exception happens " + + "when we reference the proxy, but thats not correct" + ) + public void testGet() { + inTransaction( (session) -> { + try { + session.get( Coin.class, 1 ); + fail( "Expecting ObjectNotFoundException" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + } + + @Test + @FailureExpected( + jiraKey = "HHH-15060", + message = "We return a proxy here for `Coin#currency`, which violates NOTE #1. The exception happens " + + "when we reference the proxy, but thats not correct" + ) + public void testQueryImplicitPathDereferencePredicate() { + inTransaction( (session) -> { + final String hql = "select c from Coin c where c.currency.id = 1"; + try { + session.createQuery( hql, Coin.class ).getResultList(); + fail( "Expecting ObjectNotFoundException for broken fk" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + } + + @Test + @FailureExpected( + jiraKey = "HHH-15060", + message = "We return a proxy here for `Coin#currency`, which violates NOTE #1. The exception happens " + + "when we reference the proxy, but thats not correct" + ) + public void testQueryOwnerSelection() { + inTransaction( (session) -> { + final String hql = "select c from Coin c"; + try { + session.createQuery( hql, Coin.class ).getResultList(); + fail( "Expecting ObjectNotFoundException for broken fk" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Coin.class, Currency.class }; + } + + @Override + protected void prepareTest() throws Exception { + super.prepareTest(); + + inTransaction( (session) -> { + Currency euro = new Currency( 1, "Euro" ); + Coin fiveC = new Coin( 1, "Five cents", euro ); + + session.persist( euro ); + session.persist( fiveC ); + } ); + + inTransaction( (session) -> { + session.createQuery( "delete Currency where id = 1" ).executeUpdate(); + } ); + } + + @Override + protected void cleanupTest() throws Exception { + inTransaction( (session) -> { + session.createQuery( "delete Coin where id = 1" ).executeUpdate(); + } ); + + super.cleanupTest(); + } + + @Entity(name = "Coin") + public static class Coin { + private Integer id; + private String name; + private Currency currency; + + public Coin() { + } + + public Coin(Integer id, String name, Currency currency) { + this.id = id; + this.name = name; + this.currency = currency; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToOne(fetch = FetchType.LAZY) + @NotFound(action = NotFoundAction.EXCEPTION) + public Currency getCurrency() { + return currency; + } + + public void setCurrency(Currency currency) { + this.currency = currency; + } + } + + @Entity(name = "Currency") + public static class Currency implements Serializable { + private Integer id; + private String name; + + public Currency() { + } + + public Currency(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java new file mode 100644 index 000000000000..bf711229368f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java @@ -0,0 +1,281 @@ +/* + * 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 . + */ +package org.hibernate.orm.test.notfound.exception; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Hibernate; +import org.hibernate.ObjectNotFoundException; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.resource.jdbc.spi.StatementInspector; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import org.assertj.core.api.Assertions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +/** + * Tests for `@ManyToOne @NotFound(EXCEPTION)` + * + * NOTES:
    + *
  1. `@NotFound` should force the association to be eager - `Coin#currency` should be loaded immediately
  2. + *
  3. `EXCEPTION` should trigger an exception since the particular `Coin#currency` fk is broken
  4. + *
+ * + * @author Steve Ebersole + */ +public class NotFoundExceptionManyToOneTest extends BaseCoreFunctionalTestCase { + + @Test + public void testProxy() { + inTransaction( (session) -> { + // the non-existent Child + final Currency proxy = session.byId( Currency.class ).getReference( 1 ); + try { + Hibernate.initialize( proxy ); + Assertions.fail( "Expecting ObjectNotFoundException" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).endsWith( "Currency" ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + } + + @Test + @FailureExpected( + jiraKey = "HHH-15060", + message = "ObjectNotFoundException is thrown but caught and null is returned - see " + + "org.hibernate.internal.SessionImpl.IdentifierLoadAccessImpl#doLoad" + ) + public void testGet() { + inTransaction( (session) -> { + try { + final Coin coin = session.get( Coin.class, 1 ); + coin.getCurrency().getName(); + fail( "Expecting ObjectNotFoundException for broken fk" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + } + + @Test + @FailureExpected( + jiraKey = "HHH-15060", + message = "EntityNotFoundException thrown rather than ObjectNotFoundException; " + + "ObjectNotFoundException is thrown but caught and then converted to EntityNotFoundException" + ) + public void testQueryImplicitPathDereferencePredicate() { + statementInspector.clear(); + + inTransaction( (session) -> { + final String hql = "select c from Coin c where c.currency.id = 1"; + try { + session.createQuery( hql, Coin.class ).getResultList(); + fail( "Expecting ObjectNotFoundException for broken fk" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + } + + @Test + @FailureExpected( + jiraKey = "HHH-15060", + message = "EntityNotFoundException thrown rather than ObjectNotFoundException; " + + "ObjectNotFoundException is thrown but caught and then converted to EntityNotFoundException" + ) + public void testQueryOwnerSelection() { + statementInspector.clear(); + + inTransaction( (session) -> { + final String hql = "select c from Coin c"; + try { + session.createQuery( hql, Coin.class ).getResultList(); + fail( "Expecting ObjectNotFoundException for broken fk" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + } + + @Test + @FailureExpected( + jiraKey = "HHH-15060", + message = "This one is somewhat debatable. Is this selecting the association? Or simply matching Currencies?" + ) + public void testQueryAssociationSelection() { + // NOTE: this one is not obvious + // - we are selecting the association so from that perspective, throwing the ObjectNotFoundException is nice + // - the other way to look at it is that there are simply no matching results, so nothing to return + statementInspector.clear(); + + inTransaction( (session) -> { + final String hql = "select c.currency from Coin c"; + try { + session.createQuery( hql, Currency.class ).getResultList(); + fail( "Expecting ObjectNotFoundException for broken fk" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Coin.class, Currency.class }; + } + + public static class CollectionStatementInspector implements StatementInspector { + private List queries = new ArrayList<>(); + + @Override + public String inspect(String sql) { + queries.add( sql ); + return sql; + } + + public void clear() { + queries.clear(); + } + + public List getQueries() { + return queries; + } + } + + private final CollectionStatementInspector statementInspector = new CollectionStatementInspector(); + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.getProperties().put( AvailableSettings.STATEMENT_INSPECTOR, statementInspector ); + } + + @Override + protected void prepareTest() throws Exception { + super.prepareTest(); + + inTransaction( (session) -> { + Currency euro = new Currency( 1, "Euro" ); + Coin fiveC = new Coin( 1, "Five cents", euro ); + + session.persist( euro ); + session.persist( fiveC ); + } ); + + inTransaction( (session) -> { + session.createQuery( "delete Currency where id = 1" ).executeUpdate(); + } ); + } + + @Override + protected void cleanupTest() throws Exception { + inTransaction( (session) -> { + session.createQuery( "delete Coin where id = 1" ).executeUpdate(); + } ); + + super.cleanupTest(); + } + + @Entity(name = "Coin") + public static class Coin { + private Integer id; + private String name; + private Currency currency; + + public Coin() { + } + + public Coin(Integer id, String name, Currency currency) { + this.id = id; + this.name = name; + this.currency = currency; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToOne(fetch = FetchType.EAGER) + @NotFound(action = NotFoundAction.EXCEPTION) + public Currency getCurrency() { + return currency; + } + + public void setCurrency(Currency currency) { + this.currency = currency; + } + } + + @Entity(name = "Currency") + public static class Currency implements Serializable { + private Integer id; + private String name; + + public Currency() { + } + + public Currency(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java new file mode 100644 index 000000000000..0d07e76945ee --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java @@ -0,0 +1,215 @@ +/* + * 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 . + */ +package org.hibernate.orm.test.notfound.ignore; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Hibernate; +import org.hibernate.ObjectNotFoundException; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.resource.jdbc.spi.StatementInspector; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import org.assertj.core.api.Assertions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +/** + * Tests for `@ManyToOne @NotFound(IGNORE)` + * + * NOTES:
    + *
  1. `@NotFound` should force the association to be eager - `Coin#currency` should be loaded immediately
  2. + *
  3. `IGNORE` says to treat the broken fk as null
  4. + *
+ * + * @author Steve Ebersole + */ +public class NotFoundIgnoreManyToOneTest extends BaseCoreFunctionalTestCase { + + @Test + public void testProxy() { + inTransaction( (session) -> { + // the non-existent Child + // - this is the one valid deviation from treating the broken fk as null + try { + final Currency proxy = session.byId( Currency.class ).getReference( 1 ); + Hibernate.initialize( proxy ); + Assertions.fail( "Expecting ObjectNotFoundException" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).endsWith( "Currency" ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + } + + @Test + public void testGet() { + inTransaction( (session) -> { + final Coin coin = session.get( Coin.class, 1 ); + assertThat( coin.getCurrency() ).isNull(); + } ); + } + + @Test + @FailureExpected( + jiraKey = "HHH-15060", + message = "Bad results due to cross-join" + ) + public void testQueryImplicitPathDereferencePredicate() { + inTransaction( (session) -> { + final String hql = "select c from Coin c where c.currency.id = 1"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 1 ); + assertThat( coins.get( 0 ).getCurrency() ).isNull(); + } ); + } + + @Test + public void testQueryOwnerSelection() { + inTransaction( (session) -> { + final String hql = "select c from Coin c"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 1 ); + assertThat( coins.get( 0 ).getCurrency() ).isNull(); + } ); + } + + @Test + @FailureExpected( + jiraKey = "HHH-15060", + message = "Has zero results because of inner-join - the select w/ inner-join is executed twice for some odd reason" + ) + public void testQueryAssociationSelection() { + inTransaction( (session) -> { + final String hql = "select c.currency from Coin c"; + session.createQuery( hql, Currency.class ).getResultList(); + final List currencies = session.createQuery( hql, Currency.class ).getResultList(); + assertThat( currencies ).hasSize( 1 ); + assertThat( currencies.get( 0 ) ).isNull(); + } ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Coin.class, Currency.class }; + } + + @Override + protected void prepareTest() throws Exception { + super.prepareTest(); + + inTransaction( (session) -> { + Currency euro = new Currency( 1, "Euro" ); + Coin fiveC = new Coin( 1, "Five cents", euro ); + + session.persist( euro ); + session.persist( fiveC ); + } ); + + inTransaction( (session) -> { + session.createQuery( "delete Currency where id = 1" ).executeUpdate(); + } ); + } + + @Override + protected void cleanupTest() throws Exception { + inTransaction( (session) -> { + session.createQuery( "delete Coin where id = 1" ).executeUpdate(); + } ); + + super.cleanupTest(); + } + + @Entity(name = "Coin") + public static class Coin { + private Integer id; + private String name; + private Currency currency; + + public Coin() { + } + + public Coin(Integer id, String name, Currency currency) { + this.id = id; + this.name = name; + this.currency = currency; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToOne(fetch = FetchType.EAGER) + @NotFound(action = NotFoundAction.IGNORE) + public Currency getCurrency() { + return currency; + } + + public void setCurrency(Currency currency) { + this.currency = currency; + } + } + + @Entity(name = "Currency") + public static class Currency implements Serializable { + private Integer id; + private String name; + + public Currency() { + } + + public Currency(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java new file mode 100644 index 000000000000..d4192de998e7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java @@ -0,0 +1,210 @@ +/* + * 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 . + */ +package org.hibernate.orm.test.notfound.ignore; + +import java.io.Serializable; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToOne; + +import org.hibernate.Hibernate; +import org.hibernate.ObjectNotFoundException; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import org.assertj.core.api.Assertions; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for `@ManyToOne @NotFound(IGNORE)` + * + * NOTES:
    + *
  1. `@NotFound` should force the association to be eager - `Coin#currency` should be loaded immediately
  2. + *
  3. `IGNORE` says to treat the broken fk as null
  4. + *
+ * + * @author Steve Ebersole + */ +public class NotFoundIgnoreOneToOneTest extends BaseCoreFunctionalTestCase { + + @Test + public void testProxy() { + inTransaction( (session) -> { + // the non-existent Child + // - this is the one valid deviation from treating the broken fk as null + try { + final Currency proxy = session.byId( Currency.class ).getReference( 1 ); + Hibernate.initialize( proxy ); + Assertions.fail( "Expecting ObjectNotFoundException" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).endsWith( "Currency" ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + } + + @Test + public void testGet() { + inTransaction( (session) -> { + final Coin coin = session.get( Coin.class, 1 ); + assertThat( coin.getCurrency() ).isNull(); + } ); + } + + @Test + @FailureExpected( + jiraKey = "HHH-15060", + message = "Bad results due to cross-join" + ) + public void testQueryImplicitPathDereferencePredicate() { + inTransaction( (session) -> { + final String hql = "select c from Coin c where c.currency.id = 1"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 1 ); + assertThat( coins.get( 0 ).getCurrency() ).isNull(); + } ); + } + + @Test + public void testQueryOwnerSelection() { + inTransaction( (session) -> { + final String hql = "select c from Coin c"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 1 ); + assertThat( coins.get( 0 ).getCurrency() ).isNull(); + } ); + } + + @Test + @FailureExpected( + jiraKey = "HHH-15060", + message = "Has zero results because of inner-join - the select w/ inner-join is executed twice for some odd reason" + ) + public void testQueryAssociationSelection() { + inTransaction( (session) -> { + final String hql = "select c.currency from Coin c"; + session.createQuery( hql, Currency.class ).getResultList(); + final List currencies = session.createQuery( hql, Currency.class ).getResultList(); + assertThat( currencies ).hasSize( 1 ); + assertThat( currencies.get( 0 ) ).isNull(); + } ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Coin.class, Currency.class }; + } + + @Override + protected void prepareTest() throws Exception { + super.prepareTest(); + + inTransaction( (session) -> { + Currency euro = new Currency( 1, "Euro" ); + Coin fiveC = new Coin( 1, "Five cents", euro ); + + session.persist( euro ); + session.persist( fiveC ); + } ); + + inTransaction( (session) -> { + session.createQuery( "delete Currency where id = 1" ).executeUpdate(); + } ); + } + + @Override + protected void cleanupTest() throws Exception { + inTransaction( (session) -> { + session.createQuery( "delete Coin where id = 1" ).executeUpdate(); + } ); + + super.cleanupTest(); + } + + @Entity(name = "Coin") + public static class Coin { + private Integer id; + private String name; + private Currency currency; + + public Coin() { + } + + public Coin(Integer id, String name, Currency currency) { + this.id = id; + this.name = name; + this.currency = currency; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToOne(fetch = FetchType.EAGER) + @NotFound(action = NotFoundAction.IGNORE) + public Currency getCurrency() { + return currency; + } + + public void setCurrency(Currency currency) { + this.currency = currency; + } + } + + @Entity(name = "Currency") + public static class Currency implements Serializable { + private Integer id; + private String name; + + public Currency() { + } + + public Currency(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} From 6d1a47e5980b23e8688b7d21a0f7450d7450c2a5 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Fri, 11 Feb 2022 12:29:56 -0600 Subject: [PATCH 016/201] HHH-15060 - Associations with @NotFound should always be left joined when de-referenced in HQL/Criteria - `@NotFound` no longer exports a physical foreign-key - tests showing bugs and inconsistencies wrt `@NotFound` handling - added `FetchNotFoundException` - force association to EAGER --- .../org/hibernate/FetchNotFoundException.java | 43 ++++ .../org/hibernate/annotations/FetchMode.java | 22 +- .../hibernate/annotations/NotFoundAction.java | 24 ++- .../org/hibernate/cfg/AnnotationBinder.java | 31 ++- .../hibernate/cfg/CollectionSecondPass.java | 4 +- .../org/hibernate/cfg/OneToOneSecondPass.java | 11 +- .../org/hibernate/cfg/ToOneFkSecondPass.java | 4 +- .../cfg/annotations/CollectionBinder.java | 106 ++++++--- .../cfg/annotations/IdBagBinder.java | 5 +- .../hibernate/cfg/annotations/ListBinder.java | 8 +- .../hibernate/cfg/annotations/MapBinder.java | 8 +- .../java/org/hibernate/mapping/ManyToOne.java | 21 +- .../java/org/hibernate/mapping/OneToMany.java | 22 +- .../org/hibernate/type/ManyToOneType.java | 37 ++-- .../java/org/hibernate/type/TypeFactory.java | 19 +- .../cfg/annotations/CollectionBinderTest.java | 2 +- .../NotFoundExceptionLogicalOneToOneTest.java | 176 +++++++++------ .../NotFoundExceptionManyToOneTest.java | 204 +++++++++--------- .../ignore/NotFoundIgnoreManyToOneTest.java | 150 ++++++++----- .../ignore/NotFoundIgnoreOneToOneTest.java | 128 +++++++---- ...ulaOneToManyNotIgnoreLazyFetchingTest.java | 25 +-- ...anyToOneNonUpdatableNonInsertableTest.java | 11 +- ...OneToOneNonUpdatableNonInsertableTest.java | 12 +- .../notfound/LazyNotFoundOneToOneTest.java | 12 +- .../orm/junit/SessionFactoryExtension.java | 14 +- 25 files changed, 700 insertions(+), 399 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/FetchNotFoundException.java diff --git a/hibernate-core/src/main/java/org/hibernate/FetchNotFoundException.java b/hibernate-core/src/main/java/org/hibernate/FetchNotFoundException.java new file mode 100644 index 000000000000..42f729c1f2ce --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/FetchNotFoundException.java @@ -0,0 +1,43 @@ +/* + * 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 . + */ +package org.hibernate; + +import java.util.Locale; +import javax.persistence.EntityNotFoundException; + +/** + * Exception for {@link org.hibernate.annotations.NotFoundAction#EXCEPTION} + * + * @see org.hibernate.annotations.NotFound + * + * @author Steve Ebersole + */ +public class FetchNotFoundException extends EntityNotFoundException { + private final String entityName; + private final Object identifier; + + public FetchNotFoundException(String entityName, Object identifier) { + super( + String.format( + Locale.ROOT, + "Entity `%s` with identifier value `%s` does not exist", + entityName, + identifier + ) + ); + this.entityName = entityName; + this.identifier = identifier; + } + + public String getEntityName() { + return entityName; + } + + public Object getIdentifier() { + return identifier; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java b/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java index fa1e03a7645a..e630814324e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java @@ -16,13 +16,27 @@ public enum FetchMode { /** * Use a secondary select for each individual entity, collection, or join load. */ - SELECT, + SELECT( org.hibernate.FetchMode.SELECT ), /** * Use an outer join to load the related entities, collections or joins. */ - JOIN, + JOIN( org.hibernate.FetchMode.JOIN ), /** - * Available for collections only.  When accessing a non-initialized collection, this fetch mode will trigger loading all elements of all collections of the same role for all owners associated with the persistence context using a single secondary select. + * Available for collections only. + * + * When accessing a non-initialized collection, this fetch mode will trigger + * loading all elements of all collections of the same role for all owners + * associated with the persistence context using a single secondary select. */ - SUBSELECT + SUBSELECT( org.hibernate.FetchMode.SELECT ); + + private final org.hibernate.FetchMode hibernateFetchMode; + + FetchMode(org.hibernate.FetchMode hibernateFetchMode) { + this.hibernateFetchMode = hibernateFetchMode; + } + + public org.hibernate.FetchMode getHibernateFetchMode() { + return hibernateFetchMode; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/NotFoundAction.java b/hibernate-core/src/main/java/org/hibernate/annotations/NotFoundAction.java index c6e4e9622d29..906d65024096 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/NotFoundAction.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/NotFoundAction.java @@ -6,20 +6,36 @@ */ package org.hibernate.annotations; +import org.hibernate.FetchNotFoundException; /** - * Possible actions when an associated entity is not found in the database. Often seen with "legacy" foreign-key - * schemes which do not use {@code NULL} to indicate a missing reference, instead using a "magic value". + * Possible actions when the database contains a non-null fk with no + * matching target. This also implies that there are no physical + * foreign-key constraints on the database. * + * As an example, consider a typical Customer/Order model. These actions apply + * when a non-null `orders.customer_fk` value does not have a corresponding value + * in `customers.id`. + * + * Generally this will occur in 2 scenarios:
    + *
  • the associated data has been deleted
  • + *
  • the model uses special "magic" values to indicate null
  • + *
+ * + * @author Steve Ebersole * @author Emmanuel Bernard */ public enum NotFoundAction { /** - * Raise an exception when an element is not found (default and recommended). + * Throw an exception when the association is not found (default and recommended). + * + * @see FetchNotFoundException */ EXCEPTION, + /** - * Ignore the element when not found in database. + * Ignore the association when not found in database. Effectively treats the + * association as null, despite the non-null foreign-key value. */ IGNORE } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index db8e15eb6a18..ca6292429685 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -1796,7 +1796,8 @@ private static void processElementAnnotations( Cascade hibernateCascade = property.getAnnotation( Cascade.class ); NotFound notFound = property.getAnnotation( NotFound.class ); - boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE ); + NotFoundAction notFoundAction = notFound == null ? null : notFound.action(); + boolean ignoreNotFound = notFoundAction == NotFoundAction.IGNORE; matchIgnoreNotFoundWithFetchType(propertyHolder.getEntityName(), property.getName(), ignoreNotFound, ann.fetch()); OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ); @@ -1821,7 +1822,7 @@ private static void processElementAnnotations( getCascadeStrategy( ann.cascade(), hibernateCascade, false, forcePersist ), joinColumns, !mandatory, - ignoreNotFound, + notFoundAction, onDeleteCascade, ToOneBinder.getTargetEntity( inferredData, context ), propertyHolder, @@ -1851,7 +1852,8 @@ else if ( property.isAnnotationPresent( OneToOne.class ) ) { boolean trueOneToOne = hasPkjc; Cascade hibernateCascade = property.getAnnotation( Cascade.class ); NotFound notFound = property.getAnnotation( NotFound.class ); - boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE ); + NotFoundAction notFoundAction = notFound == null ? null : notFound.action(); + boolean ignoreNotFound = notFoundAction == NotFoundAction.IGNORE; // MapsId means the columns belong to the pk; // A @MapsId association (obviously) must be non-null when the entity is first persisted. // If a @MapsId association is not mapped with @NotFound(IGNORE), then the association @@ -1878,7 +1880,8 @@ else if ( property.isAnnotationPresent( OneToOne.class ) ) { joinColumns, !mandatory, getFetchMode( ann.fetch() ), - ignoreNotFound, onDeleteCascade, + notFoundAction, + onDeleteCascade, ToOneBinder.getTargetEntity( inferredData, context ), propertyHolder, inferredData, @@ -3040,7 +3043,7 @@ private static void bindManyToOne( String cascadeStrategy, Ejb3JoinColumn[] columns, boolean optional, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean cascadeOnDelete, XClass targetEntity, PropertyHolder propertyHolder, @@ -3060,7 +3063,7 @@ private static void bindManyToOne( final XProperty property = inferredData.getProperty(); defineFetchingStrategy( value, property ); //value.setFetchMode( fetchMode ); - value.setIgnoreNotFound( ignoreNotFound ); + value.setNotFoundAction( notFoundAction ); value.setCascadeDeleteEnabled( cascadeOnDelete ); //value.setLazy( fetchMode != FetchMode.JOIN ); if ( !optional ) { @@ -3166,6 +3169,8 @@ protected static void defineFetchingStrategy(ToOne toOne, XProperty property) { Fetch fetch = property.getAnnotation( Fetch.class ); ManyToOne manyToOne = property.getAnnotation( ManyToOne.class ); OneToOne oneToOne = property.getAnnotation( OneToOne.class ); + NotFound notFound = property.getAnnotation( NotFound.class ); + FetchType fetchType; if ( manyToOne != null ) { fetchType = manyToOne.fetch(); @@ -3178,7 +3183,12 @@ else if ( oneToOne != null ) { "Define fetch strategy on a property not annotated with @OneToMany nor @OneToOne" ); } - if ( lazy != null ) { + + if ( notFound != null ) { + toOne.setLazy( false ); + toOne.setUnwrapProxy( true ); + } + else if ( lazy != null ) { toOne.setLazy( !( lazy.value() == LazyToOneOption.FALSE ) ); toOne.setUnwrapProxy( ( lazy.value() == LazyToOneOption.NO_PROXY ) ); } @@ -3187,6 +3197,7 @@ else if ( oneToOne != null ) { toOne.setUnwrapProxy( fetchType != FetchType.LAZY ); toOne.setUnwrapProxyImplicit( true ); } + if ( fetch != null ) { if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) { toOne.setFetchMode( FetchMode.JOIN ); @@ -3213,7 +3224,7 @@ private static void bindOneToOne( Ejb3JoinColumn[] joinColumns, boolean optional, FetchMode fetchMode, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean cascadeOnDelete, XClass targetEntity, PropertyHolder propertyHolder, @@ -3267,7 +3278,7 @@ private static void bindOneToOne( propertyHolder, inferredData, targetEntity, - ignoreNotFound, + notFoundAction, cascadeOnDelete, optional, cascadeStrategy, @@ -3287,7 +3298,7 @@ private static void bindOneToOne( else { //has a FK on the table bindManyToOne( - cascadeStrategy, joinColumns, optional, ignoreNotFound, cascadeOnDelete, + cascadeStrategy, joinColumns, optional, notFoundAction, cascadeOnDelete, targetEntity, propertyHolder, inferredData, true, isIdentifierMapper, inSecondPass, propertyBinder, context diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/CollectionSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/CollectionSecondPass.java index 24256e2e37a0..4f4108fa9932 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/CollectionSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/CollectionSecondPass.java @@ -16,6 +16,7 @@ import org.hibernate.mapping.Collection; import org.hibernate.mapping.IndexedCollection; import org.hibernate.mapping.OneToMany; +import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Selectable; import org.hibernate.mapping.Value; @@ -68,8 +69,7 @@ public void doSecondPass(java.util.Map persistentClasses) } } - abstract public void secondPass(java.util.Map persistentClasses, java.util.Map inheritedMetas) - throws MappingException; + abstract public void secondPass(java.util.Map persistentClasses, java.util.Map inheritedMetas); private static String columns(Value val) { StringBuilder columns = new StringBuilder(); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java index 7a376ee894b4..912ac22ae629 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java @@ -8,14 +8,13 @@ import java.util.Iterator; import java.util.Map; - import javax.persistence.JoinColumn; import javax.persistence.JoinColumns; import org.hibernate.AnnotationException; -import org.hibernate.FetchMode; import org.hibernate.MappingException; import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.cfg.annotations.PropertyBinder; @@ -41,7 +40,7 @@ public class OneToOneSecondPass implements SecondPass { private String ownerEntity; private String ownerProperty; private PropertyHolder propertyHolder; - private boolean ignoreNotFound; + private NotFoundAction notFoundAction; private PropertyData inferredData; private XClass targetEntity; private boolean cascadeOnDelete; @@ -57,7 +56,7 @@ public OneToOneSecondPass( PropertyHolder propertyHolder, PropertyData inferredData, XClass targetEntity, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean cascadeOnDelete, boolean optional, String cascadeStrategy, @@ -68,7 +67,7 @@ public OneToOneSecondPass( this.mappedBy = mappedBy; this.propertyHolder = propertyHolder; this.buildingContext = buildingContext; - this.ignoreNotFound = ignoreNotFound; + this.notFoundAction = notFoundAction; this.inferredData = inferredData; this.targetEntity = targetEntity; this.cascadeOnDelete = cascadeOnDelete; @@ -191,7 +190,7 @@ else if ( otherSideProperty.getValue() instanceof ManyToOne ) { ); ManyToOne manyToOne = new ManyToOne( buildingContext, mappedByJoin.getTable() ); //FIXME use ignore not found here - manyToOne.setIgnoreNotFound( ignoreNotFound ); + manyToOne.setNotFoundAction( notFoundAction ); manyToOne.setCascadeDeleteEnabled( value.isCascadeDeleteEnabled() ); manyToOne.setFetchMode( value.getFetchMode() ); manyToOne.setLazy( value.isLazy() ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java index b04c27d16e18..f79c8df5dfe6 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ToOneFkSecondPass.java @@ -106,7 +106,9 @@ public void doSecondPass(java.util.Map persistentClasses) throws MappingExceptio /* * HbmMetadataSourceProcessorImpl does this only when property-ref != null, but IMO, it makes sense event if it is null */ - if ( !manyToOne.isIgnoreNotFound() ) manyToOne.createPropertyRefConstraints( persistentClasses ); + if ( manyToOne.getNotFoundAction() == null ) { + manyToOne.createPropertyRefConstraints( persistentClasses ); + } } else if ( value instanceof OneToOne ) { value.createForeignKey(); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java index 9570c1df3d79..23a141065762 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java @@ -50,6 +50,8 @@ import org.hibernate.annotations.LazyGroup; import org.hibernate.annotations.Loader; import org.hibernate.annotations.ManyToAny; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; import org.hibernate.annotations.OptimisticLock; @@ -156,7 +158,7 @@ public abstract class CollectionBinder { private Ejb3Column[] elementColumns; private boolean isEmbedded; private XProperty property; - private boolean ignoreNotFound; + private NotFoundAction notFoundAction; private TableBinder tableBinder; private Ejb3Column[] mapKeyColumns; private Ejb3JoinColumn[] mapKeyManyToManyColumns; @@ -572,7 +574,7 @@ public void bind() { isEmbedded, property, collectionType, - ignoreNotFound, + notFoundAction, oneToMany, tableBinder, buildingContext @@ -714,6 +716,8 @@ private void defineFetchingStrategy() { ManyToMany manyToMany = property.getAnnotation( ManyToMany.class ); ElementCollection elementCollection = property.getAnnotation( ElementCollection.class ); ManyToAny manyToAny = property.getAnnotation( ManyToAny.class ); + NotFound notFound = property.getAnnotation( NotFound.class ); + FetchType fetchType; if ( oneToMany != null ) { fetchType = oneToMany.fetch(); @@ -732,34 +736,57 @@ else if ( manyToAny != null ) { "Define fetch strategy on a property not annotated with @ManyToOne nor @OneToMany nor @CollectionOfElements" ); } - if ( lazy != null ) { - collection.setLazy( !( lazy.value() == LazyCollectionOption.FALSE ) ); - collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA ); + + if ( notFound != null ) { + collection.setLazy( false ); + + if ( lazy != null ) { + collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA ); + } + + if ( fetch != null ) { + if ( fetch.value() != null ) { + collection.setFetchMode( fetch.value().getHibernateFetchMode() ); + if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) { + collection.setSubselectLoadable( true ); + collection.getOwner().setSubselectLoadableCollections( true ); + } + } + } + else { + collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) ); + } } else { - collection.setLazy( fetchType == FetchType.LAZY ); - collection.setExtraLazy( false ); - } - if ( fetch != null ) { - if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) { - collection.setFetchMode( FetchMode.JOIN ); - collection.setLazy( false ); + if ( lazy != null ) { + collection.setLazy( !( lazy.value() == LazyCollectionOption.FALSE ) ); + collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA ); } - else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) { - collection.setFetchMode( FetchMode.SELECT ); + else { + collection.setLazy( fetchType == FetchType.LAZY ); + collection.setExtraLazy( false ); } - else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) { - collection.setFetchMode( FetchMode.SELECT ); - collection.setSubselectLoadable( true ); - collection.getOwner().setSubselectLoadableCollections( true ); + if ( fetch != null ) { + if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) { + collection.setFetchMode( FetchMode.JOIN ); + collection.setLazy( false ); + } + else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) { + collection.setFetchMode( FetchMode.SELECT ); + } + else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) { + collection.setFetchMode( FetchMode.SELECT ); + collection.setSubselectLoadable( true ); + collection.getOwner().setSubselectLoadableCollections( true ); + } + else { + throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() ); + } } else { - throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() ); + collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) ); } } - else { - collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) ); - } } private XClass getCollectionType() { @@ -788,14 +815,14 @@ public SecondPass getSecondPass( final boolean isEmbedded, final XProperty property, final XClass collType, - final boolean ignoreNotFound, + final NotFoundAction notFoundAction, final boolean unique, final TableBinder assocTableBinder, final MetadataBuildingContext buildingContext) { return new CollectionSecondPass( buildingContext, collection ) { @SuppressWarnings("rawtypes") @Override - public void secondPass(Map persistentClasses, Map inheritedMetas) throws MappingException { + public void secondPass(Map persistentClasses, Map inheritedMetas) { bindStarToManySecondPass( persistentClasses, collType, @@ -807,7 +834,7 @@ public void secondPass(Map persistentClasses, Map inheritedMetas) throws Mapping property, unique, assocTableBinder, - ignoreNotFound, + notFoundAction, buildingContext ); } @@ -828,7 +855,7 @@ protected boolean bindStarToManySecondPass( XProperty property, boolean unique, TableBinder associationTableBinder, - boolean ignoreNotFound, + NotFoundAction notFoundAction, MetadataBuildingContext buildingContext) { PersistentClass persistentClass = persistentClasses.get( collType.getName() ); boolean reversePropertyInJoin = false; @@ -863,7 +890,7 @@ protected boolean bindStarToManySecondPass( fkJoinColumns, collType, cascadeDeleteEnabled, - ignoreNotFound, + notFoundAction, buildingContext, inheritanceStatePerClass ); @@ -878,7 +905,7 @@ protected boolean bindStarToManySecondPass( inverseColumns, elementColumns, isEmbedded, collType, - ignoreNotFound, unique, + notFoundAction, unique, cascadeDeleteEnabled, associationTableBinder, property, @@ -895,7 +922,7 @@ protected void bindOneToManySecondPass( Ejb3JoinColumn[] fkJoinColumns, XClass collectionType, boolean cascadeDeleteEnabled, - boolean ignoreNotFound, + NotFoundAction notFoundAction, MetadataBuildingContext buildingContext, Map inheritanceStatePerClass) { @@ -910,7 +937,7 @@ protected void bindOneToManySecondPass( org.hibernate.mapping.OneToMany oneToMany = new org.hibernate.mapping.OneToMany( buildingContext, collection.getOwner() ); collection.setElement( oneToMany ); oneToMany.setReferencedEntityName( collectionType.getName() ); - oneToMany.setIgnoreNotFound( ignoreNotFound ); + oneToMany.setNotFoundAction( notFoundAction ); String assocClass = oneToMany.getReferencedEntityName(); PersistentClass associatedClass = persistentClasses.get( assocClass ); @@ -1314,7 +1341,7 @@ private void bindManyToManySecondPass( Ejb3Column[] elementColumns, boolean isEmbedded, XClass collType, - boolean ignoreNotFound, boolean unique, + NotFoundAction notFoundAction, boolean unique, boolean cascadeDeleteEnabled, TableBinder associationTableBinder, XProperty property, @@ -1457,7 +1484,7 @@ else if ( anyAnn != null ) { //make the second join non lazy element.setFetchMode( FetchMode.JOIN ); element.setLazy( false ); - element.setIgnoreNotFound( ignoreNotFound ); + element.setNotFoundAction( notFoundAction ); // as per 11.1.38 of JPA 2.0 spec, default to primary key if no column is specified by @OrderBy. if ( hqlOrderBy != null ) { collValue.setManyToManyOrdering( @@ -1824,8 +1851,21 @@ public void setProperty(XProperty property) { this.property = property; } + public NotFoundAction getNotFoundAction() { + return notFoundAction; + } + + public void setNotFoundAction(NotFoundAction notFoundAction) { + this.notFoundAction = notFoundAction; + } + public void setIgnoreNotFound(boolean ignoreNotFound) { - this.ignoreNotFound = ignoreNotFound; + if ( ignoreNotFound ) { + setNotFoundAction( NotFoundAction.IGNORE ); + } + else { + setNotFoundAction( null ); + } } public void setMapKeyColumns(Ejb3Column[] mapKeyColumns) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java index 34ef384638e7..9bfb2b62f44d 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java @@ -13,6 +13,7 @@ import org.hibernate.AnnotationException; import org.hibernate.annotations.CollectionId; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.Type; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; @@ -52,11 +53,11 @@ protected boolean bindStarToManySecondPass( XProperty property, boolean unique, TableBinder associationTableBinder, - boolean ignoreNotFound, + NotFoundAction notFoundAction, MetadataBuildingContext buildingContext) { boolean result = super.bindStarToManySecondPass( persistentClasses, collType, fkJoinColumns, keyColumns, inverseColumns, elementColumns, isEmbedded, - property, unique, associationTableBinder, ignoreNotFound, getBuildingContext() + property, unique, associationTableBinder, notFoundAction, getBuildingContext() ); CollectionId collectionIdAnn = property.getAnnotation( CollectionId.class ); if ( collectionIdAnn != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java index 69aa6c597699..5f09945873d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java @@ -10,6 +10,7 @@ import org.hibernate.AnnotationException; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.OrderBy; import org.hibernate.annotations.Sort; import org.hibernate.annotations.common.reflection.XClass; @@ -76,14 +77,13 @@ public SecondPass getSecondPass( final boolean isEmbedded, final XProperty property, final XClass collType, - final boolean ignoreNotFound, + final NotFoundAction notFoundAction, final boolean unique, final TableBinder assocTableBinder, final MetadataBuildingContext buildingContext) { return new CollectionSecondPass( getBuildingContext(), ListBinder.this.collection ) { @Override - public void secondPass(Map persistentClasses, Map inheritedMetas) - throws MappingException { + public void secondPass(Map persistentClasses, Map inheritedMetas) { bindStarToManySecondPass( persistentClasses, collType, @@ -95,7 +95,7 @@ public void secondPass(Map persistentClasses, Map inheritedMetas) property, unique, assocTableBinder, - ignoreNotFound, + notFoundAction, buildingContext ); bindIndex( buildingContext ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java index 4a4f83fe1c97..b863ab97a219 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java @@ -23,6 +23,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.boot.spi.BootstrapContext; @@ -87,16 +88,15 @@ public SecondPass getSecondPass( final boolean isEmbedded, final XProperty property, final XClass collType, - final boolean ignoreNotFound, + final NotFoundAction notFoundAction, final boolean unique, final TableBinder assocTableBinder, final MetadataBuildingContext buildingContext) { return new CollectionSecondPass( buildingContext, MapBinder.this.collection ) { - public void secondPass(Map persistentClasses, Map inheritedMetas) - throws MappingException { + public void secondPass(Map persistentClasses, Map inheritedMetas) { bindStarToManySecondPass( persistentClasses, collType, fkJoinColumns, keyColumns, inverseColumns, elementColumns, - isEmbedded, property, unique, assocTableBinder, ignoreNotFound, buildingContext + isEmbedded, property, unique, assocTableBinder, notFoundAction, buildingContext ); bindKeyFromAssociationTable( collType, persistentClasses, mapKeyPropertyName, property, isEmbedded, buildingContext, diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java b/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java index 22ebebf3e21b..7e87f5c25ad5 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java @@ -11,6 +11,7 @@ import java.util.Map; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.type.EntityType; @@ -22,6 +23,7 @@ */ public class ManyToOne extends ToOne { private boolean ignoreNotFound; + private NotFoundAction notFoundAction; private boolean isLogicalOneToOne; /** @@ -44,7 +46,7 @@ public Type getType() throws MappingException { getPropertyName(), isLazy(), isUnwrapProxy(), - isIgnoreNotFound(), + getNotFoundAction(), isLogicalOneToOne ); } @@ -97,12 +99,25 @@ public Object accept(ValueVisitor visitor) { return visitor.accept(this); } + public NotFoundAction getNotFoundAction() { + return notFoundAction; + } + + public void setNotFoundAction(NotFoundAction notFoundAction) { + this.notFoundAction = notFoundAction; + } + public boolean isIgnoreNotFound() { - return ignoreNotFound; + return notFoundAction == NotFoundAction.IGNORE; } public void setIgnoreNotFound(boolean ignoreNotFound) { - this.ignoreNotFound = ignoreNotFound; + if ( ignoreNotFound ) { + notFoundAction = NotFoundAction.IGNORE; + } + else { + notFoundAction = null; + } } public void markAsLogicalOneToOne() { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java b/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java index 3f0d8204742e..01767dbf9722 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java @@ -11,6 +11,7 @@ import org.hibernate.FetchMode; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.engine.spi.Mapping; @@ -29,7 +30,7 @@ public class OneToMany implements Value { private String referencedEntityName; private PersistentClass associatedClass; - private boolean ignoreNotFound; + private NotFoundAction notFoundAction; /** * @deprecated Use {@link OneToMany#OneToMany(MetadataBuildingContext, PersistentClass)} instead. @@ -57,7 +58,7 @@ private EntityType getEntityType() { null, false, false, - isIgnoreNotFound(), + notFoundAction, false ); } @@ -162,12 +163,25 @@ public boolean[] getColumnUpdateability() { throw new UnsupportedOperationException(); } + public NotFoundAction getNotFoundAction() { + return notFoundAction; + } + + public void setNotFoundAction(NotFoundAction notFoundAction) { + this.notFoundAction = notFoundAction; + } + public boolean isIgnoreNotFound() { - return ignoreNotFound; + return notFoundAction == NotFoundAction.IGNORE; } public void setIgnoreNotFound(boolean ignoreNotFound) { - this.ignoreNotFound = ignoreNotFound; + if ( ignoreNotFound ) { + notFoundAction = NotFoundAction.IGNORE; + } + else { + notFoundAction = null; + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java index c6be39af5eff..11eee9bd490d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java @@ -12,8 +12,11 @@ import java.util.Arrays; import org.hibernate.AssertionFailure; +import org.hibernate.FetchNotFoundException; import org.hibernate.HibernateException; import org.hibernate.MappingException; +import org.hibernate.ObjectNotFoundException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.engine.internal.ForeignKeys; import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.spi.*; @@ -27,7 +30,7 @@ */ public class ManyToOneType extends EntityType { private final String propertyName; - private final boolean ignoreNotFound; + private final NotFoundAction notFoundAction; private boolean isLogicalOneToOne; /** @@ -49,12 +52,12 @@ public ManyToOneType(TypeFactory.TypeScope scope, String referencedEntityName) { * @param lazy Should the association be handled lazily */ public ManyToOneType(TypeFactory.TypeScope scope, String referencedEntityName, boolean lazy) { - this( scope, referencedEntityName, true, null, lazy, true, false, false ); + this( scope, referencedEntityName, true, null, lazy, true, null, false ); } /** - * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, boolean, boolean ) } instead. + * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, NotFoundAction, boolean ) } instead. */ @Deprecated public ManyToOneType( @@ -64,13 +67,13 @@ public ManyToOneType( boolean lazy, boolean unwrapProxy, boolean isEmbeddedInXML, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean isLogicalOneToOne) { - this( scope, referencedEntityName, uniqueKeyPropertyName == null, uniqueKeyPropertyName, lazy, unwrapProxy, ignoreNotFound, isLogicalOneToOne ); + this( scope, referencedEntityName, uniqueKeyPropertyName == null, uniqueKeyPropertyName, lazy, unwrapProxy, notFoundAction, isLogicalOneToOne ); } /** - * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, boolean, boolean ) } instead. + * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, NotFoundAction, boolean ) } instead. */ @Deprecated public ManyToOneType( @@ -80,9 +83,9 @@ public ManyToOneType( String uniqueKeyPropertyName, boolean lazy, boolean unwrapProxy, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean isLogicalOneToOne) { - this( scope, referencedEntityName, referenceToPrimaryKey, uniqueKeyPropertyName, null, lazy, unwrapProxy, ignoreNotFound, isLogicalOneToOne ); + this( scope, referencedEntityName, referenceToPrimaryKey, uniqueKeyPropertyName, null, lazy, unwrapProxy, notFoundAction, isLogicalOneToOne ); } public ManyToOneType( @@ -93,24 +96,25 @@ public ManyToOneType( String propertyName, boolean lazy, boolean unwrapProxy, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean isLogicalOneToOne) { super( scope, referencedEntityName, referenceToPrimaryKey, uniqueKeyPropertyName, !lazy, unwrapProxy ); this.propertyName = propertyName; - this.ignoreNotFound = ignoreNotFound; + this.notFoundAction = notFoundAction; this.isLogicalOneToOne = isLogicalOneToOne; } public ManyToOneType(ManyToOneType original, String superTypeEntityName) { super( original, superTypeEntityName ); this.propertyName = original.propertyName; - this.ignoreNotFound = original.ignoreNotFound; + this.notFoundAction = original.notFoundAction; this.isLogicalOneToOne = original.isLogicalOneToOne; } @Override public boolean isNullable() { - return ignoreNotFound; + // this matches the original check, but this seems wrong. + return notFoundAction == NotFoundAction.IGNORE; } @Override @@ -237,7 +241,14 @@ public boolean isModified( @Override public Object resolve(Object value, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) throws HibernateException { - Object resolvedValue = super.resolve(value, session, owner, overridingEager); + final Object resolvedValue; + try { + resolvedValue = super.resolve( value, session, owner, overridingEager ); + } + catch (ObjectNotFoundException e) { + throw new FetchNotFoundException( getAssociatedEntityName(), value ); + } + if ( isLogicalOneToOne && value != null && getPropertyName() != null ) { final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); EntityEntry entry = persistenceContext.getEntry( owner ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java index 9acb4bc484a5..b83d719d3fe1 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java @@ -13,6 +13,7 @@ import java.util.Properties; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.classic.Lifecycle; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -41,7 +42,7 @@ @SuppressWarnings({"unchecked"}) public final class TypeFactory implements Serializable, TypeBootstrapContext { /** - * @deprecated Use {@link TypeConfiguration}/{@link TypeConfiguration.Scope} instead + * @deprecated Use {@link TypeConfiguration} */ @Deprecated public interface TypeScope extends Serializable { @@ -295,7 +296,7 @@ public EntityType manyToOne(String persistentClass, boolean lazy) { } /** - * @deprecated Use {@link #manyToOne(String, boolean, String, boolean, boolean, boolean, boolean)} instead. + * @deprecated Use {@link #manyToOne(String, boolean, String, boolean, boolean, NotFoundAction, boolean)} instead. */ @Deprecated public EntityType manyToOne( @@ -303,7 +304,7 @@ public EntityType manyToOne( String uniqueKeyPropertyName, boolean lazy, boolean unwrapProxy, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean isLogicalOneToOne) { return manyToOne( persistentClass, @@ -311,13 +312,13 @@ public EntityType manyToOne( uniqueKeyPropertyName, lazy, unwrapProxy, - ignoreNotFound, + notFoundAction, isLogicalOneToOne ); } /** - * @deprecated Use {@link #manyToOne(String, boolean, String, String, boolean, boolean, boolean, boolean)} instead. + * @deprecated Use {@link #manyToOne(String, boolean, String, String, boolean, boolean, NotFoundAction, boolean)} instead. */ @Deprecated public EntityType manyToOne( @@ -326,7 +327,7 @@ public EntityType manyToOne( String uniqueKeyPropertyName, boolean lazy, boolean unwrapProxy, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean isLogicalOneToOne) { return manyToOne( persistentClass, @@ -335,7 +336,7 @@ public EntityType manyToOne( null, lazy, unwrapProxy, - ignoreNotFound, + notFoundAction, isLogicalOneToOne ); } @@ -347,7 +348,7 @@ public EntityType manyToOne( String propertyName, boolean lazy, boolean unwrapProxy, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean isLogicalOneToOne) { return new ManyToOneType( typeScope, @@ -357,7 +358,7 @@ public EntityType manyToOne( propertyName, lazy, unwrapProxy, - ignoreNotFound, + notFoundAction, isLogicalOneToOne ); } diff --git a/hibernate-core/src/test/java/org/hibernate/cfg/annotations/CollectionBinderTest.java b/hibernate-core/src/test/java/org/hibernate/cfg/annotations/CollectionBinderTest.java index edc30e455e9f..409feb979df0 100644 --- a/hibernate-core/src/test/java/org/hibernate/cfg/annotations/CollectionBinderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/cfg/annotations/CollectionBinderTest.java @@ -65,7 +65,7 @@ protected Collection createCollection(PersistentClass persistentClass) { String expectMessage = "Association [abc] for entity [CollectionBinderTest] references unmapped class [List]"; try { - collectionBinder.bindOneToManySecondPass(collection, new HashMap(), null, collectionType, false, false, buildingContext, null); + collectionBinder.bindOneToManySecondPass(collection, new HashMap(), null, collectionType, false, null, buildingContext, null); } catch (MappingException e) { assertEquals(expectMessage, e.getMessage()); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java index 333c9bbc519c..a600fea6423b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java @@ -8,24 +8,32 @@ import java.io.Serializable; import java.util.List; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.OneToOne; +import org.hibernate.FetchNotFoundException; import org.hibernate.Hibernate; import org.hibernate.ObjectNotFoundException; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; -import org.hibernate.testing.FailureExpected; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.junit.Test; +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.FailureExpected; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import javax.persistence.Entity; +import javax.persistence.EntityNotFoundException; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToOne; import org.assertj.core.api.Assertions; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.fail; /** * Tests for `@OneToOne @NotFound(EXCEPTION)` @@ -37,11 +45,14 @@ * * @author Steve Ebersole */ -public class NotFoundExceptionLogicalOneToOneTest extends BaseCoreFunctionalTestCase { +@DomainModel( annotatedClasses = { NotFoundExceptionLogicalOneToOneTest.Coin.class, NotFoundExceptionLogicalOneToOneTest.Currency.class } ) +@SessionFactory( useCollectingStatementInspector = true ) +public class NotFoundExceptionLogicalOneToOneTest { @Test - public void testProxy() { - inTransaction( (session) -> { - // the non-existent Child + @JiraKey( "HHH-15060" ) + public void testProxy(SessionFactoryScope scope) { + // test handling of a proxy for the missing Coin + scope.inTransaction( (session) -> { final Currency proxy = session.byId( Currency.class ).getReference( 1 ); try { Hibernate.initialize( proxy ); @@ -55,18 +66,38 @@ public void testProxy() { } @Test - @FailureExpected( - jiraKey = "HHH-15060", - message = "We return a proxy here for `Coin#currency`, which violates NOTE #1. The exception happens " + - "when we reference the proxy, but thats not correct" - ) - public void testGet() { - inTransaction( (session) -> { + @JiraKey( "HHH-15060" ) + public void testGet(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final Coin coin = session.get( Coin.class, 2 ); + + // most importantly, the currency should not be uninitialized + assertThat( Hibernate.isPropertyInitialized( coin, "currency" ) ) + .describedAs( "Expecting `Coin#currency` to be eagerly fetched (bytecode) due to `@NotFound`" ) + .isTrue(); + assertThat( Hibernate.isInitialized( coin.getCurrency() ) ) + .describedAs( "Expecting `Coin#currency` to be eagerly fetched due to `@NotFound`" ) + .isTrue(); + + // join may be better here. but for now, 5.x generates 2 selects here + // which is not wrong +// assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); + } ); + + scope.inTransaction( (session) -> { try { session.get( Coin.class, 1 ); - fail( "Expecting ObjectNotFoundException" ); + fail( "Expecting FetchNotFoundException" ); } - catch (ObjectNotFoundException expected) { + catch (FetchNotFoundException expected) { assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); assertThat( expected.getIdentifier() ).isEqualTo( 1 ); } @@ -74,74 +105,93 @@ public void testGet() { } @Test + @JiraKey( "HHH-15060" ) @FailureExpected( - jiraKey = "HHH-15060", - message = "We return a proxy here for `Coin#currency`, which violates NOTE #1. The exception happens " + - "when we reference the proxy, but thats not correct" + reason = "At the moment, throws FetchNotFoundException. But the correct outcome is " + + "simply no results. This needs to trigger the join to use the fk target as part " + + "of the predicate, not the fk value" ) - public void testQueryImplicitPathDereferencePredicate() { - inTransaction( (session) -> { + public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { final String hql = "select c from Coin c where c.currency.id = 1"; - try { - session.createQuery( hql, Coin.class ).getResultList(); - fail( "Expecting ObjectNotFoundException for broken fk" ); - } - catch (ObjectNotFoundException expected) { - assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); - assertThat( expected.getIdentifier() ).isEqualTo( 1 ); - } + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).isEmpty(); + + // join may be better here. but for now, 5.x generates 2 selects here + // which is not wrong +// assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); + } ); + + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.currency.id = 2"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 1 ); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); } ); } @Test - @FailureExpected( - jiraKey = "HHH-15060", - message = "We return a proxy here for `Coin#currency`, which violates NOTE #1. The exception happens " + - "when we reference the proxy, but thats not correct" - ) - public void testQueryOwnerSelection() { - inTransaction( (session) -> { - final String hql = "select c from Coin c"; + @JiraKey( "HHH-15060" ) + public void testQueryOwnerSelection(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.id = 1"; try { - session.createQuery( hql, Coin.class ).getResultList(); - fail( "Expecting ObjectNotFoundException for broken fk" ); + //noinspection unused (debugging) + final Coin coin = session.createQuery( hql, Coin.class ).uniqueResult(); + fail( "Expecting FetchNotFoundException for broken fk" ); } - catch (ObjectNotFoundException expected) { - assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); + catch (FetchNotFoundException expected) { + assertThat( expected.getEntityName() ).endsWith( "NotFoundExceptionLogicalOneToOneTest$Currency" ); assertThat( expected.getIdentifier() ).isEqualTo( 1 ); } } ); - } - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { Coin.class, Currency.class }; + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.id = 2"; + final Coin coin = session.createQuery( hql, Coin.class ).uniqueResult(); + assertThat( Hibernate.isPropertyInitialized( coin, "currency" ) ).isTrue(); + assertThat( Hibernate.isInitialized( coin.getCurrency() ) ).isTrue(); + } ); } - @Override - protected void prepareTest() throws Exception { - super.prepareTest(); - - inTransaction( (session) -> { + @BeforeEach + public void prepareTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { Currency euro = new Currency( 1, "Euro" ); Coin fiveC = new Coin( 1, "Five cents", euro ); - session.persist( euro ); session.persist( fiveC ); + + Currency usd = new Currency( 2, "USD" ); + Coin penny = new Coin( 2, "Penny", usd ); + session.persist( usd ); + session.persist( penny ); } ); - inTransaction( (session) -> { + scope.inTransaction( (session) -> { session.createQuery( "delete Currency where id = 1" ).executeUpdate(); } ); } - @Override - protected void cleanupTest() throws Exception { - inTransaction( (session) -> { - session.createQuery( "delete Coin where id = 1" ).executeUpdate(); + @AfterEach + public void cleanupTest(SessionFactoryScope scope) throws Exception { + scope.inTransaction( (session) -> { + session.createQuery( "delete Coin" ).executeUpdate(); + session.createQuery( "delete Currency" ).executeUpdate(); } ); - - super.cleanupTest(); } @Entity(name = "Coin") diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java index bf711229368f..fc5910d61304 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java @@ -7,29 +7,32 @@ package org.hibernate.orm.test.notfound.exception; import java.io.Serializable; -import java.util.ArrayList; import java.util.List; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.ManyToOne; +import org.hibernate.FetchNotFoundException; import org.hibernate.Hibernate; import org.hibernate.ObjectNotFoundException; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.cfg.Configuration; -import org.hibernate.resource.jdbc.spi.StatementInspector; -import org.hibernate.testing.FailureExpected; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.junit.Test; +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.FailureExpected; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; import org.assertj.core.api.Assertions; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.fail; /** * Tests for `@ManyToOne @NotFound(EXCEPTION)` @@ -41,11 +44,14 @@ * * @author Steve Ebersole */ -public class NotFoundExceptionManyToOneTest extends BaseCoreFunctionalTestCase { +@DomainModel( annotatedClasses = { NotFoundExceptionManyToOneTest.Coin.class, NotFoundExceptionManyToOneTest.Currency.class } ) +@SessionFactory( useCollectingStatementInspector = true ) +public class NotFoundExceptionManyToOneTest { @Test - public void testProxy() { - inTransaction( (session) -> { + @JiraKey( "HHH-15060" ) + public void testProxy(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { // the non-existent Child final Currency proxy = session.byId( Currency.class ).getReference( 1 ); try { @@ -60,148 +66,130 @@ public void testProxy() { } @Test - @FailureExpected( - jiraKey = "HHH-15060", - message = "ObjectNotFoundException is thrown but caught and null is returned - see " + - "org.hibernate.internal.SessionImpl.IdentifierLoadAccessImpl#doLoad" - ) - public void testGet() { - inTransaction( (session) -> { + @JiraKey( "HHH-15060" ) + public void testGet(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { try { + // should fail here loading the Coin due to missing currency (see NOTE#1) final Coin coin = session.get( Coin.class, 1 ); - coin.getCurrency().getName(); - fail( "Expecting ObjectNotFoundException for broken fk" ); + fail( "Expecting FetchNotFoundException for broken fk" ); } - catch (ObjectNotFoundException expected) { + catch (FetchNotFoundException expected) { assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + + // atm, 5.x generates 2 selects here; which wouldn't be bad, except that + // the first one contains a join + // + // what "should" happen +// assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + // what actually happens + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); } } ); } @Test - @FailureExpected( - jiraKey = "HHH-15060", - message = "EntityNotFoundException thrown rather than ObjectNotFoundException; " + - "ObjectNotFoundException is thrown but caught and then converted to EntityNotFoundException" - ) - public void testQueryImplicitPathDereferencePredicate() { + @JiraKey( "HHH-15060" ) + public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); - inTransaction( (session) -> { - final String hql = "select c from Coin c where c.currency.id = 1"; + scope.inTransaction( (session) -> { try { + final String hql = "select c from Coin c where c.currency.id = 1"; session.createQuery( hql, Coin.class ).getResultList(); - fail( "Expecting ObjectNotFoundException for broken fk" ); + + fail( "Expecting FetchNotFoundException for broken fk" ); } - catch (ObjectNotFoundException expected) { + catch (FetchNotFoundException expected) { assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + + // join may be better here. but for now, 5.x generates 2 selects here + // which is not wrong +// assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); } } ); } @Test - @FailureExpected( - jiraKey = "HHH-15060", - message = "EntityNotFoundException thrown rather than ObjectNotFoundException; " + - "ObjectNotFoundException is thrown but caught and then converted to EntityNotFoundException" - ) - public void testQueryOwnerSelection() { + @JiraKey( "HHH-15060" ) + public void testQueryOwnerSelection(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); - inTransaction( (session) -> { - final String hql = "select c from Coin c"; + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.id = 1"; try { session.createQuery( hql, Coin.class ).getResultList(); fail( "Expecting ObjectNotFoundException for broken fk" ); } - catch (ObjectNotFoundException expected) { + catch (FetchNotFoundException expected) { assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + + // join may be better here. but for now, 5.x generates 2 selects here + // which is not wrong +// assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); } } ); } @Test - @FailureExpected( - jiraKey = "HHH-15060", - message = "This one is somewhat debatable. Is this selecting the association? Or simply matching Currencies?" - ) - public void testQueryAssociationSelection() { - // NOTE: this one is not obvious - // - we are selecting the association so from that perspective, throwing the ObjectNotFoundException is nice - // - the other way to look at it is that there are simply no matching results, so nothing to return - statementInspector.clear(); - - inTransaction( (session) -> { - final String hql = "select c.currency from Coin c"; - try { - session.createQuery( hql, Currency.class ).getResultList(); - fail( "Expecting ObjectNotFoundException for broken fk" ); - } - catch (ObjectNotFoundException expected) { - assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); - assertThat( expected.getIdentifier() ).isEqualTo( 1 ); - } + @JiraKey( "HHH-15060" ) + public void testQueryAssociationSelection(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + final String hql = "select c.currency from Coin c where c.id = 1"; + final List currencies = session.createQuery( hql, Currency.class ).getResultList(); + assertThat( currencies ).isEmpty(); } ); } - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { Coin.class, Currency.class }; - } - - public static class CollectionStatementInspector implements StatementInspector { - private List queries = new ArrayList<>(); - - @Override - public String inspect(String sql) { - queries.add( sql ); - return sql; - } - - public void clear() { - queries.clear(); - } - - public List getQueries() { - return queries; - } - } - - private final CollectionStatementInspector statementInspector = new CollectionStatementInspector(); - - @Override - protected void configure(Configuration configuration) { - super.configure( configuration ); - configuration.getProperties().put( AvailableSettings.STATEMENT_INSPECTOR, statementInspector ); - } - - @Override - protected void prepareTest() throws Exception { - super.prepareTest(); - - inTransaction( (session) -> { + @BeforeEach + public void prepareTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { Currency euro = new Currency( 1, "Euro" ); Coin fiveC = new Coin( 1, "Five cents", euro ); - session.persist( euro ); session.persist( fiveC ); + + Currency usd = new Currency( 2, "USD" ); + Coin penny = new Coin( 2, "Penny", usd ); + session.persist( usd ); + session.persist( penny ); } ); - inTransaction( (session) -> { + scope.inTransaction( (session) -> { session.createQuery( "delete Currency where id = 1" ).executeUpdate(); } ); } - @Override - protected void cleanupTest() throws Exception { - inTransaction( (session) -> { - session.createQuery( "delete Coin where id = 1" ).executeUpdate(); + @AfterEach + public void cleanupTest(SessionFactoryScope scope) throws Exception { + scope.inTransaction( (session) -> { + session.createQuery( "delete Coin" ).executeUpdate(); + session.createQuery( "delete Currency" ).executeUpdate(); } ); - - super.cleanupTest(); } @Entity(name = "Coin") diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java index 0d07e76945ee..125cf536691c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java @@ -7,29 +7,31 @@ package org.hibernate.orm.test.notfound.ignore; import java.io.Serializable; -import java.util.ArrayList; import java.util.List; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.ManyToOne; import org.hibernate.Hibernate; import org.hibernate.ObjectNotFoundException; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.cfg.Configuration; -import org.hibernate.resource.jdbc.spi.StatementInspector; -import org.hibernate.testing.FailureExpected; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.junit.Test; +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.FailureExpected; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Tuple; import org.assertj.core.api.Assertions; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; /** * Tests for `@ManyToOne @NotFound(IGNORE)` @@ -41,15 +43,18 @@ * * @author Steve Ebersole */ -public class NotFoundIgnoreManyToOneTest extends BaseCoreFunctionalTestCase { +@DomainModel( annotatedClasses = { NotFoundIgnoreManyToOneTest.Coin.class, NotFoundIgnoreManyToOneTest.Currency.class } ) +@SessionFactory( useCollectingStatementInspector = true ) +public class NotFoundIgnoreManyToOneTest { @Test - public void testProxy() { - inTransaction( (session) -> { + @JiraKey( "HHH-15060" ) + public void testProxy(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { // the non-existent Child // - this is the one valid deviation from treating the broken fk as null + final Currency proxy = session.byId( Currency.class ).getReference( 1 ); try { - final Currency proxy = session.byId( Currency.class ).getReference( 1 ); Hibernate.initialize( proxy ); Assertions.fail( "Expecting ObjectNotFoundException" ); } @@ -61,62 +66,113 @@ public void testProxy() { } @Test - public void testGet() { - inTransaction( (session) -> { + @JiraKey( "HHH-15060" ) + public void testGet(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { final Coin coin = session.get( Coin.class, 1 ); assertThat( coin.getCurrency() ).isNull(); + + // atm, 5.x generates 2 selects here; which wouldn't be bad, except that + // the first one contains a join + // + // what "should" happen +// assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); +// assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + // what actually happens + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); } ); } @Test - @FailureExpected( - jiraKey = "HHH-15060", - message = "Bad results due to cross-join" - ) - public void testQueryImplicitPathDereferencePredicate() { - inTransaction( (session) -> { + @JiraKey( "HHH-15060" ) + public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { final String hql = "select c from Coin c where c.currency.id = 1"; final List coins = session.createQuery( hql, Coin.class ).getResultList(); - assertThat( coins ).hasSize( 1 ); - assertThat( coins.get( 0 ).getCurrency() ).isNull(); + assertThat( coins ).isEmpty(); + + // technically we could use a subsequent-select rather than a join... + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); } ); } @Test - public void testQueryOwnerSelection() { - inTransaction( (session) -> { + @JiraKey( "HHH-15060" ) + public void testQueryOwnerSelection(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { final String hql = "select c from Coin c"; final List coins = session.createQuery( hql, Coin.class ).getResultList(); assertThat( coins ).hasSize( 1 ); assertThat( coins.get( 0 ).getCurrency() ).isNull(); + + // at the moment this uses a subsequent-select. on the bright side, it is at least eagerly fetched. + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).contains( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " join " ); } ); } @Test + @JiraKey( "HHH-15060" ) @FailureExpected( - jiraKey = "HHH-15060", - message = "Has zero results because of inner-join - the select w/ inner-join is executed twice for some odd reason" + reason = "Has zero results because of inner-join; & the select w/ inner-join is executed twice for some odd reason" ) - public void testQueryAssociationSelection() { - inTransaction( (session) -> { + public void testQueryAssociationSelection(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final String hql = "select c.id, c.currency from Coin c"; + final List tuples = session.createQuery( hql, Tuple.class ).getResultList(); + assertThat( tuples ).hasSize( 1 ); + final Tuple tuple = tuples.get( 0 ); + assertThat( tuple.get( 0 ) ).isEqualTo( 1 ); + assertThat( tuple.get( 1 ) ).isNull(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + } ); + + statementInspector.clear(); + + // I guess this one is somewhat debatable, but for consistency I think this makes the most sense + scope.inTransaction( (session) -> { final String hql = "select c.currency from Coin c"; session.createQuery( hql, Currency.class ).getResultList(); final List currencies = session.createQuery( hql, Currency.class ).getResultList(); assertThat( currencies ).hasSize( 1 ); assertThat( currencies.get( 0 ) ).isNull(); - } ); - } - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { Coin.class, Currency.class }; + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + } ); } - @Override - protected void prepareTest() throws Exception { - super.prepareTest(); - - inTransaction( (session) -> { + @BeforeEach + protected void prepareTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { Currency euro = new Currency( 1, "Euro" ); Coin fiveC = new Coin( 1, "Five cents", euro ); @@ -124,18 +180,16 @@ protected void prepareTest() throws Exception { session.persist( fiveC ); } ); - inTransaction( (session) -> { + scope.inTransaction( (session) -> { session.createQuery( "delete Currency where id = 1" ).executeUpdate(); } ); } - @Override - protected void cleanupTest() throws Exception { - inTransaction( (session) -> { + @AfterEach + protected void dropTestData(SessionFactoryScope scope) throws Exception { + scope.inTransaction( (session) -> { session.createQuery( "delete Coin where id = 1" ).executeUpdate(); } ); - - super.cleanupTest(); } @Entity(name = "Coin") diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java index d4192de998e7..3263a43554db 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java @@ -8,26 +8,34 @@ import java.io.Serializable; import java.util.List; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.OneToOne; +import org.hibernate.FetchNotFoundException; import org.hibernate.Hibernate; import org.hibernate.ObjectNotFoundException; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; -import org.hibernate.testing.FailureExpected; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.junit.Test; +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.FailureExpected; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Tuple; import org.assertj.core.api.Assertions; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for `@ManyToOne @NotFound(IGNORE)` + * Tests for `@OneToOne @NotFound(IGNORE)` * * NOTES:
    *
  1. `@NotFound` should force the association to be eager - `Coin#currency` should be loaded immediately
  2. @@ -36,11 +44,14 @@ * * @author Steve Ebersole */ -public class NotFoundIgnoreOneToOneTest extends BaseCoreFunctionalTestCase { +@DomainModel( annotatedClasses = { NotFoundIgnoreOneToOneTest.Coin.class, NotFoundIgnoreOneToOneTest.Currency.class } ) +@SessionFactory( useCollectingStatementInspector = true ) +public class NotFoundIgnoreOneToOneTest { @Test - public void testProxy() { - inTransaction( (session) -> { + @JiraKey( "HHH-15060" ) + public void testProxy(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { // the non-existent Child // - this is the one valid deviation from treating the broken fk as null try { @@ -56,62 +67,89 @@ public void testProxy() { } @Test - public void testGet() { - inTransaction( (session) -> { - final Coin coin = session.get( Coin.class, 1 ); - assertThat( coin.getCurrency() ).isNull(); + @JiraKey( "HHH-15060" ) + public void testGet(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + try { + final Coin coin = session.get( Coin.class, 1 ); + } + catch (FetchNotFoundException expected) { + // technically we could use a subsequent-select rather than a join... + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + } } ); } @Test - @FailureExpected( - jiraKey = "HHH-15060", - message = "Bad results due to cross-join" - ) - public void testQueryImplicitPathDereferencePredicate() { - inTransaction( (session) -> { + @JiraKey( "HHH-15060" ) + @FailureExpected( reason = "No results due to bad join" ) + public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { final String hql = "select c from Coin c where c.currency.id = 1"; final List coins = session.createQuery( hql, Coin.class ).getResultList(); assertThat( coins ).hasSize( 1 ); assertThat( coins.get( 0 ).getCurrency() ).isNull(); + + // technically we could use a subsequent-select rather than a join... + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); } ); } @Test - public void testQueryOwnerSelection() { - inTransaction( (session) -> { + @JiraKey( "HHH-15060" ) + public void testQueryOwnerSelection(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { final String hql = "select c from Coin c"; final List coins = session.createQuery( hql, Coin.class ).getResultList(); assertThat( coins ).hasSize( 1 ); assertThat( coins.get( 0 ).getCurrency() ).isNull(); + + // at the moment this uses a subsequent-select. on the bright side, it is at least eagerly fetched. + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " from Coin " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).contains( " from Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " join " ); } ); } @Test - @FailureExpected( - jiraKey = "HHH-15060", - message = "Has zero results because of inner-join - the select w/ inner-join is executed twice for some odd reason" - ) - public void testQueryAssociationSelection() { - inTransaction( (session) -> { + @JiraKey( "HHH-15060" ) + @FailureExpected( reason = "No results because of join" ) + public void testQueryAssociationSelection(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + final String hql = "select c.id, c.currency from Coin c"; + final List tuples = session.createQuery( hql, Tuple.class ).getResultList(); + assertThat( tuples ).hasSize( 1 ); + final Tuple tuple = tuples.get( 0 ); + assertThat( tuple.get( 0 ) ).isEqualTo( 1 ); + assertThat( tuple.get( 1 ) ).isNull(); + } ); + + scope.inTransaction( (session) -> { final String hql = "select c.currency from Coin c"; - session.createQuery( hql, Currency.class ).getResultList(); final List currencies = session.createQuery( hql, Currency.class ).getResultList(); assertThat( currencies ).hasSize( 1 ); assertThat( currencies.get( 0 ) ).isNull(); } ); } - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { Coin.class, Currency.class }; - } - - @Override - protected void prepareTest() throws Exception { - super.prepareTest(); - - inTransaction( (session) -> { + @BeforeEach + public void prepareTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { Currency euro = new Currency( 1, "Euro" ); Coin fiveC = new Coin( 1, "Five cents", euro ); @@ -119,18 +157,16 @@ protected void prepareTest() throws Exception { session.persist( fiveC ); } ); - inTransaction( (session) -> { + scope.inTransaction( (session) -> { session.createQuery( "delete Currency where id = 1" ).executeUpdate(); } ); } - @Override - protected void cleanupTest() throws Exception { - inTransaction( (session) -> { + @AfterEach + public void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { session.createQuery( "delete Coin where id = 1" ).executeUpdate(); } ); - - super.cleanupTest(); } @Entity(name = "Coin") diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java index eb16c23d6f99..69a1552a7211 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java @@ -19,23 +19,28 @@ import javax.persistence.ManyToOne; import javax.persistence.OneToMany; +import org.hibernate.Hibernate; import org.hibernate.LazyInitializationException; import org.hibernate.annotations.JoinColumnOrFormula; import org.hibernate.annotations.JoinFormula; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; import org.hibernate.cfg.AnnotationBinder; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.logger.LoggerInspectionRule; import org.hibernate.testing.logger.Triggerable; +import org.hibernate.testing.transaction.TransactionUtil; +import org.hibernate.testing.transaction.TransactionUtil2; import org.junit.Rule; import org.junit.Test; import org.jboss.logging.Logger; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -82,25 +87,21 @@ protected void afterEntityManagerFactoryBuilt() { } @Test - public void testLazyLoading() { + public void testLoading() { assertFalse( triggerable.wasTriggered() ); - List stocks = doInJPA( this::entityManagerFactory, entityManager -> { - return entityManager.createQuery( - "SELECT s FROM Stock s", Stock.class ) - .getResultList(); + List stocks = TransactionUtil2.fromTransaction( entityManagerFactory().unwrap( SessionFactoryImplementor.class ), (session) -> { + return session.createQuery("SELECT s FROM Stock s order by id", Stock.class ).getResultList(); } ); - assertEquals( 2, stocks.size() ); - try { - assertEquals( "ABC", stocks.get( 0 ).getCodes().get( 0 ).getRefNumber() ); + assertThat( stocks ).hasSize( 2 ); - fail( "Should have thrown LazyInitializationException" ); - } - catch (LazyInitializationException expected) { + final Stock firstStock = stocks.get( 0 ); + final Stock secondStock = stocks.get( 1 ); - } + assertThat( firstStock.getCodes() ).hasSize( 1 ); + assertThat( secondStock.getCodes() ).hasSize( 0 ); } @Entity(name = "Stock") diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundManyToOneNonUpdatableNonInsertableTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundManyToOneNonUpdatableNonInsertableTest.java index 3b22a0598efb..f516b7ac69ea 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundManyToOneNonUpdatableNonInsertableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundManyToOneNonUpdatableNonInsertableTest.java @@ -28,9 +28,8 @@ import org.junit.Test; import org.junit.runner.RunWith; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; /** * @author Gail Badner @@ -70,8 +69,12 @@ public void test() { doInHibernate( this::sessionFactory, session -> { User user = session.find( User.class, ID ); - assertFalse( Hibernate.isPropertyInitialized( user, "lazy" ) ); - assertNull( user.getLazy() ); + assertThat( Hibernate.isPropertyInitialized( user, "lazy" ) ) + .describedAs( "Expecting `User#lazy to be (bytecode) initialized due to `@NotFound`" ) + .isTrue(); + assertThat( user.getLazy() ) + .describedAs( "Expecting `User#lazy to null due to `NotFoundAction#IGNORE`" ) + .isNull(); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneNonUpdatableNonInsertableTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneNonUpdatableNonInsertableTest.java index 33673f001f22..bab77547af96 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneNonUpdatableNonInsertableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneNonUpdatableNonInsertableTest.java @@ -33,6 +33,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -73,9 +74,14 @@ public void test() { doInHibernate( this::sessionFactory, session -> { User user = session.find( User.class, ID ); - assertFalse( Hibernate.isPropertyInitialized( user, "lazy" ) ); - assertNull( user.getLazy() ); - assertTrue( Hibernate.isPropertyInitialized( user, "lazy" ) ); + + assertThat( Hibernate.isPropertyInitialized( user, "lazy" ) ) + .describedAs( "Expecting `User#lazy to be (bytecode) initialized due to `@NotFound`" ) + .isTrue(); + assertThat( user.getLazy() ) + .describedAs( "Expecting `User#lazy to null due to `NotFoundAction#IGNORE`" ) + .isNull(); + } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneTest.java index 7d6676a7ac0d..c439bdc28c97 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneTest.java @@ -21,7 +21,6 @@ import org.hibernate.annotations.LazyToOneOption; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; -import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.testing.TestForIssue; @@ -31,11 +30,8 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static org.hamcrest.CoreMatchers.is; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; /** * @author Gail Badner @@ -86,10 +82,10 @@ public void test() { this::sessionFactory, session -> { User user = session.find( User.class, ID ); - assertThat( sqlInterceptor.getQueryCount(), is( 1 ) ); - assertFalse( Hibernate.isPropertyInitialized( user, "lazy" ) ); + assertThat( sqlInterceptor.getQueryCount() ).isEqualTo( 2 ); + assertThat( Hibernate.isPropertyInitialized( user, "lazy" ) ).isTrue(); - assertNull( user.getLazy() ); + assertThat( user.getLazy() ).isNull(); } ); } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java index ed6b86897af0..751782461012 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java @@ -143,21 +143,21 @@ private static void prepareSchemaExport( SessionFactoryImplementor sessionFactory, MetadataImplementor model, boolean createSecondarySchemas) { - final Map baseProperties = sessionFactory.getProperties(); - - final ActionGrouping grouping = ActionGrouping.interpret( baseProperties ); - if ( grouping.getDatabaseAction() == Action.NONE && grouping.getScriptAction() == Action.NONE ) { + final ActionGrouping grouping = ActionGrouping.interpret( sessionFactory.getProperties() ); + if ( grouping.getDatabaseAction() != Action.NONE || grouping.getScriptAction() != Action.NONE ) { + // the properties contained explicit settings for auto schema tooling; skip here as part of + // @SessionFactory handling return; } - final HashMap settings = new HashMap<>( baseProperties ); - settings.put( AvailableSettings.HBM2DDL_DATABASE_ACTION, Action.CREATE_DROP ); + final HashMap settings = new HashMap<>( sessionFactory.getProperties() ); + settings.put( AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION, Action.CREATE_DROP ); if ( createSecondarySchemas ) { if ( !( model.getDatabase().getDialect().canCreateSchema() ) ) { throw new UnsupportedOperationException( model.getDatabase().getDialect() + " does not support schema creation" ); } - settings.put( AvailableSettings.HBM2DDL_CREATE_SCHEMAS, true ); + settings.put( AvailableSettings.JAKARTA_HBM2DDL_CREATE_SCHEMAS, true ); } final StandardServiceRegistry serviceRegistry = model.getMetadataBuildingOptions().getServiceRegistry(); From b1e590c3bcbb749fdeaab6d1058e3f3f1757303c Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Fri, 11 Feb 2022 13:26:12 -0600 Subject: [PATCH 017/201] HHH-15060 - Associations with @NotFound should always be left joined when de-referenced in HQL/Criteria - `@NotFound` no longer exports a physical foreign-key - tests showing bugs and inconsistencies wrt `@NotFound` handling - added `FetchNotFoundException` - force association to EAGER --- .../src/main/java/org/hibernate/annotations/FetchMode.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java b/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java index e630814324e9..b9ca4abc83c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java @@ -7,8 +7,8 @@ package org.hibernate.annotations; /** - * Fetch options on associations. Defines more of the "how" of fetching, whereas JPA {@link javax.persistence.FetchType} - * focuses on the "when". + * Defines how the association should be fetched, compared to + * {@link javax.persistence.FetchType} which defines when it should be fetched * * @author Emmanuel Bernard */ From 1097914917f5b943f841af3cf33a145b9ad861f1 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 13 Jan 2022 12:06:14 +0100 Subject: [PATCH 018/201] HHH-15060 Add test for issue --- .../test/query/TypedQueryResultListTest.java | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/query/TypedQueryResultListTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TypedQueryResultListTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TypedQueryResultListTest.java new file mode 100644 index 000000000000..967862b38525 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TypedQueryResultListTest.java @@ -0,0 +1,112 @@ +package org.hibernate.jpa.test.query; + +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.TypedQuery; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +public class TypedQueryResultListTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Parent.class }; + } + + @Test + public void testIt() { + doInJPA( this::entityManagerFactory, entityManager -> { + Parent parent1 = new Parent( 1, "usr1", null ); + Parent parent2 = new Parent( 1, "usr2", null ); + entityManager.persist( parent1 ); + entityManager.persist( parent2 ); + + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists (select 1 " + + "from Parent otherParent " + + "where lower(otherParent.text) like :name and (parent.id = otherParent.sourceParent.id or parent.number = otherParent.number))", + Parent.class + ); + + query.setParameter( "name", "usr1" ); + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 2 ) ); + } ); + + + } + + @Test + public void testIt2() { + doInJPA( this::entityManagerFactory, entityManager -> { + Parent parent1 = new Parent( 1, "usr1", null ); + Parent parent2 = new Parent( 1, "usr2", null ); + entityManager.persist( parent1 ); + entityManager.persist( parent2 ); + + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists (select 1 " + + "from Parent otherParent " + + "where lower(otherParent.text) like :name and (parent = otherParent.sourceParent or parent.number = otherParent.number))", + Parent.class + ); + + query.setParameter( "name", "usr1" ); + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 2 ) ); + } ); + + + } + + @Entity(name = "Parent") + public static class Parent { + + @Id + @GeneratedValue + private int id; + + @Column(name = "NUMBER", nullable = false, precision = 9) + private Integer number; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "SOURCE_ID", referencedColumnName = "ID") + @NotFound(action = NotFoundAction.IGNORE) + private Parent sourceParent; + + @Column(name = "TEXT", nullable = false, length = 20) + private String text; + + Parent() { + } + + public Parent(Integer num, String txt, Parent source) { + this.number = num; + this.text = txt; + this.sourceParent = source; + } + + public int getId() { + return id; + } + } + +} From c200eaa2b3afe2f73a115836b7f70478b406c0c6 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 13 Jan 2022 15:34:08 +0100 Subject: [PATCH 019/201] HHH-15060 Associations with @NotFound should always be left joined when de-referenced in HQL/Criteria --- .../hql/internal/ast/tree/DotNode.java | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java index 7400d42e859c..35c76b2b13a0 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java @@ -393,6 +393,7 @@ private void dereferenceEntity( String property = propertyName; final boolean joinIsNeeded; + if ( isDotNode( parent ) ) { // our parent is another dot node, meaning we are being further dereferenced. // thus we need to generate a join unless the association is non-nullable and @@ -401,8 +402,14 @@ private void dereferenceEntity( property = parentAsDotNode.propertyName; joinIsNeeded = generateJoin && ( entityType.isNullable() || - !isPropertyEmbeddedInJoinProperties( parentAsDotNode.propertyName ) + !isPropertyEmbeddedInJoinProperties( parentAsDotNode.propertyName ) ); + if ( generateJoin + && implicitJoin + && entityType.isNullable() + && isReferenceToPrimaryKey( parentAsDotNode.propertyName, entityType ) ) { + joinType = JoinType.LEFT_OUTER_JOIN; + } } else if ( !getWalker().isSelectStatement() ) { // in non-select queries, the only time we should need to join is if we are in a subquery from clause @@ -430,6 +437,42 @@ else if ( parentPredicate != null ) { } + /** + * Is the given property name a reference to the primary key of the associated + * entity construed by the given entity type? + *

    + * For example, consider a fragment like order.customer.id + * (where order is a from-element alias). Here, we'd have: + * propertyName = "id" AND + * owningType = ManyToOneType(Customer) + * and are being asked to determine whether "customer.id" is a reference + * to customer's PK... + * + * @param propertyName The name of the property to check. + * @param owningType The type represeting the entity "owning" the property + * + * @return True if propertyName references the entity's (owningType->associatedEntity) + * primary key; false otherwise. + */ + private boolean isReferenceToPrimaryKey(String propertyName, EntityType owningType) { + EntityPersister persister = getSessionFactoryHelper() + .getFactory().getMetamodel().entityPersister( owningType.getAssociatedEntityName() ); + if ( persister.getEntityMetamodel().hasNonIdentifierPropertyNamedId() ) { + // only the identifier property field name can be a reference to the associated entity's PK... + return propertyName.equals( persister.getIdentifierPropertyName() ) && owningType.isReferenceToPrimaryKey(); + } + // here, we have two possibilities: + // 1) the property-name matches the explicitly identifier property name + // 2) the property-name matches the implicit 'id' property name + // the referenced node text is the special 'id' + if ( EntityPersister.ENTITY_ID.equals( propertyName ) ) { + return owningType.isReferenceToPrimaryKey(); + } + String keyPropertyName = getSessionFactoryHelper().getIdentifierOrUniqueKeyPropertyName( owningType ); + return keyPropertyName != null && keyPropertyName.equals( propertyName ) && owningType.isReferenceToPrimaryKey(); + } + + private static boolean isDotNode(AST n) { return n != null && n.getType() == SqlTokenTypes.DOT; } From d530de492b2bc1e423ad88d15f63f1b37a62d6d0 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Wed, 23 Feb 2022 13:44:47 -0600 Subject: [PATCH 020/201] HHH-15060 - Associations with @NotFound should always be left joined when de-referenced in HQL/Criteria - `@NotFound` no longer exports a physical foreign-key - tests showing bugs and inconsistencies wrt `@NotFound` handling - added `FetchNotFoundException` - consider a to-one nullable if either: - explicitly marked nullable - `@NotFound` is specified, whether IGNORE or EXCEPTION - force association to EAGER --- .../org/hibernate/type/ManyToOneType.java | 31 +++++++++++++++---- .../java/org/hibernate/type/TypeFactory.java | 25 +++++++++++++++ 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java index 11eee9bd490d..bc99c7c0d151 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java @@ -19,7 +19,12 @@ import org.hibernate.annotations.NotFoundAction; import org.hibernate.engine.internal.ForeignKeys; import org.hibernate.engine.jdbc.Size; -import org.hibernate.engine.spi.*; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.EntityUniqueKey; +import org.hibernate.engine.spi.Mapping; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Loadable; @@ -30,6 +35,7 @@ */ public class ManyToOneType extends EntityType { private final String propertyName; + private final boolean nullable; private final NotFoundAction notFoundAction; private boolean isLogicalOneToOne; @@ -57,7 +63,7 @@ public ManyToOneType(TypeFactory.TypeScope scope, String referencedEntityName, b /** - * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, NotFoundAction, boolean ) } instead. + * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, boolean, NotFoundAction, boolean ) } instead. */ @Deprecated public ManyToOneType( @@ -73,7 +79,7 @@ public ManyToOneType( } /** - * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, NotFoundAction, boolean ) } instead. + * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, boolean, NotFoundAction, boolean ) } instead. */ @Deprecated public ManyToOneType( @@ -85,7 +91,18 @@ public ManyToOneType( boolean unwrapProxy, NotFoundAction notFoundAction, boolean isLogicalOneToOne) { - this( scope, referencedEntityName, referenceToPrimaryKey, uniqueKeyPropertyName, null, lazy, unwrapProxy, notFoundAction, isLogicalOneToOne ); + this( + scope, + referencedEntityName, + referenceToPrimaryKey, + uniqueKeyPropertyName, + null, + notFoundAction != null, + lazy, + unwrapProxy, + notFoundAction, + isLogicalOneToOne + ); } public ManyToOneType( @@ -94,6 +111,7 @@ public ManyToOneType( boolean referenceToPrimaryKey, String uniqueKeyPropertyName, String propertyName, + boolean nullable, boolean lazy, boolean unwrapProxy, NotFoundAction notFoundAction, @@ -101,6 +119,7 @@ public ManyToOneType( super( scope, referencedEntityName, referenceToPrimaryKey, uniqueKeyPropertyName, !lazy, unwrapProxy ); this.propertyName = propertyName; this.notFoundAction = notFoundAction; + this.nullable = nullable; this.isLogicalOneToOne = isLogicalOneToOne; } @@ -109,12 +128,12 @@ public ManyToOneType(ManyToOneType original, String superTypeEntityName) { this.propertyName = original.propertyName; this.notFoundAction = original.notFoundAction; this.isLogicalOneToOne = original.isLogicalOneToOne; + this.nullable = original.nullable; } @Override public boolean isNullable() { - // this matches the original check, but this seems wrong. - return notFoundAction == NotFoundAction.IGNORE; + return notFoundAction != null; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java index b83d719d3fe1..1c97bddbad76 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java @@ -356,6 +356,31 @@ public EntityType manyToOne( referenceToPrimaryKey, uniqueKeyPropertyName, propertyName, + notFoundAction != null, + lazy, + unwrapProxy, + notFoundAction, + isLogicalOneToOne + ); + } + + public EntityType manyToOne( + String persistentClass, + boolean referenceToPrimaryKey, + String uniqueKeyPropertyName, + String propertyName, + boolean nullable, + boolean lazy, + boolean unwrapProxy, + NotFoundAction notFoundAction, + boolean isLogicalOneToOne) { + return new ManyToOneType( + typeScope, + persistentClass, + referenceToPrimaryKey, + uniqueKeyPropertyName, + propertyName, + nullable, lazy, unwrapProxy, notFoundAction, From 60283ce9eaac5214b619f8031fc39cb90554b9d2 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Wed, 23 Feb 2022 16:49:21 -0600 Subject: [PATCH 021/201] HHH-15060 - fix bad test --- .../hibernate/jpa/test/query/TypedQueryResultListTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TypedQueryResultListTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TypedQueryResultListTest.java index 967862b38525..8fdeec5c21db 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TypedQueryResultListTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TypedQueryResultListTest.java @@ -84,15 +84,15 @@ public static class Parent { @GeneratedValue private int id; - @Column(name = "NUMBER", nullable = false, precision = 9) + @Column(name = "num", nullable = false, precision = 9) private Integer number; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "SOURCE_ID", referencedColumnName = "ID") + @JoinColumn(name = "source_fk", referencedColumnName = "id") @NotFound(action = NotFoundAction.IGNORE) private Parent sourceParent; - @Column(name = "TEXT", nullable = false, length = 20) + @Column(name = "txt", nullable = false, length = 20) private String text; Parent() { From c2bc5991dbb4bb71baf18fe3580eb29ce5a52aaa Mon Sep 17 00:00:00 2001 From: Brian Stansberry Date: Fri, 18 Feb 2022 06:56:04 -0600 Subject: [PATCH 022/201] HHH-15084 Remove use of BeanManager.createInjectionTarget(AnnotatedType) --- .../beans/container/internal/JpaCompliantLifecycleStrategy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/JpaCompliantLifecycleStrategy.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/JpaCompliantLifecycleStrategy.java index 4d76417fa88f..f43765f30d76 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/JpaCompliantLifecycleStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/JpaCompliantLifecycleStrategy.java @@ -113,7 +113,7 @@ public void initialize() { } try { - this.injectionTarget = beanManager.createInjectionTarget( annotatedType ); + this.injectionTarget = beanManager.getInjectionTargetFactory( annotatedType ).createInjectionTarget( (Bean) null ); this.creationalContext = beanManager.createCreationalContext( null ); this.beanInstance = this.injectionTarget.produce( creationalContext ); From 99cf560f9f81452bb5f92e3196431a3f5793f5af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 1 Mar 2022 10:32:14 +0100 Subject: [PATCH 023/201] HHH-15094 Remove duplicate DTD descriptor --- .../stax/LocalXmlResourceResolver.java | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java index 89a749a2d154..d76666c300e4 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java @@ -77,16 +77,6 @@ else if ( LEGACY_HBM_DTD_MAPPING.matches( publicID, systemID ) ) { ); return openUrlStream( HBM_DTD_MAPPING.getMappedLocalUrl() ); } - else if ( LEGACY2_HBM_DTD_MAPPING.matches( publicID, systemID ) ) { - DeprecationLogger.DEPRECATION_LOGGER.recognizedObsoleteHibernateNamespace( - LEGACY2_HBM_DTD_MAPPING.getIdentifierBase(), - HBM_DTD_MAPPING.getIdentifierBase() - ); - log.debug( - "Recognized legacy hibernate-mapping identifier; attempting to resolve on classpath under org/hibernate/" - ); - return openUrlStream( HBM_DTD_MAPPING.getMappedLocalUrl() ); - } else if ( CFG_DTD_MAPPING.matches( publicID, systemID ) ) { log.debug( "Recognized hibernate-configuration identifier; attempting to resolve on classpath under org/hibernate/" @@ -158,7 +148,7 @@ private InputStream resolveInLocalNamespace(String path) { "http://xmlns.jcp.org/xml/ns/persistence/orm", "org/hibernate/jpa/orm_2_1.xsd" ); - + /** * Maps the namespace for the orm.xml xsd for Jakarta Persistence 2.2 */ @@ -174,7 +164,7 @@ private InputStream resolveInLocalNamespace(String path) { "https://jakarta.ee/xml/ns/persistence/orm", "org/hibernate/jpa/orm_3_0.xsd" ); - + public static final NamespaceSchemaMapping HBM_XSD_MAPPING = new NamespaceSchemaMapping( "http://www.hibernate.org/xsd/orm/hbm", "org/hibernate/xsd/mapping/legacy-mapping-4.0.xsd" @@ -196,11 +186,6 @@ private InputStream resolveInLocalNamespace(String path) { ); public static final DtdMapping LEGACY_HBM_DTD_MAPPING = new DtdMapping( - "http://www.hibernate.org/dtd/hibernate-mapping", - "org/hibernate/hibernate-mapping-3.0.dtd" - ); - - public static final DtdMapping LEGACY2_HBM_DTD_MAPPING = new DtdMapping( "http://hibernate.sourceforge.net/hibernate-mapping", "org/hibernate/hibernate-mapping-3.0.dtd" ); From a3be8c00c5f737422716af957b84448a0a7ed601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 1 Mar 2022 09:36:13 +0100 Subject: [PATCH 024/201] HHH-15094 Test LocalXmlResourceResolver --- .../stax/LocalXmlResourceResolverTest.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolverTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolverTest.java b/hibernate-core/src/test/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolverTest.java new file mode 100644 index 000000000000..f8070fca395b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolverTest.java @@ -0,0 +1,76 @@ +/* + * 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 + */ +package org.hibernate.boot.jaxb.internal.stax; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.xml.stream.XMLStreamException; + +import org.hibernate.testing.boot.ClassLoaderServiceTestingImpl; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import org.assertj.core.api.InstanceOfAssertFactories; + +/** + * Test the resolution of known XML schemas/DTDs to local resources. + *

    + * Note that when it comes to XML schemas, + * LocalXmlResourceResolver doesn't seem to be actually invoked; + * which makes sense since we set the XML schema ourselves when configuring the parser. + * So this test is probably only relevant for DTDs, but we keep tests about XML schemas too just in case. + */ +public class LocalXmlResourceResolverTest { + + private final LocalXmlResourceResolver resolver; + + public LocalXmlResourceResolverTest() { + this.resolver = new LocalXmlResourceResolver( ClassLoaderServiceTestingImpl.INSTANCE ); + } + + @ParameterizedTest + @CsvSource({ + // JPA 1.0 and 2.0 share the same namespace URI + // NOTE: Behavior differs from Hibernate ORM 6, which resolves to org/hibernate/jpa/orm_1_0.xsd + "http://java.sun.com/xml/ns/persistence/orm,org/hibernate/jpa/orm_2_0.xsd", + // JPA 2.1 and 2.2 share the same namespace URI + "http://xmlns.jcp.org/xml/ns/persistence/orm,org/hibernate/jpa/orm_2_1.xsd", + "https://jakarta.ee/xml/ns/persistence/orm,org/hibernate/jpa/orm_3_0.xsd", + + // NOTE: Hibernate ORM 5 doesn't resolve persistence.xml XSDs to local resources + // so we don't test them here. + + "http://www.hibernate.org/xsd/orm/hbm,org/hibernate/xsd/mapping/legacy-mapping-4.0.xsd", + "http://www.hibernate.org/xsd/hibernate-mapping,org/hibernate/hibernate-mapping-4.0.xsd", + "http://www.hibernate.org/xsd/orm/cfg,org/hibernate/xsd/cfg/legacy-configuration-4.0.xsd", + }) + void resolve_namespace_localResource(String namespace, String expectedLocalResource) throws XMLStreamException { + assertThat( resolver.resolveEntity( null, null, null, namespace ) ) + .asInstanceOf( InstanceOfAssertFactories.INPUT_STREAM ) + .hasSameContentAs( getClass().getClassLoader().getResourceAsStream( expectedLocalResource ) ); + } + + @ParameterizedTest + @CsvSource({ + "http://www.hibernate.org/dtd/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", + "http://hibernate.sourceforge.net/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", + "http://www.hibernate.org/dtd/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd", + "http://hibernate.sourceforge.net/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd" + }) + void resolve_dtd_localResource(String id, String expectedLocalResource) throws XMLStreamException { + // publicId + assertThat( resolver.resolveEntity( id, null, null, null ) ) + .asInstanceOf( InstanceOfAssertFactories.INPUT_STREAM ) + .hasSameContentAs( getClass().getClassLoader().getResourceAsStream( expectedLocalResource ) ); + + // systemId + assertThat( resolver.resolveEntity( null, id, null, null ) ) + .asInstanceOf( InstanceOfAssertFactories.INPUT_STREAM ) + .hasSameContentAs( getClass().getClassLoader().getResourceAsStream( expectedLocalResource ) ); + } + +} \ No newline at end of file From 24df531d7339b4fab32c58e6e6873acabaabb491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 1 Mar 2022 11:02:01 +0100 Subject: [PATCH 025/201] HHH-15094 Handle both HTTP and HTTPS versions of DTDs in LocalXmlResourceResolver --- .../stax/LocalXmlResourceResolver.java | 22 +++++++++++-------- .../stax/LocalXmlResourceResolverTest.java | 9 +++++++- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java index d76666c300e4..3b0a2251f929 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java @@ -181,22 +181,22 @@ private InputStream resolveInLocalNamespace(String path) { ); public static final DtdMapping HBM_DTD_MAPPING = new DtdMapping( - "http://www.hibernate.org/dtd/hibernate-mapping", + "www.hibernate.org/dtd/hibernate-mapping", "org/hibernate/hibernate-mapping-3.0.dtd" ); public static final DtdMapping LEGACY_HBM_DTD_MAPPING = new DtdMapping( - "http://hibernate.sourceforge.net/hibernate-mapping", + "hibernate.sourceforge.net/hibernate-mapping", "org/hibernate/hibernate-mapping-3.0.dtd" ); public static final DtdMapping CFG_DTD_MAPPING = new DtdMapping( - "http://www.hibernate.org/dtd/hibernate-configuration", + "www.hibernate.org/dtd/hibernate-configuration", "org/hibernate/hibernate-configuration-3.0.dtd" ); public static final DtdMapping LEGACY_CFG_DTD_MAPPING = new DtdMapping( - "http://hibernate.sourceforge.net/hibernate-configuration", + "hibernate.sourceforge.net/hibernate-configuration", "org/hibernate/hibernate-configuration-3.0.dtd" ); @@ -220,27 +220,31 @@ public URL getMappedLocalUrl() { } public static class DtdMapping { - private final String identifierBase; + private final String httpBase; + private final String httpsBase; private final URL localSchemaUrl; public DtdMapping(String identifierBase, String resourceName) { - this.identifierBase = identifierBase; + this.httpBase = "http://" + identifierBase; + this.httpsBase = "https://" + identifierBase; this.localSchemaUrl = LocalSchemaLocator.resolveLocalSchemaUrl( resourceName ); } public String getIdentifierBase() { - return identifierBase; + return httpBase; } public boolean matches(String publicId, String systemId) { if ( publicId != null ) { - if ( publicId.startsWith( identifierBase ) ) { + if ( publicId.startsWith( httpBase ) + || publicId.matches( httpsBase ) ) { return true; } } if ( systemId != null ) { - if ( systemId.startsWith( identifierBase ) ) { + if ( systemId.startsWith( httpBase ) + || systemId.matches( httpsBase ) ) { return true; } } diff --git a/hibernate-core/src/test/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolverTest.java b/hibernate-core/src/test/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolverTest.java index f8070fca395b..6dcb9d22265d 100644 --- a/hibernate-core/src/test/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolverTest.java +++ b/hibernate-core/src/test/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolverTest.java @@ -57,9 +57,16 @@ void resolve_namespace_localResource(String namespace, String expectedLocalResou @ParameterizedTest @CsvSource({ "http://www.hibernate.org/dtd/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", + "https://www.hibernate.org/dtd/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", + "http://hibernate.sourceforge.net/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", + "https://hibernate.sourceforge.net/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", + "http://www.hibernate.org/dtd/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd", - "http://hibernate.sourceforge.net/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd" + "https://www.hibernate.org/dtd/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd", + + "http://hibernate.sourceforge.net/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd", + "https://hibernate.sourceforge.net/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd" }) void resolve_dtd_localResource(String id, String expectedLocalResource) throws XMLStreamException { // publicId From c19a825d93d74faf8f11493fbc26fb1995724981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 1 Mar 2022 11:09:43 +0100 Subject: [PATCH 026/201] HHH-15094 Handle http://hibernate.org and https://hibernate.org for DTDs in LocalXmlResourceResolver --- .../stax/LocalXmlResourceResolver.java | 22 +++++++++++++++++++ .../stax/LocalXmlResourceResolverTest.java | 6 +++++ 2 files changed, 28 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java index 3b0a2251f929..f573bf04dbe8 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolver.java @@ -67,6 +67,12 @@ else if ( CFG_XSD_MAPPING.matches( namespace ) ) { ); return openUrlStream( HBM_DTD_MAPPING.getMappedLocalUrl() ); } + else if ( ALTERNATE_MAPPING_DTD.matches( publicID, systemID ) ) { + log.debug( + "Recognized alternate hibernate-mapping identifier; attempting to resolve on classpath under org/hibernate/" + ); + return openUrlStream( ALTERNATE_MAPPING_DTD.getMappedLocalUrl() ); + } else if ( LEGACY_HBM_DTD_MAPPING.matches( publicID, systemID ) ) { DeprecationLogger.DEPRECATION_LOGGER.recognizedObsoleteHibernateNamespace( LEGACY_HBM_DTD_MAPPING.getIdentifierBase(), @@ -83,6 +89,12 @@ else if ( CFG_DTD_MAPPING.matches( publicID, systemID ) ) { ); return openUrlStream( CFG_DTD_MAPPING.getMappedLocalUrl() ); } + else if ( ALTERNATE_CFG_DTD.matches( publicID, systemID ) ) { + log.debug( + "Recognized alternate hibernate-configuration identifier; attempting to resolve on classpath under org/hibernate/" + ); + return openUrlStream( ALTERNATE_CFG_DTD.getMappedLocalUrl() ); + } else if ( LEGACY_CFG_DTD_MAPPING.matches( publicID, systemID ) ) { DeprecationLogger.DEPRECATION_LOGGER.recognizedObsoleteHibernateNamespace( LEGACY_CFG_DTD_MAPPING.getIdentifierBase(), @@ -185,6 +197,11 @@ private InputStream resolveInLocalNamespace(String path) { "org/hibernate/hibernate-mapping-3.0.dtd" ); + public static final DtdMapping ALTERNATE_MAPPING_DTD = new DtdMapping( + "hibernate.org/dtd/hibernate-mapping", + "org/hibernate/hibernate-mapping-3.0.dtd" + ); + public static final DtdMapping LEGACY_HBM_DTD_MAPPING = new DtdMapping( "hibernate.sourceforge.net/hibernate-mapping", "org/hibernate/hibernate-mapping-3.0.dtd" @@ -195,6 +212,11 @@ private InputStream resolveInLocalNamespace(String path) { "org/hibernate/hibernate-configuration-3.0.dtd" ); + public static final DtdMapping ALTERNATE_CFG_DTD = new DtdMapping( + "hibernate.org/dtd/hibernate-configuration", + "org/hibernate/hibernate-configuration-3.0.dtd" + ); + public static final DtdMapping LEGACY_CFG_DTD_MAPPING = new DtdMapping( "hibernate.sourceforge.net/hibernate-configuration", "org/hibernate/hibernate-configuration-3.0.dtd" diff --git a/hibernate-core/src/test/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolverTest.java b/hibernate-core/src/test/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolverTest.java index 6dcb9d22265d..dbeb0acf3f54 100644 --- a/hibernate-core/src/test/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolverTest.java +++ b/hibernate-core/src/test/java/org/hibernate/boot/jaxb/internal/stax/LocalXmlResourceResolverTest.java @@ -59,12 +59,18 @@ void resolve_namespace_localResource(String namespace, String expectedLocalResou "http://www.hibernate.org/dtd/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", "https://www.hibernate.org/dtd/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", + "http://hibernate.org/dtd/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", + "https://hibernate.org/dtd/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", + "http://hibernate.sourceforge.net/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", "https://hibernate.sourceforge.net/hibernate-mapping,org/hibernate/hibernate-mapping-3.0.dtd", "http://www.hibernate.org/dtd/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd", "https://www.hibernate.org/dtd/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd", + "http://hibernate.org/dtd/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd", + "https://hibernate.org/dtd/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd", + "http://hibernate.sourceforge.net/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd", "https://hibernate.sourceforge.net/hibernate-configuration,org/hibernate/hibernate-configuration-3.0.dtd" }) From 3de27995328e28fbf5d3959e28dabfa6ba3a2f7f Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Sat, 26 Feb 2022 12:46:22 -0600 Subject: [PATCH 027/201] HHH-15060 - Associations with @NotFound should always be left joined when de-referenced in HQL/Criteria - code clean-up --- .../hql/internal/ast/HqlSqlWalker.java | 2 +- .../hql/internal/ast/tree/FromElement.java | 4 +++ .../internal/ast/tree/FromElementType.java | 21 ++++++++++++++++ .../ast/util/SessionFactoryHelper.java | 1 + .../java/org/hibernate/mapping/ManyToOne.java | 1 - .../org/hibernate/type/ManyToOneType.java | 10 +++----- .../java/org/hibernate/type/TypeFactory.java | 25 ------------------- 7 files changed, 30 insertions(+), 34 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java index 4174070e6fb4..a8d3de42edf3 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java @@ -724,7 +724,7 @@ protected boolean isNonQualifiedPropertyRef(AST ident) { final FromElement fromElement = (FromElement) fromElements.get( 0 ); try { LOG.tracev( "Attempting to resolve property [{0}] as a non-qualified ref", identText ); - return fromElement.getPropertyMapping( identText ).toType( identText ) != null; + return fromElement.isNonQualifiedPropertyRef( identText ); } catch (QueryException e) { // Should mean that no such property was found diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java index 969c1507ba4d..179d542a0c40 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java @@ -471,6 +471,10 @@ public FromElement getFetchOrigin() { public static final String DISCRIMINATOR_PROPERTY_NAME = "class"; private TypeDiscriminatorMetadata typeDiscriminatorMetadata; + public boolean isNonQualifiedPropertyRef(String identifier) { + return elementType.isNonQualifiedPropertyRef( identifier ); + } + private static class TypeDiscriminatorMetadataImpl implements TypeDiscriminatorMetadata { private final DiscriminatorMetadata persisterDiscriminatorMetadata; private final String alias; diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java index 23e8ce656f13..24e7801217de 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java @@ -660,6 +660,27 @@ public String[] toColumns(final String tableAlias) { }; } + /** + * Does the incoming identifier represent a non-qualified attribute reference. + * + * E.g. `... from Order where total > :discountThreshold`. We are checking + * the identifier `total` and see it is an attribute of `Order`, so it is in fact + * an unqualified reference to that attribute + */ + public boolean isNonQualifiedPropertyRef(String identifier) { + if ( queryableCollection == null ) { + assert persister != null; + try { + return persister.getPropertyType( identifier ) != null; + } + catch (QueryException qe) { + return false; + } + } + + return false; + } + private class SpecialManyToManyCollectionPropertyMapping implements PropertyMapping { @Override public Type getType() { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SessionFactoryHelper.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SessionFactoryHelper.java index 355632ae98d6..ef0e32d3cf87 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SessionFactoryHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/SessionFactoryHelper.java @@ -28,6 +28,7 @@ import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.EntityType; +import org.hibernate.type.ManyToOneType; import org.hibernate.type.Type; import antlr.SemanticException; diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java b/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java index 7e87f5c25ad5..a0d25460b5bd 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java @@ -22,7 +22,6 @@ * @author Gavin King */ public class ManyToOne extends ToOne { - private boolean ignoreNotFound; private NotFoundAction notFoundAction; private boolean isLogicalOneToOne; diff --git a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java index bc99c7c0d151..3846817d52a0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java @@ -35,7 +35,7 @@ */ public class ManyToOneType extends EntityType { private final String propertyName; - private final boolean nullable; +// private final boolean nullable; private final NotFoundAction notFoundAction; private boolean isLogicalOneToOne; @@ -63,7 +63,7 @@ public ManyToOneType(TypeFactory.TypeScope scope, String referencedEntityName, b /** - * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, boolean, NotFoundAction, boolean ) } instead. + * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, NotFoundAction, boolean ) } instead. */ @Deprecated public ManyToOneType( @@ -79,7 +79,7 @@ public ManyToOneType( } /** - * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, boolean, NotFoundAction, boolean ) } instead. + * @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, NotFoundAction, boolean ) } instead. */ @Deprecated public ManyToOneType( @@ -97,7 +97,6 @@ public ManyToOneType( referenceToPrimaryKey, uniqueKeyPropertyName, null, - notFoundAction != null, lazy, unwrapProxy, notFoundAction, @@ -111,7 +110,6 @@ public ManyToOneType( boolean referenceToPrimaryKey, String uniqueKeyPropertyName, String propertyName, - boolean nullable, boolean lazy, boolean unwrapProxy, NotFoundAction notFoundAction, @@ -119,7 +117,6 @@ public ManyToOneType( super( scope, referencedEntityName, referenceToPrimaryKey, uniqueKeyPropertyName, !lazy, unwrapProxy ); this.propertyName = propertyName; this.notFoundAction = notFoundAction; - this.nullable = nullable; this.isLogicalOneToOne = isLogicalOneToOne; } @@ -128,7 +125,6 @@ public ManyToOneType(ManyToOneType original, String superTypeEntityName) { this.propertyName = original.propertyName; this.notFoundAction = original.notFoundAction; this.isLogicalOneToOne = original.isLogicalOneToOne; - this.nullable = original.nullable; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java index 1c97bddbad76..b83d719d3fe1 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java @@ -356,31 +356,6 @@ public EntityType manyToOne( referenceToPrimaryKey, uniqueKeyPropertyName, propertyName, - notFoundAction != null, - lazy, - unwrapProxy, - notFoundAction, - isLogicalOneToOne - ); - } - - public EntityType manyToOne( - String persistentClass, - boolean referenceToPrimaryKey, - String uniqueKeyPropertyName, - String propertyName, - boolean nullable, - boolean lazy, - boolean unwrapProxy, - NotFoundAction notFoundAction, - boolean isLogicalOneToOne) { - return new ManyToOneType( - typeScope, - persistentClass, - referenceToPrimaryKey, - uniqueKeyPropertyName, - propertyName, - nullable, lazy, unwrapProxy, notFoundAction, From e46752c05b03125d616e68c5066ce2e235510e7a Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Tue, 1 Mar 2022 11:23:57 -0600 Subject: [PATCH 028/201] HHH-15060 - Fix handling of associations with @NotFound --- .../org/hibernate/cfg/AnnotationBinder.java | 85 +++++----- .../hql/internal/ast/tree/DotNode.java | 65 ++++---- .../org/hibernate/internal/SessionImpl.java | 12 +- .../java/org/hibernate/type/EntityType.java | 7 + .../org/hibernate/type/ManyToOneType.java | 12 +- .../java/org/hibernate/type/OneToOneType.java | 6 + .../test/query/TypedQueryResultListTest.java | 118 ++++++++++++-- .../NotFoundExceptionLogicalOneToOneTest.java | 148 ++++++++++++++---- .../NotFoundExceptionManyToOneTest.java | 54 ++++--- .../ignore/NotFoundIgnoreManyToOneTest.java | 68 ++++++-- .../ignore/NotFoundIgnoreOneToOneTest.java | 41 +++-- .../test/hql/ASTParserLoadingTest.java | 5 + .../java/org/hibernate/test/hql/HQLTest.java | 5 + 13 files changed, 464 insertions(+), 162 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index ca6292429685..4ea840ebb2f2 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -170,6 +170,7 @@ import org.hibernate.mapping.ToOne; import org.hibernate.mapping.UnionSubclass; +import static org.hibernate.cfg.BinderHelper.isEmptyAnnotationValue; import static org.hibernate.internal.CoreLogging.messageLogger; /** @@ -691,7 +692,7 @@ else if ( InheritanceType.JOINED.equals( inheritanceState.getType() ) ) { SimpleValue key = new DependantValue( context, jsc.getTable(), jsc.getIdentifier() ); jsc.setKey( key ); ForeignKey fk = clazzToProcess.getAnnotation( ForeignKey.class ); - if ( fk != null && !BinderHelper.isEmptyAnnotationValue( fk.name() ) ) { + if ( fk != null && !isEmptyAnnotationValue( fk.name() ) ) { key.setForeignKeyName( fk.name() ); } else { @@ -1371,7 +1372,7 @@ private static void bindTypeDef(TypeDef defAnn, MetadataBuildingContext context) params.setProperty( param.name(), param.value() ); } - if ( BinderHelper.isEmptyAnnotationValue( defAnn.name() ) && defAnn.defaultForType().equals( void.class ) ) { + if ( isEmptyAnnotationValue( defAnn.name() ) && defAnn.defaultForType().equals( void.class ) ) { throw new AnnotationException( "Either name or defaultForType (or both) attribute should be set in TypeDef having typeClass " + defAnn.typeClass().getName() @@ -1379,7 +1380,7 @@ private static void bindTypeDef(TypeDef defAnn, MetadataBuildingContext context) } final String typeBindMessageF = "Binding type definition: %s"; - if ( !BinderHelper.isEmptyAnnotationValue( defAnn.name() ) ) { + if ( !isEmptyAnnotationValue( defAnn.name() ) ) { if ( LOG.isDebugEnabled() ) { LOG.debugf( typeBindMessageF, defAnn.name() ); } @@ -1794,11 +1795,12 @@ private static void processElementAnnotations( ); } + final NotFound notFound = property.getAnnotation( NotFound.class ); + final NotFoundAction notFoundAction = notFound == null ? null : notFound.action(); + final boolean hasNotFound = notFoundAction != null; + checkFetchModeAgainstNotFound( propertyHolder.getEntityName(), property.getName(), hasNotFound, ann.fetch() ); + Cascade hibernateCascade = property.getAnnotation( Cascade.class ); - NotFound notFound = property.getAnnotation( NotFound.class ); - NotFoundAction notFoundAction = notFound == null ? null : notFound.action(); - boolean ignoreNotFound = notFoundAction == NotFoundAction.IGNORE; - matchIgnoreNotFoundWithFetchType(propertyHolder.getEntityName(), property.getName(), ignoreNotFound, ann.fetch()); OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ); JoinTable assocTable = propertyHolder.getJoinTable( property ); @@ -1814,10 +1816,9 @@ private static void processElementAnnotations( // is mandatory (even if the association has optional=true). // If a @MapsId association has optional=true and is mapped with @NotFound(IGNORE) then // the association is optional. - final boolean mandatory = - !ann.optional() || - property.isAnnotationPresent( Id.class ) || - ( property.isAnnotationPresent( MapsId.class ) && !ignoreNotFound ); + final boolean mandatory = !ann.optional() + || property.isAnnotationPresent( Id.class ) + || ( property.isAnnotationPresent( MapsId.class ) && !hasNotFound ); bindManyToOne( getCascadeStrategy( ann.cascade(), hibernateCascade, false, forcePersist ), joinColumns, @@ -1850,10 +1851,12 @@ else if ( property.isAnnotationPresent( OneToOne.class ) ) { final boolean hasPkjc = property.isAnnotationPresent( PrimaryKeyJoinColumn.class ) || property.isAnnotationPresent( PrimaryKeyJoinColumns.class ); boolean trueOneToOne = hasPkjc; - Cascade hibernateCascade = property.getAnnotation( Cascade.class ); - NotFound notFound = property.getAnnotation( NotFound.class ); - NotFoundAction notFoundAction = notFound == null ? null : notFound.action(); - boolean ignoreNotFound = notFoundAction == NotFoundAction.IGNORE; + final Cascade hibernateCascade = property.getAnnotation( Cascade.class ); + final NotFound notFound = property.getAnnotation( NotFound.class ); + final NotFoundAction notFoundAction = notFound == null ? null : notFound.action(); + final boolean hasNotFound = notFoundAction != null; + checkFetchModeAgainstNotFound( propertyHolder.getEntityName(), property.getName(), hasNotFound, ann.fetch() ); + // MapsId means the columns belong to the pk; // A @MapsId association (obviously) must be non-null when the entity is first persisted. // If a @MapsId association is not mapped with @NotFound(IGNORE), then the association @@ -1861,14 +1864,14 @@ else if ( property.isAnnotationPresent( OneToOne.class ) ) { // If a @MapsId association has optional=true and is mapped with @NotFound(IGNORE) then // the association is optional. // @OneToOne(optional = true) with @PKJC makes the association optional. - final boolean mandatory = - !ann.optional() || - property.isAnnotationPresent( Id.class ) || - ( property.isAnnotationPresent( MapsId.class ) && !ignoreNotFound ); - matchIgnoreNotFoundWithFetchType(propertyHolder.getEntityName(), property.getName(), ignoreNotFound, ann.fetch()); - OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); - boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ); - JoinTable assocTable = propertyHolder.getJoinTable( property ); + final boolean mandatory = !ann.optional() + || property.isAnnotationPresent( Id.class ) + || ( property.isAnnotationPresent( MapsId.class ) && !hasNotFound ); + checkFetchModeAgainstNotFound( propertyHolder.getEntityName(), property.getName(), hasNotFound, ann.fetch() ); + + final OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); + final boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ); + final JoinTable assocTable = propertyHolder.getJoinTable( property ); if ( assocTable != null ) { Join join = propertyHolder.addJoin( assocTable, false ); for ( Ejb3JoinColumn joinColumn : joinColumns ) { @@ -2575,13 +2578,13 @@ private static void bindJoinedTableAssociation( if ( jpaIndexes != null && jpaIndexes.length > 0 ) { associationTableBinder.setJpaIndex( jpaIndexes ); } - if ( !BinderHelper.isEmptyAnnotationValue( schema ) ) { + if ( !isEmptyAnnotationValue( schema ) ) { associationTableBinder.setSchema( schema ); } - if ( !BinderHelper.isEmptyAnnotationValue( catalog ) ) { + if ( !isEmptyAnnotationValue( catalog ) ) { associationTableBinder.setCatalog( catalog ); } - if ( !BinderHelper.isEmptyAnnotationValue( tableName ) ) { + if ( !isEmptyAnnotationValue( tableName ) ) { associationTableBinder.setName( tableName ); } associationTableBinder.setUniqueConstraints( uniqueConstraints ); @@ -3093,7 +3096,7 @@ private static void bindManyToOne( } if ( property.isAnnotationPresent( ManyToOne.class ) && joinColumn != null - && ! BinderHelper.isEmptyAnnotationValue( joinColumn.name() ) + && ! isEmptyAnnotationValue( joinColumn.name() ) && joinColumn.name().equals( columnName ) && !property.isAnnotationPresent( MapsId.class ) ) { hasSpecjManyToOne = true; @@ -3268,7 +3271,7 @@ private static void bindOneToOne( } } } - if ( trueOneToOne || mapToPK || !BinderHelper.isEmptyAnnotationValue( mappedBy ) ) { + if ( trueOneToOne || mapToPK || !isEmptyAnnotationValue( mappedBy ) ) { //is a true one-to-one //FIXME referencedColumnName ignored => ordering may fail. OneToOneSecondPass secondPass = new OneToOneSecondPass( @@ -3289,19 +3292,25 @@ private static void bindOneToOne( secondPass.doSecondPass( context.getMetadataCollector().getEntityBindingMap() ); } else { - context.getMetadataCollector().addSecondPass( - secondPass, - BinderHelper.isEmptyAnnotationValue( mappedBy ) - ); + context.getMetadataCollector().addSecondPass( secondPass, isEmptyAnnotationValue( mappedBy ) ); } } else { //has a FK on the table bindManyToOne( - cascadeStrategy, joinColumns, optional, notFoundAction, cascadeOnDelete, + cascadeStrategy, + joinColumns, + optional, + notFoundAction, + cascadeOnDelete, targetEntity, - propertyHolder, inferredData, true, isIdentifierMapper, inSecondPass, - propertyBinder, context + propertyHolder, + inferredData, + true, + isIdentifierMapper, + inSecondPass, + propertyBinder, + context ); } } @@ -3645,12 +3654,12 @@ private static boolean hasAnnotationsOnIdClass(XClass idClass) { return false; } - private static void matchIgnoreNotFoundWithFetchType( + private static void checkFetchModeAgainstNotFound( String entity, String association, - boolean ignoreNotFound, + boolean hasNotFound, FetchType fetchType) { - if ( ignoreNotFound && fetchType == FetchType.LAZY ) { + if ( hasNotFound && fetchType == FetchType.LAZY ) { LOG.ignoreNotFoundWithFetchTypeLazy( entity, association ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java index 35c76b2b13a0..dbe4873f4473 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java @@ -18,7 +18,6 @@ import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.loader.plan.spi.EntityQuerySpace; -import org.hibernate.loader.plan.spi.QuerySpace; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; @@ -362,7 +361,7 @@ private void dereferenceCollection( } private void dereferenceEntity( - EntityType entityType, + EntityType toOneType, boolean implicitJoin, String classAlias, boolean generateJoin, @@ -393,22 +392,23 @@ private void dereferenceEntity( String property = propertyName; final boolean joinIsNeeded; - if ( isDotNode( parent ) ) { // our parent is another dot node, meaning we are being further dereferenced. // thus we need to generate a join unless the association is non-nullable and // parent refers to the associated entity's PK (because 'our' table would know the FK). parentAsDotNode = (DotNode) parent; property = parentAsDotNode.propertyName; - joinIsNeeded = generateJoin && ( - entityType.isNullable() || - !isPropertyEmbeddedInJoinProperties( parentAsDotNode.propertyName ) - ); - if ( generateJoin - && implicitJoin - && entityType.isNullable() - && isReferenceToPrimaryKey( parentAsDotNode.propertyName, entityType ) ) { - joinType = JoinType.LEFT_OUTER_JOIN; + if ( generateJoin ) { + if ( implicitJoin && ( toOneType.hasNotFoundAction() || toOneType.isNullable() ) ) { + fetch = !getWalker().isSubQuery(); + joinIsNeeded = true; + } + else { + joinIsNeeded = !isPropertyEmbeddedInJoinProperties( parentAsDotNode.propertyName ); + } + } + else { + joinIsNeeded = false; } } else if ( !getWalker().isSelectStatement() ) { @@ -429,7 +429,7 @@ else if ( parentPredicate != null ) { } if ( joinIsNeeded ) { - dereferenceEntityJoin( classAlias, entityType, implicitJoin, parent ); + dereferenceEntityJoin( classAlias, toOneType, implicitJoin, parent ); } else { dereferenceEntityIdentifier( property, parentAsDotNode ); @@ -477,8 +477,7 @@ private static boolean isDotNode(AST n) { return n != null && n.getType() == SqlTokenTypes.DOT; } - private void dereferenceEntityJoin(String classAlias, EntityType propertyType, boolean impliedJoin, AST parent) - throws SemanticException { + private void dereferenceEntityJoin(String classAlias, EntityType toOneType, boolean impliedJoin, AST parent) throws SemanticException { dereferenceType = DereferenceType.ENTITY; if ( LOG.isDebugEnabled() ) { LOG.debugf( @@ -490,17 +489,17 @@ private void dereferenceEntityJoin(String classAlias, EntityType propertyType, b ); } // Create a new FROM node for the referenced class. - String associatedEntityName = propertyType.getAssociatedEntityName(); - String tableAlias = getAliasGenerator().createName( associatedEntityName ); + final String associatedEntityName = toOneType.getAssociatedEntityName(); + final String tableAlias = getAliasGenerator().createName( associatedEntityName ); - String[] joinColumns = getColumns(); - String joinPath = getPath(); + final String[] joinColumns = getColumns(); + final String joinPath = getPath(); if ( impliedJoin && getWalker().isInFrom() ) { joinType = getWalker().getImpliedJoinType(); } - FromClause currentFromClause = getWalker().getCurrentFromClause(); + final FromClause currentFromClause = getWalker().getCurrentFromClause(); FromElement elem = currentFromClause.findJoinByPath( joinPath ); /////////////////////////////////////////////////////////////////////////////// @@ -530,9 +529,9 @@ private void dereferenceEntityJoin(String classAlias, EntityType propertyType, b // /////////////////////////////////////////////////////////////////////////////// - boolean found = elem != null; + final boolean found = elem != null; // even though we might find a pre-existing element by join path, we may not be able to reuse it... - boolean useFoundFromElement = found && canReuse( classAlias, elem ); + final boolean useFoundFromElement = found && canReuse( classAlias, elem ); if ( !useFoundFromElement ) { // If the lhs of the join is a "component join", we need to go back to the @@ -546,30 +545,30 @@ private void dereferenceEntityJoin(String classAlias, EntityType propertyType, b throw new QueryException( "Unable to locate appropriate lhs" ); } - String role = lhsFromElement.getClassName() + "." + propertyName; + final String role = lhsFromElement.getClassName() + "." + propertyName; - JoinSequence joinSequence; + final JoinSequence joinSequence; if ( joinColumns.length == 0 && lhsFromElement instanceof EntityQuerySpace ) { // When no columns are available, this is a special join that involves multiple subtypes - String lhsTableAlias = getLhs().getFromElement().getTableAlias(); + final String lhsTableAlias = getLhs().getFromElement().getTableAlias(); - AbstractEntityPersister persister = (AbstractEntityPersister) lhsFromElement.getEntityPersister(); + final AbstractEntityPersister persister = (AbstractEntityPersister) lhsFromElement.getEntityPersister(); - String[][] polyJoinColumns = persister.getPolymorphicJoinColumns(lhsTableAlias, propertyPath); + final String[][] polyJoinColumns = persister.getPolymorphicJoinColumns(lhsTableAlias, propertyPath); // Special join sequence that uses the poly join columns joinSequence = getSessionFactoryHelper() - .createJoinSequence( impliedJoin, propertyType, tableAlias, joinType, polyJoinColumns ); + .createJoinSequence( impliedJoin, toOneType, tableAlias, joinType, polyJoinColumns ); } else { // If this is an implied join in a from element, then use the implied join type which is part of the // tree parser's state (set by the grammar actions). joinSequence = getSessionFactoryHelper() - .createJoinSequence( impliedJoin, propertyType, tableAlias, joinType, joinColumns ); + .createJoinSequence( impliedJoin, toOneType, tableAlias, joinType, joinColumns ); } - FromElementFactory factory = new FromElementFactory( + final FromElementFactory factory = new FromElementFactory( currentFromClause, lhsFromElement, joinPath, @@ -583,10 +582,14 @@ private void dereferenceEntityJoin(String classAlias, EntityType propertyType, b joinSequence, fetch, getWalker().isInFrom(), - propertyType, + toOneType, role, joinPath ); + + if ( impliedJoin && toOneType.hasNotFoundAction() && ! getWalker().isSubQuery() ) { + elem.setInProjectionList( true ); + } } else { // NOTE : addDuplicateAlias() already performs nullness checks on the alias. diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 6489891016e6..6c4b3f92693d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -42,6 +42,7 @@ import org.hibernate.CacheMode; import org.hibernate.Criteria; +import org.hibernate.FetchNotFoundException; import org.hibernate.Filter; import org.hibernate.FlushMode; import org.hibernate.HibernateException; @@ -1042,7 +1043,16 @@ public Object immediateLoad(String entityName, Serializable id) throws Hibernate LoadEvent event = loadEvent; loadEvent = null; event = recycleEventInstance( event, id, entityName ); - fireLoadNoChecks( event, LoadEventListener.IMMEDIATE_LOAD ); + + try { + fireLoadNoChecks( event, LoadEventListener.IMMEDIATE_LOAD ); + } + catch (FetchNotFoundException e) { + // when this happens at the "top-level" of a load, for 5.x we want to + // keep this the same user-facing exception as it was before + getSessionFactory().getEntityNotFoundDelegate().handleEntityNotFound( e.getEntityName(), (Serializable) e.getIdentifier() ); + } + Object result = event.getResult(); if ( loadEvent == null ) { event.setEntityClassName( null ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java index e9f4583a696a..fc761f539572 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java @@ -16,6 +16,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.engine.internal.ForeignKeys; import org.hibernate.engine.spi.EntityUniqueKey; import org.hibernate.engine.spi.Mapping; @@ -680,6 +681,12 @@ public boolean isReferenceToIdentifierProperty() { */ public abstract boolean isNullable(); + public abstract NotFoundAction getNotFoundAction(); + + public boolean hasNotFoundAction() { + return getNotFoundAction() != null; + } + /** * Resolve an identifier via a load. * diff --git a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java index 3846817d52a0..79ab72907452 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java @@ -35,7 +35,6 @@ */ public class ManyToOneType extends EntityType { private final String propertyName; -// private final boolean nullable; private final NotFoundAction notFoundAction; private boolean isLogicalOneToOne; @@ -132,6 +131,11 @@ public boolean isNullable() { return notFoundAction != null; } + @Override + public NotFoundAction getNotFoundAction() { + return notFoundAction; + } + @Override public String getPropertyName() { return propertyName; @@ -264,6 +268,12 @@ public Object resolve(Object value, SharedSessionContractImplementor session, Ob throw new FetchNotFoundException( getAssociatedEntityName(), value ); } + if ( value != null + && resolvedValue == null + && getNotFoundAction() == NotFoundAction.EXCEPTION ) { + throw new FetchNotFoundException( getAssociatedEntityName(), value ); + } + if ( isLogicalOneToOne && value != null && getPropertyName() != null ) { final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); EntityEntry entry = persistenceContext.getEntry( owner ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java b/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java index 07311e32bf10..2033da81b140 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java @@ -14,6 +14,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.engine.internal.ForeignKeys; import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.spi.EntityKey; @@ -190,6 +191,11 @@ public boolean isNullable() { return !constrained; } + @Override + public NotFoundAction getNotFoundAction() { + return null; + } + @Override public boolean useLHSPrimaryKey() { return true; diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TypedQueryResultListTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TypedQueryResultListTest.java index 8fdeec5c21db..8d5e7ed4f2ad 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TypedQueryResultListTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/TypedQueryResultListTest.java @@ -14,6 +14,9 @@ import org.hibernate.annotations.NotFoundAction; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.FailureExpected; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import static org.hamcrest.CoreMatchers.is; @@ -27,20 +30,42 @@ protected Class[] getAnnotatedClasses() { return new Class[] { Parent.class }; } - @Test - public void testIt() { + @Before + public void createTestData() { doInJPA( this::entityManagerFactory, entityManager -> { Parent parent1 = new Parent( 1, "usr1", null ); Parent parent2 = new Parent( 1, "usr2", null ); entityManager.persist( parent1 ); entityManager.persist( parent2 ); + } ); + } + + @After + public void dropTestData() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "delete Parent" ).executeUpdate(); + } ); + } + + @Test + @FailureExpected( + jiraKey = "HHH-15060", + message = "This was the exact reported case. Even though @NotFound support is buggy, this " + + "query is not valid for the expected results. See `#badExpectationResultBaselineTest` " + + "for additional discussion about why this is an incorrect expectation." + ) + public void badExpectationResultTest() { + doInJPA( this::entityManagerFactory, entityManager -> { TypedQuery query = entityManager.createQuery( "select parent " + "from Parent parent " + - "where exists (select 1 " + - "from Parent otherParent " + - "where lower(otherParent.text) like :name and (parent.id = otherParent.sourceParent.id or parent.number = otherParent.number))", + "where exists ( " + + " select 1 " + + " from Parent otherParent " + + " where lower(otherParent.text) like :name" + + " and (parent.id = otherParent.sourceParent.id or parent.number = otherParent.number)" + + ")", Parent.class ); @@ -48,18 +73,91 @@ public void testIt() { List entityList = query.getResultList(); assertThat( entityList.size(), is( 2 ) ); } ); + } + /** + * Adjustment to {@link #badExpectationResultTest} in terms of the results which should actually be expected + * + * @see #actualExpectationResultBaselineTest + */ + @Test + public void actualExpectationResultTest() { + doInJPA( this::entityManagerFactory, entityManager -> { + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists ( " + + " select 1 " + + " from Parent otherParent " + + " where lower(otherParent.text) like :name" + + " and (parent.id = otherParent.sourceParent.id or parent.number = otherParent.number)" + + ")", + Parent.class + ); + query.setParameter( "name", "usr1" ); + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 0 ) ); + } ); } + /** + * A baseline test for {@link #badExpectationResultTest}, with the expectation adjustment described in + * {@link #actualExpectationResultTest()}. + * + * Here, instead of `.id` references (which are handled specially even outside of `@NotFound`), we use + * non-id references, which should ultimately return the same results. + */ @Test - public void testIt2() { + public void actualExpectationResultBaselineTest() { doInJPA( this::entityManagerFactory, entityManager -> { - Parent parent1 = new Parent( 1, "usr1", null ); - Parent parent2 = new Parent( 1, "usr2", null ); - entityManager.persist( parent1 ); - entityManager.persist( parent2 ); + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists ( " + + " select 1 " + + " from Parent otherParent " + + " where lower(otherParent.text) like :name " + + " and (parent.text = otherParent.sourceParent.text or parent.number = otherParent.number) " + + ")", + Parent.class + ); + + query.setParameter( "name", "usr1" ); + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 0 ) ); + } ); + } + /** + * An adjusted query showing the results wanted in the original report. + * + * Actually a series of adjusted queries, showing a few possibilities + */ + @Test + public void expectedResultQueryAdjustmentTest() { + doInJPA( this::entityManagerFactory, entityManager -> { + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists ( " + + " select 1 " + + " from Parent otherParent " + + " where lower(otherParent.text) like :name" + + " and (parent = otherParent.sourceParent or parent.number = otherParent.number)" + + ")", + Parent.class + ); + + query.setParameter( "name", "usr1" ); + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 2 ) ); + } ); + } + + @Test + public void testIt2() { + doInJPA( this::entityManagerFactory, entityManager -> { TypedQuery query = entityManager.createQuery( "select parent " + "from Parent parent " + diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java index a600fea6423b..b4415802a3b6 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java @@ -8,6 +8,11 @@ import java.io.Serializable; import java.util.List; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; import org.hibernate.FetchNotFoundException; import org.hibernate.Hibernate; @@ -17,7 +22,6 @@ import org.hibernate.testing.jdbc.SQLStatementInspector; import org.hibernate.testing.orm.junit.DomainModel; -import org.hibernate.testing.orm.junit.FailureExpected; import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; @@ -25,11 +29,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import javax.persistence.Entity; -import javax.persistence.EntityNotFoundException; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.OneToOne; import org.assertj.core.api.Assertions; import static org.assertj.core.api.Assertions.assertThat; @@ -51,12 +50,25 @@ public class NotFoundExceptionLogicalOneToOneTest { @Test @JiraKey( "HHH-15060" ) public void testProxy(SessionFactoryScope scope) { - // test handling of a proxy for the missing Coin + // test handling of a proxy for the Coin pointing to the missing Currency + scope.inTransaction( (session) -> { + final Coin proxy = session.byId( Coin.class ).getReference( 1 ); + try { + Hibernate.initialize( proxy ); + Assertions.fail( "Expecting FetchNotFoundException" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).endsWith( "Currency" ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + + // test handling of a proxy for the missing Currency scope.inTransaction( (session) -> { final Currency proxy = session.byId( Currency.class ).getReference( 1 ); try { Hibernate.initialize( proxy ); - Assertions.fail( "Expecting ObjectNotFoundException" ); + Assertions.fail( "Expecting FetchNotFoundException" ); } catch (ObjectNotFoundException expected) { assertThat( expected.getEntityName() ).endsWith( "Currency" ); @@ -84,61 +96,136 @@ public void testGet(SessionFactoryScope scope) { // join may be better here. but for now, 5.x generates 2 selects here // which is not wrong -// assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); -// assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); -// assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " ); assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); } ); + statementInspector.clear(); + scope.inTransaction( (session) -> { try { session.get( Coin.class, 1 ); - fail( "Expecting FetchNotFoundException" ); + fail( "Expecting ObjectNotFoundException" ); } catch (FetchNotFoundException expected) { assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " ); + assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); } } ); } @Test @JiraKey( "HHH-15060" ) - @FailureExpected( - reason = "At the moment, throws FetchNotFoundException. But the correct outcome is " + - "simply no results. This needs to trigger the join to use the fk target as part " + - "of the predicate, not the fk value" - ) - public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) { + public void testQueryImplicitPathDereferencePredicateBaseline(SessionFactoryScope scope) { final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); + // Baseline for comparison with `#testQueryImplicitPathDereferencePredicate` + // We ultimately want the `.id` reference to behave exactly the same as + // this query - specifically forcing the join scope.inTransaction( (session) -> { - final String hql = "select c from Coin c where c.currency.id = 1"; + final String hql = "select c from Coin c where c.currency.name = 'Euro'"; final List coins = session.createQuery( hql, Coin.class ).getResultList(); assertThat( coins ).isEmpty(); - - // join may be better here. but for now, 5.x generates 2 selects here - // which is not wrong -// assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); -// assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); -// assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); - assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); - assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " ); - assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); } ); + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + // unfortunately, versions of Hibernate prior to 6 used restricted cross joins + // (i.e. `x cross join y where x.y_fk = y.id`) to handle implicit query joins + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " cross " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testQueryImplicitPathDereferencePredicateBaseline2(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); scope.inTransaction( (session) -> { + // NOTE : this query is conceptually the same as the one from + // `#testQueryImplicitPathDereferencePredicateBaseline` in that we want + // a join and we want to use the fk target column (here, `Currency.id`) + // rather than the normal perf-opt strategy of using the fk key column + // (here, `Coin.currency_fk`). final String hql = "select c from Coin c where c.currency.id = 2"; final List coins = session.createQuery( hql, Coin.class ).getResultList(); assertThat( coins ).hasSize( 1 ); assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + // unfortunately, versions of Hibernate prior to 6 used restricted cross joins + // (i.e. `x cross join y where x.y_fk = y.id`) to handle implicit query joins + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " cross " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testQueryImplicitPathDereferencePredicateBaseline3(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + // NOTE : this query is conceptually the same as the one from + // `#testQueryImplicitPathDereferencePredicateBaseline` in that we want + // a join and we want to use the fk target column (here, `Currency.id`) + // rather than the normal perf-opt strategy of using the fk key column + // (here, `Coin.currency_fk`). + final String hql = "select c from Coin c join fetch c.currency c2 where c2.name = 'USD'"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + } ); + + statementInspector.clear(); + + scope.inTransaction( (session) -> { + // NOTE : this query is conceptually the same as the one from + // `#testQueryImplicitPathDereferencePredicateBaseline` in that we want + // a join and we want to use the fk target column (here, `Currency.id`) + // rather than the normal perf-opt strategy of using the fk key column + // (here, `Coin.currency_fk`). + final String hql = "select c from Coin c join fetch c.currency c2 where c2.name = 'Euro'"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 0 ); + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) +// @FailureExpected( +// reason = "When we have a dangling key (as in the `c.currency.id = 1` case), the outcome " + +// "ought to simply be no results. At the moment, however, FetchNotFoundException is " + +// "thrown. The underlying problem is that we use the FK key rather than the FK " + +// "target for selecting the association" + +// "" + +// " But the correct outcome is " + +// "simply no results. This needs to trigger the join to use the fk target as part " + +// "of the predicate, not the fk value" +// ) + public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.currency.id = 1"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + // there is no Currency with id=1 (Euro) + assertThat( coins ).isEmpty(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + // unfortunately, versions of Hibernate prior to 6 used restricted cross joins + // (i.e. `x cross join y where x.y_fk = y.id`) to handle implicit query joins + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " cross " ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); } ); } @@ -151,10 +238,10 @@ public void testQueryOwnerSelection(SessionFactoryScope scope) { try { //noinspection unused (debugging) final Coin coin = session.createQuery( hql, Coin.class ).uniqueResult(); - fail( "Expecting FetchNotFoundException for broken fk" ); + fail( "Expecting FetchNotFoundException" ); } catch (FetchNotFoundException expected) { - assertThat( expected.getEntityName() ).endsWith( "NotFoundExceptionLogicalOneToOneTest$Currency" ); + assertThat( expected.getEntityName() ).endsWith( "Currency" ); assertThat( expected.getIdentifier() ).isEqualTo( 1 ); } } ); @@ -228,6 +315,7 @@ public void setName(String name) { @OneToOne(fetch = FetchType.LAZY) @NotFound(action = NotFoundAction.EXCEPTION) + @JoinColumn( name = "currency_fk" ) public Currency getCurrency() { return currency; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java index fc5910d61304..813c691c2b06 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java @@ -28,6 +28,7 @@ import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; +import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import org.assertj.core.api.Assertions; @@ -51,8 +52,21 @@ public class NotFoundExceptionManyToOneTest { @Test @JiraKey( "HHH-15060" ) public void testProxy(SessionFactoryScope scope) { + // test handling of a proxy for the Coin pointing to the missing Currency scope.inTransaction( (session) -> { - // the non-existent Child + final Coin proxy = session.byId( Coin.class ).getReference( 1 ); + try { + Hibernate.initialize( proxy ); + Assertions.fail( "Expecting ObjectNotFoundException" ); + } + catch (ObjectNotFoundException expected) { + assertThat( expected.getEntityName() ).endsWith( "Currency" ); + assertThat( expected.getIdentifier() ).isEqualTo( 1 ); + } + } ); + + scope.inTransaction( (session) -> { + // the non-existent Currency final Currency proxy = session.byId( Currency.class ).getReference( 1 ); try { Hibernate.initialize( proxy ); @@ -75,7 +89,7 @@ public void testGet(SessionFactoryScope scope) { try { // should fail here loading the Coin due to missing currency (see NOTE#1) final Coin coin = session.get( Coin.class, 1 ); - fail( "Expecting FetchNotFoundException for broken fk" ); + fail( "Expecting ObjectNotFoundException for broken fk" ); } catch (FetchNotFoundException expected) { assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); @@ -105,25 +119,17 @@ public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) statementInspector.clear(); scope.inTransaction( (session) -> { - try { - final String hql = "select c from Coin c where c.currency.id = 1"; - session.createQuery( hql, Coin.class ).getResultList(); - - fail( "Expecting FetchNotFoundException for broken fk" ); - } - catch (FetchNotFoundException expected) { - assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); - assertThat( expected.getIdentifier() ).isEqualTo( 1 ); - - // join may be better here. but for now, 5.x generates 2 selects here - // which is not wrong -// assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); -// assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); -// assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); - assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); - assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " ); - assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); - } + final String hql = "select c from Coin c where c.currency.id = 1"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + // there is no Currency with id=1 (Euro) + assertThat( coins ).isEmpty(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + // unfortunately, versions of Hibernate prior to 6 used restricted cross joins + // (i.e. `x cross join y where x.y_fk = y.id`) to handle implicit query joins + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " cross " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); } ); } @@ -144,10 +150,7 @@ public void testQueryOwnerSelection(SessionFactoryScope scope) { assertThat( expected.getIdentifier() ).isEqualTo( 1 ); // join may be better here. but for now, 5.x generates 2 selects here - // which is not wrong -// assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); -// assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); -// assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + // which is not wrong. assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " ); assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " ); @@ -226,6 +229,7 @@ public void setName(String name) { @ManyToOne(fetch = FetchType.EAGER) @NotFound(action = NotFoundAction.EXCEPTION) + @JoinColumn( name = "currency_fk" ) public Currency getCurrency() { return currency; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java index 125cf536691c..062da1e17455 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java @@ -9,10 +9,12 @@ import java.io.Serializable; import java.util.List; +import org.hibernate.FetchNotFoundException; import org.hibernate.Hibernate; import org.hibernate.ObjectNotFoundException; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; +import org.hibernate.orm.test.notfound.exception.NotFoundExceptionLogicalOneToOneTest; import org.hibernate.testing.jdbc.SQLStatementInspector; import org.hibernate.testing.orm.junit.DomainModel; @@ -27,6 +29,7 @@ import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; +import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Tuple; import org.assertj.core.api.Assertions; @@ -50,8 +53,15 @@ public class NotFoundIgnoreManyToOneTest { @Test @JiraKey( "HHH-15060" ) public void testProxy(SessionFactoryScope scope) { + // test handling of a proxy for the Coin pointing to the missing Currency scope.inTransaction( (session) -> { - // the non-existent Child + final Coin proxy = session.byId( Coin.class ).getReference( 1 ); + Hibernate.initialize( proxy ); + assertThat( proxy.getCurrency() ).isNull(); + } ); + + scope.inTransaction( (session) -> { + // the non-existent Currency // - this is the one valid deviation from treating the broken fk as null final Currency proxy = session.byId( Currency.class ).getReference( 1 ); try { @@ -100,11 +110,12 @@ public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) scope.inTransaction( (session) -> { final String hql = "select c from Coin c where c.currency.id = 1"; final List coins = session.createQuery( hql, Coin.class ).getResultList(); + // there is no Currency with id=1 (Euro) assertThat( coins ).isEmpty(); - // technically we could use a subsequent-select rather than a join... assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " cross " ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); } ); } @@ -135,34 +146,60 @@ public void testQueryOwnerSelection(SessionFactoryScope scope) { @Test @JiraKey( "HHH-15060" ) @FailureExpected( - reason = "Has zero results because of inner-join; & the select w/ inner-join is executed twice for some odd reason" + reason = "Has zero results because of inner-join due to being defined in the select-clause. " + + "Not sure the best outcome here - no results or null elements within the results?" ) public void testQueryAssociationSelection(SessionFactoryScope scope) { final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); + // I guess this one is somewhat debatable, but for consistency I think this makes the most sense scope.inTransaction( (session) -> { - final String hql = "select c.id, c.currency from Coin c"; - final List tuples = session.createQuery( hql, Tuple.class ).getResultList(); - assertThat( tuples ).hasSize( 1 ); - final Tuple tuple = tuples.get( 0 ); - assertThat( tuple.get( 0 ) ).isEqualTo( 1 ); - assertThat( tuple.get( 1 ) ).isNull(); + final String hql = "select c.currency from Coin c"; + session.createQuery( hql, Currency.class ).getResultList(); + final List currencies = session.createQuery( hql, Currency.class ).getResultList(); + assertThat( currencies ).hasSize( 1 ); + assertThat( currencies.get( 0 ) ).isNull(); assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); } ); + } + @Test + @JiraKey( "HHH-15060" ) + public void testSubqueryUse(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); - // I guess this one is somewhat debatable, but for consistency I think this makes the most sense scope.inTransaction( (session) -> { - final String hql = "select c.currency from Coin c"; - session.createQuery( hql, Currency.class ).getResultList(); - final List currencies = session.createQuery( hql, Currency.class ).getResultList(); - assertThat( currencies ).hasSize( 1 ); - assertThat( currencies.get( 0 ) ).isNull(); + final String hql = "select c " + + "from Coin c " + + "where exists (" + + " select 1" + + " from Coin other" + + ")"; + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + @FailureExpected( + reason = "Has zero results because of inner-join due to being defined in the select-clause. " + + "Not sure the best outcome here - no results or null elements within the results?" + ) + public void testQueryAssociationSelection2(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + final String hql = "select c.id, c.currency from Coin c"; + final List tuples = session.createQuery( hql, Tuple.class ).getResultList(); + assertThat( tuples ).hasSize( 1 ); + final Tuple tuple = tuples.get( 0 ); + assertThat( tuple.get( 0 ) ).isEqualTo( 1 ); + assertThat( tuple.get( 1 ) ).isNull(); assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); @@ -226,6 +263,7 @@ public void setName(String name) { @ManyToOne(fetch = FetchType.EAGER) @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn( name = "currency_fk" ) public Currency getCurrency() { return currency; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java index 3263a43554db..6e1598dbc30b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java @@ -28,6 +28,7 @@ import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; +import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Tuple; import org.assertj.core.api.Assertions; @@ -51,6 +52,13 @@ public class NotFoundIgnoreOneToOneTest { @Test @JiraKey( "HHH-15060" ) public void testProxy(SessionFactoryScope scope) { + // test handling of a proxy for the Coin pointing to the missing Currency + scope.inTransaction( (session) -> { + final Coin proxy = session.byId( Coin.class ).getReference( 1 ); + Hibernate.initialize( proxy ); + assertThat( proxy.getCurrency() ).isNull(); + } ); + scope.inTransaction( (session) -> { // the non-existent Child // - this is the one valid deviation from treating the broken fk as null @@ -87,7 +95,6 @@ public void testGet(SessionFactoryScope scope) { @Test @JiraKey( "HHH-15060" ) - @FailureExpected( reason = "No results due to bad join" ) public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) { final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); @@ -95,8 +102,8 @@ public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) scope.inTransaction( (session) -> { final String hql = "select c from Coin c where c.currency.id = 1"; final List coins = session.createQuery( hql, Coin.class ).getResultList(); - assertThat( coins ).hasSize( 1 ); - assertThat( coins.get( 0 ).getCurrency() ).isNull(); + // there is no Currency with id=1 (Euro) + assertThat( coins ).isEmpty(); // technically we could use a subsequent-select rather than a join... assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); @@ -128,8 +135,26 @@ public void testQueryOwnerSelection(SessionFactoryScope scope) { @Test @JiraKey( "HHH-15060" ) - @FailureExpected( reason = "No results because of join" ) + @FailureExpected( + reason = "Has zero results because of inner-join due to being defined in the select-clause. " + + "Not sure the best outcome here - no results or null elements within the results?" + ) public void testQueryAssociationSelection(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + final String hql = "select c.currency from Coin c"; + final List currencies = session.createQuery( hql, Currency.class ).getResultList(); + assertThat( currencies ).hasSize( 1 ); + assertThat( currencies.get( 0 ) ).isNull(); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + @FailureExpected( + reason = "Has zero results because of inner-join due to being defined in the select-clause. " + + "Not sure the best outcome here - no results or null elements within the results?" + ) + public void testQueryAssociationSelection2(SessionFactoryScope scope) { scope.inTransaction( (session) -> { final String hql = "select c.id, c.currency from Coin c"; final List tuples = session.createQuery( hql, Tuple.class ).getResultList(); @@ -138,13 +163,6 @@ public void testQueryAssociationSelection(SessionFactoryScope scope) { assertThat( tuple.get( 0 ) ).isEqualTo( 1 ); assertThat( tuple.get( 1 ) ).isNull(); } ); - - scope.inTransaction( (session) -> { - final String hql = "select c.currency from Coin c"; - final List currencies = session.createQuery( hql, Currency.class ).getResultList(); - assertThat( currencies ).hasSize( 1 ); - assertThat( currencies.get( 0 ) ).isNull(); - } ); } @BeforeEach @@ -203,6 +221,7 @@ public void setName(String name) { @OneToOne(fetch = FetchType.EAGER) @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn( name = "currency_fk" ) public Currency getCurrency() { return currency; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java index f0e7330dd3d8..0577c67096b7 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java @@ -1379,6 +1379,11 @@ public void testExplicitToOneInnerJoinAndImplicitToOne() { } @Test + @FailureExpected( + jiraKey = "HHH-15060", + message = "HHH-15060 changes how associations mapped with `@NotFound` are handled " + + "which results in a (now expected) exception" + ) public void testNestedComponentIsNull() { // (1) From MapTest originally... // (2) Was then moved into HQLTest... diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java index beb71222b4bd..166bb1867bcf 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java @@ -1371,6 +1371,11 @@ public void testCollectionOfComponents() throws Exception { } @Test + @FailureExpected( + jiraKey = "HHH-15060", + message = "HHH-15060 changes how associations mapped with `@NotFound` are handled " + + "which results in a (now expected) exception" + ) public void testNestedComponentIsNull() { // From MapTest... assertTranslation( "from Commento c where c.marelo.commento.mcompr is null" ); From 251884ae88b2487b5a9739077915137ccdbe0205 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 28 Feb 2022 16:19:52 +0100 Subject: [PATCH 029/201] HHH-15060 Add additional test --- .../query/NotFoundAssociationQueryTest.java | 179 +++++++++++ .../ignore/IsNullAndNotFoundTest.java | 192 +++++++++++ .../OptionalManyToOneMapsIdQueryTest.java | 297 ++++++++++++++++++ .../OptionalManyToOnePKJCQueryTest.java | 290 +++++++++++++++++ 4 files changed, 958 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/query/NotFoundAssociationQueryTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/IsNullAndNotFoundTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/OptionalManyToOneMapsIdQueryTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/OptionalManyToOnePKJCQueryTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NotFoundAssociationQueryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NotFoundAssociationQueryTest.java new file mode 100644 index 000000000000..2f0adc99d07b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/NotFoundAssociationQueryTest.java @@ -0,0 +1,179 @@ +package org.hibernate.jpa.test.query; + +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.TypedQuery; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +public class NotFoundAssociationQueryTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, Child.class }; + } + + @Before + public void setUp() { + doInJPA( this::entityManagerFactory, entityManager -> { + Parent parent1 = new Parent( 1, "usr1", null ); + + Child child = new Child( 3, "Fab" ); + Parent parent2 = new Parent( 2, "usr2", child ); + + entityManager.persist( child ); + entityManager.persist( parent1 ); + entityManager.persist( parent2 ); + } ); + } + + @After + public void tearDown() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "delete from Parent" ).executeUpdate(); + } + ); + } + + @Test + public void testIt() { + doInJPA( this::entityManagerFactory, entityManager -> { + + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists (select 1 " + + "from Child child " + + "where child.parent.name = parent.name and child.parent.id = :id)", + Parent.class + ).setParameter( "id", 2 ); + + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 1 ) ); + assertThat( entityList.get( 0 ).name, is( "usr2" ) ); + + } ); + } + + @Test + public void testIt2() { + doInJPA( this::entityManagerFactory, entityManager -> { + + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists (select 1 " + + "from Child child " + + "where child.name = parent.child.name )", + Parent.class + ); + + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 1 ) ); + assertThat( entityList.get( 0 ).name, is( "usr2" ) ); + + } ); + } + + @Test + public void testIt3() { + doInJPA( this::entityManagerFactory, entityManager -> { + + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where parent.child.id = (select child.id " + + "from Child child " + + "where parent.child.id = :id)", + Parent.class + ).setParameter( "id", 3 ); + + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 1 ) ); + assertThat( entityList.get( 0 ).name, is( "usr2" ) ); + + } ); + } + + @Test + public void testIt4() { + doInJPA( this::entityManagerFactory, entityManager -> { + + TypedQuery query = entityManager.createQuery( + "select parent " + + "from Parent parent " + + "where exists (select 1 " + + "from Child child " + + "where parent.child.id = :id)", + Parent.class + ).setParameter( "id", 3 ); + + List entityList = query.getResultList(); + assertThat( entityList.size(), is( 1 ) ); + assertThat( entityList.get( 0 ).name, is( "usr2" ) ); + + } ); + } + + @Entity(name = "Parent") + public static class Parent { + + @Id + private Integer id; + + @ManyToOne + @JoinColumn(name = "source_fk", referencedColumnName = "id") + @NotFound(action = NotFoundAction.IGNORE) + private Child child; + + private String name; + + Parent() { + } + + public Parent(Integer id, String name, Child child) { + this.id = id; + this.name = name; + this.child = child; + } + + public Integer getId() { + return id; + } + } + + @Entity(name = "Child") + public static class Child { + @Id + private Integer id; + + @OneToOne(mappedBy = "child") + @NotFound(action = NotFoundAction.IGNORE) + private Parent parent; + + private String name; + + public Child() { + } + + public Child(Integer id, String name) { + this.id = id; + this.name = name; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/IsNullAndNotFoundTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/IsNullAndNotFoundTest.java new file mode 100644 index 000000000000..e31fcac9c41e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/IsNullAndNotFoundTest.java @@ -0,0 +1,192 @@ +/* + * 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 + */ +package org.hibernate.orm.test.notfound.ignore; + +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + + +public class IsNullAndNotFoundTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Account.class, Person.class }; + } + + @Before + public void setUp() { + inTransaction( + session -> { + Account account1 = new Account( 1, null, null ); + Account account2 = new Account( 2, "Fab", null ); + + Person person1 = new Person( 1, "Luigi", account1 ); + Person person2 = new Person( 2, "Andrea", account2 ); + Person person3 = new Person( 3, "Max", null ); + + session.persist( account1 ); + session.persist( account2 ); + session.persist( person1 ); + session.persist( person2 ); + session.persist( person3 ); + } + ); + } + + @After + public void tearDown() { + inTransaction( + session -> { + session.createQuery( "delete from Person" ).executeUpdate(); + session.createQuery( "delete from Account" ).executeUpdate(); + } + ); + } + + @Test + public void testIsNullInWhereClause() { + inTransaction( + session -> { + final List ids = session.createQuery( + "select p.id from Person p where p.account.code is null" ).getResultList(); + + assertEquals( 1, ids.size() ); + assertEquals( 1, (int) ids.get( 0 ) ); + + } + ); + } + + @Test + public void testIsNullInWhereClause2() { + inTransaction( + session -> { + final List ids = session.createQuery( + "select distinct p.id from Person p where p.account is null" ).getResultList(); + + assertEquals( 1, ids.size() ); + assertEquals( 3, (int) ids.get( 0 ) ); + + } + ); + } + + @Test + public void testIsNullInWhereClause3() { + inTransaction( + session -> { + final List ids = session.createQuery( + "select distinct p.id from Person p where p.account is null" ).getResultList(); + + assertEquals( 1, ids.size() ); + assertEquals( 3, (int) ids.get( 0 ) ); + + } + ); + } + + @Test + public void testIsNullInWhereClause4() { + inTransaction( + session -> { + final List ids = session.createQuery( + "select p.id from Person p where p.account.code is null or p.account.id is null" ) + .getResultList(); + + assertEquals( 1, ids.size() ); + assertEquals( 1, (int) ids.get( 0 ) ); + + } + ); + } + + @Test + public void testWhereClause() { + inTransaction( + session -> { + final List ids = session.createQuery( + "select p.id from Person p where p.account.code = :code and p.account.id = :id" ) + .setParameter( "code", "Fab" ) + .setParameter( "id", 2 ) + .getResultList(); + + assertEquals( 1, ids.size() ); + assertEquals( 2, (int) ids.get( 0 ) ); + + } + ); + } + + + @Entity(name = "Person") + public static class Person { + + @Id + private Integer id; + + private String name; + + @OneToOne + @NotFound(action = NotFoundAction.IGNORE) + private Account account; + + Person() { + } + + public Person(Integer id, String name, Account account) { + this.id = id; + this.name = name; + this.account = account; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public Account getAccount() { + return account; + } + } + + @Entity(name = "Account") + @Table(name = "ACCOUNT_TABLE") + public static class Account { + @Id + private Integer id; + + private String code; + + private Double amount; + + public Account() { + } + + public Account(Integer id, String code, Double amount) { + this.id = id; + this.code = code; + this.amount = amount; + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/OptionalManyToOneMapsIdQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/OptionalManyToOneMapsIdQueryTest.java new file mode 100644 index 000000000000..d9106022ebc7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/OptionalManyToOneMapsIdQueryTest.java @@ -0,0 +1,297 @@ +/* + * 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 . + */ +package org.hibernate.test.annotations.manytoone; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; +import javax.persistence.Table; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@TestForIssue( jiraKey = "HHH-13875") +public class OptionalManyToOneMapsIdQueryTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testManyToOneWithIdNamedId() { + // Test with associated entity having ID named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithIdNamedId bar = new BarWithIdNamedId(); + bar.id = 1L; + bar.longValue = 2L; + FooHasBarWithIdNamedId foo = new FooHasBarWithIdNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.createQuery( + "from FooHasBarWithIdNamedId where bar.id = ?1", + FooHasBarWithIdNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.get( FooHasBarWithIdNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.createQuery( + "from FooHasBarWithIdNamedId where bar.id = ?1", + FooHasBarWithIdNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @Test + public void testManyToOneWithNoIdOrPropNamedId() { + // Test with associated entity having ID not named "id", and with no property named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithNoIdOrPropNamedId bar = new BarWithNoIdOrPropNamedId(); + bar.barId = 1L; + bar.longValue = 2L; + FooHasBarWithNoIdOrPropNamedId foo = new FooHasBarWithNoIdOrPropNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.barId = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // Querying by the generic "id" should work the same as "barId". + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.id = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.get( FooHasBarWithNoIdOrPropNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.barId = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + // Querying by the generic "id" should work the same as "barId". + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.id = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @Test + public void testManyToOneWithNonIdPropNamedId() { + // Test with associated entity having a non-ID property named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithNonIdPropNamedId bar = new BarWithNonIdPropNamedId(); + bar.barId = 1L; + bar.id = 2L; + FooHasBarWithNonIdPropNamedId foo = new FooHasBarWithNonIdPropNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.barId = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // bar.id is a non-ID property. + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 2L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // bar.id is a non-ID property. + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.get( FooHasBarWithNonIdPropNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.barId = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 2L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @After + public void cleanupData() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "delete from FooHasBarWithIdNamedId" ).executeUpdate(); + session.createQuery( "delete from FooHasBarWithNoIdOrPropNamedId" ).executeUpdate(); + session.createQuery( "delete from FooHasBarWithNonIdPropNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithIdNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithNoIdOrPropNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithNoIdOrPropNamedId" ).executeUpdate(); + }); + } + + @Override + protected Class[] getAnnotatedClasses() + { + return new Class[] { + FooHasBarWithIdNamedId.class, + BarWithIdNamedId.class, + FooHasBarWithNoIdOrPropNamedId.class, + BarWithNoIdOrPropNamedId.class, + FooHasBarWithNonIdPropNamedId.class, + BarWithNonIdPropNamedId.class + }; + } + + @Entity(name = "FooHasBarWithIdNamedId") + public static class FooHasBarWithIdNamedId + { + @Id + private Long id; + + @ManyToOne(optional = true) + @MapsId + @JoinColumn(name = "id") + @NotFound(action = NotFoundAction.IGNORE) + private BarWithIdNamedId bar; + } + + @Entity(name = "BarWithIdNamedId") + public static class BarWithIdNamedId { + @Id + private long id; + private long longValue; + } + + @Entity(name = "FooHasBarWithNoIdOrPropNamedId") + @Table(name = "FooHasBarNoIdOrPropNamedId") + public static class FooHasBarWithNoIdOrPropNamedId + { + @Id + private Long id; + + @ManyToOne(optional = true) + @MapsId + @JoinColumn(name = "id") + @NotFound(action = NotFoundAction.IGNORE) + private BarWithNoIdOrPropNamedId bar; + } + + @Entity(name = "BarWithNoIdOrPropNamedId") + public static class BarWithNoIdOrPropNamedId { + @Id + private long barId; + private long longValue; + } + + @Entity(name = "FooHasBarWithNonIdPropNamedId") + @Table(name = "FooHasBarNonIdPropNamedId") + public static class FooHasBarWithNonIdPropNamedId + { + @Id + private Long id; + + @ManyToOne(optional = true) + @MapsId + @JoinColumn(name = "id") + @NotFound(action = NotFoundAction.IGNORE) + private BarWithNonIdPropNamedId bar; + } + + @Entity(name = "BarWithNonIdPropNamedId") + public static class BarWithNonIdPropNamedId { + @Id + private long barId; + private long id; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/OptionalManyToOnePKJCQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/OptionalManyToOnePKJCQueryTest.java new file mode 100644 index 000000000000..b2b3424be748 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/OptionalManyToOnePKJCQueryTest.java @@ -0,0 +1,290 @@ +/* + * 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 . + */ +package org.hibernate.test.annotations.manytoone; + +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.Table; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@TestForIssue( jiraKey = "HHH-13875") +public class OptionalManyToOnePKJCQueryTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testManyToOneWithIdNamedId() { + // Test with associated entity having ID named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithIdNamedId bar = new BarWithIdNamedId(); + bar.id = 1L; + bar.longValue = 2L; + FooHasBarWithIdNamedId foo = new FooHasBarWithIdNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.createQuery( + "from FooHasBarWithIdNamedId where bar.id = ?1", + FooHasBarWithIdNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.get( FooHasBarWithIdNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.createQuery( + "from FooHasBarWithIdNamedId where bar.id = ?1", + FooHasBarWithIdNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @Test + public void testManyToOneWithNoIdOrPropNamedId() { + // Test with associated entity having ID not named "id", and with no property named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithNoIdOrPropNamedId bar = new BarWithNoIdOrPropNamedId(); + bar.barId = 1L; + bar.longValue = 2L; + FooHasBarWithNoIdOrPropNamedId foo = new FooHasBarWithNoIdOrPropNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.barId = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // Querying by the generic "id" should work the same as "barId". + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.id = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.get( FooHasBarWithNoIdOrPropNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.barId = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + // Querying by the generic "id" should work the same as "barId". + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.id = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @Test + public void testManyToOneWithNonIdPropNamedId() { + // Test with associated entity having a non-ID property named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithNonIdPropNamedId bar = new BarWithNonIdPropNamedId(); + bar.barId = 1L; + bar.id = 2L; + FooHasBarWithNonIdPropNamedId foo = new FooHasBarWithNonIdPropNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.barId = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // bar.id is a non-ID property. + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 2L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // bar.id is a non-ID property. + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.get( FooHasBarWithNonIdPropNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.barId = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 2L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @After + public void cleanupData() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "delete from FooHasBarWithIdNamedId" ).executeUpdate(); + session.createQuery( "delete from FooHasBarWithNoIdOrPropNamedId" ).executeUpdate(); + session.createQuery( "delete from FooHasBarWithNonIdPropNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithIdNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithNoIdOrPropNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithNoIdOrPropNamedId" ).executeUpdate(); + }); + } + + @Override + protected Class[] getAnnotatedClasses() + { + return new Class[] { + FooHasBarWithIdNamedId.class, + BarWithIdNamedId.class, + FooHasBarWithNoIdOrPropNamedId.class, + BarWithNoIdOrPropNamedId.class, + FooHasBarWithNonIdPropNamedId.class, + BarWithNonIdPropNamedId.class + }; + } + + @Entity(name = "FooHasBarWithIdNamedId") + public static class FooHasBarWithIdNamedId + { + @Id + private long id; + + @ManyToOne(optional = true) + @PrimaryKeyJoinColumn(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) + private BarWithIdNamedId bar; + } + + @Entity(name = "BarWithIdNamedId") + public static class BarWithIdNamedId { + @Id + private long id; + private long longValue; + } + + @Entity(name = "FooHasBarWithNoIdOrPropNamedId") + @Table(name = "FooHasBarNoIdOrPropNamedId") + public static class FooHasBarWithNoIdOrPropNamedId + { + @Id + private long id; + + @ManyToOne(optional = true) + @PrimaryKeyJoinColumn() + private BarWithNoIdOrPropNamedId bar; + } + + @Entity(name = "BarWithNoIdOrPropNamedId") + public static class BarWithNoIdOrPropNamedId { + @Id + private long barId; + private long longValue; + } + + @Entity(name = "FooHasBarWithNonIdPropNamedId") + @Table(name = "FooHasBarNonIdPropNamedId") + public static class FooHasBarWithNonIdPropNamedId + { + @Id + private long id; + + @ManyToOne(optional = true) + @PrimaryKeyJoinColumn() + private BarWithNonIdPropNamedId bar; + } + + @Entity(name = "BarWithNonIdPropNamedId") + public static class BarWithNonIdPropNamedId { + @Id + private long barId; + private long id; + } +} From 2ea04dc524b688616b1dfbd237c2560f600f70e8 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Tue, 1 Mar 2022 14:39:11 -0600 Subject: [PATCH 030/201] HHH-15060 - Fix handling of associations with @NotFound --- .../hql/internal/ast/HqlSqlWalker.java | 11 ++ .../hql/internal/ast/tree/DotNode.java | 67 ++++----- .../hql/internal/ast/tree/IdentNode.java | 130 +++++++++--------- .../internal/ast/tree/ImpliedFromElement.java | 11 ++ .../hql/internal/ast/tree/SelectClause.java | 46 ++++++- .../test/hql/ASTParserLoadingTest.java | 5 - .../java/org/hibernate/test/hql/HQLTest.java | 5 - 7 files changed, 156 insertions(+), 119 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java index a8d3de42edf3..f6bb275022d1 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java @@ -45,6 +45,7 @@ import org.hibernate.hql.internal.ast.tree.FromElementFactory; import org.hibernate.hql.internal.ast.tree.FromReferenceNode; import org.hibernate.hql.internal.ast.tree.IdentNode; +import org.hibernate.hql.internal.ast.tree.ImpliedFromElement; import org.hibernate.hql.internal.ast.tree.IndexNode; import org.hibernate.hql.internal.ast.tree.InsertStatement; import org.hibernate.hql.internal.ast.tree.IntoClause; @@ -524,6 +525,16 @@ private void handleWithFragment(FromElement fromElement, AST hqlWithNode) throws } } + private boolean hasAnyForcibleNotFoundImplicitJoins; + + public void registerForcibleNotFoundImplicitJoin(ImpliedFromElement impliedJoin) { + hasAnyForcibleNotFoundImplicitJoins = true; + } + + public boolean hasAnyForcibleNotFoundImplicitJoins() { + return hasAnyForcibleNotFoundImplicitJoins; + } + private static class WithClauseVisitor implements NodeTraverser.VisitationStrategy { private final FromElement joinFragment; private final QueryTranslatorImpl queryTranslatorImpl; diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java index dbe4873f4473..bdb7d933cead 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java @@ -400,7 +400,6 @@ private void dereferenceEntity( property = parentAsDotNode.propertyName; if ( generateJoin ) { if ( implicitJoin && ( toOneType.hasNotFoundAction() || toOneType.isNullable() ) ) { - fetch = !getWalker().isSubQuery(); joinIsNeeded = true; } else { @@ -437,47 +436,16 @@ else if ( parentPredicate != null ) { } - /** - * Is the given property name a reference to the primary key of the associated - * entity construed by the given entity type? - *

    - * For example, consider a fragment like order.customer.id - * (where order is a from-element alias). Here, we'd have: - * propertyName = "id" AND - * owningType = ManyToOneType(Customer) - * and are being asked to determine whether "customer.id" is a reference - * to customer's PK... - * - * @param propertyName The name of the property to check. - * @param owningType The type represeting the entity "owning" the property - * - * @return True if propertyName references the entity's (owningType->associatedEntity) - * primary key; false otherwise. - */ - private boolean isReferenceToPrimaryKey(String propertyName, EntityType owningType) { - EntityPersister persister = getSessionFactoryHelper() - .getFactory().getMetamodel().entityPersister( owningType.getAssociatedEntityName() ); - if ( persister.getEntityMetamodel().hasNonIdentifierPropertyNamedId() ) { - // only the identifier property field name can be a reference to the associated entity's PK... - return propertyName.equals( persister.getIdentifierPropertyName() ) && owningType.isReferenceToPrimaryKey(); - } - // here, we have two possibilities: - // 1) the property-name matches the explicitly identifier property name - // 2) the property-name matches the implicit 'id' property name - // the referenced node text is the special 'id' - if ( EntityPersister.ENTITY_ID.equals( propertyName ) ) { - return owningType.isReferenceToPrimaryKey(); - } - String keyPropertyName = getSessionFactoryHelper().getIdentifierOrUniqueKeyPropertyName( owningType ); - return keyPropertyName != null && keyPropertyName.equals( propertyName ) && owningType.isReferenceToPrimaryKey(); - } - private static boolean isDotNode(AST n) { return n != null && n.getType() == SqlTokenTypes.DOT; } - private void dereferenceEntityJoin(String classAlias, EntityType toOneType, boolean impliedJoin, AST parent) throws SemanticException { + private void dereferenceEntityJoin( + String classAlias, + EntityType toOneType, + boolean isImpliedJoin, + AST parent) throws SemanticException { dereferenceType = DereferenceType.ENTITY; if ( LOG.isDebugEnabled() ) { LOG.debugf( @@ -495,7 +463,7 @@ private void dereferenceEntityJoin(String classAlias, EntityType toOneType, bool final String[] joinColumns = getColumns(); final String joinPath = getPath(); - if ( impliedJoin && getWalker().isInFrom() ) { + if ( isImpliedJoin && getWalker().isInFrom() ) { joinType = getWalker().getImpliedJoinType(); } @@ -559,13 +527,13 @@ private void dereferenceEntityJoin(String classAlias, EntityType toOneType, bool // Special join sequence that uses the poly join columns joinSequence = getSessionFactoryHelper() - .createJoinSequence( impliedJoin, toOneType, tableAlias, joinType, polyJoinColumns ); + .createJoinSequence( isImpliedJoin, toOneType, tableAlias, joinType, polyJoinColumns ); } else { // If this is an implied join in a from element, then use the implied join type which is part of the // tree parser's state (set by the grammar actions). joinSequence = getSessionFactoryHelper() - .createJoinSequence( impliedJoin, toOneType, tableAlias, joinType, joinColumns ); + .createJoinSequence( isImpliedJoin, toOneType, tableAlias, joinType, joinColumns ); } final FromElementFactory factory = new FromElementFactory( @@ -574,7 +542,7 @@ private void dereferenceEntityJoin(String classAlias, EntityType toOneType, bool joinPath, classAlias, joinColumns, - impliedJoin + isImpliedJoin ); elem = factory.createEntityJoin( associatedEntityName, @@ -587,14 +555,27 @@ private void dereferenceEntityJoin(String classAlias, EntityType toOneType, bool joinPath ); - if ( impliedJoin && toOneType.hasNotFoundAction() && ! getWalker().isSubQuery() ) { - elem.setInProjectionList( true ); + if ( isImpliedJoin + && toOneType.hasNotFoundAction() + && !getWalker().isSubQuery() ) { + assert elem instanceof ImpliedFromElement; + // we want to fetch this association if + // 1. its left-hand side is part of the result-graph, and + // 2. it is not already fetched + // + // unfortunately we will not know this information until later when we handle the + // select-clause - see `SelectClause#initializeExplicitSelectClause` and + // `SelectClause#initializeDerivedSelectClause`. For now, simply mark them + // and we will use that well initializing the SelectClause + final ImpliedFromElement impliedJoin = (ImpliedFromElement) elem; + impliedJoin.forceNotFoundFetch(); } } else { // NOTE : addDuplicateAlias() already performs nullness checks on the alias. currentFromClause.addDuplicateAlias( classAlias, elem ); } + setImpliedJoin( elem ); getWalker().addQuerySpaces( elem.getEntityPersister().getQuerySpaces() ); setFromElement( elem ); // This 'dot' expression now refers to the resulting from element. diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IdentNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IdentNode.java index 32b95144b637..ab5c9af1ddfe 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IdentNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IdentNode.java @@ -39,6 +39,7 @@ private static enum DereferenceType { } private boolean nakedPropertyRef; + private boolean fromClauseAlias; private String[] columns; public String[] getColumns() { @@ -103,82 +104,85 @@ private void initText(String[] columns) { } public void resolve(boolean generateJoin, boolean implicitJoin, String classAlias, AST parent, AST parentPredicate) { - if (!isResolved()) { - if ( getWalker().getCurrentFromClause().isFromElementAlias( getText() ) ) { - FromElement fromElement = getWalker().getCurrentFromClause().getFromElement( getText() ); - if ( fromElement.getQueryableCollection() != null && fromElement.getQueryableCollection().getElementType().isComponentType() ) { - if ( getWalker().isInSelect() ) { - // This is a reference to an element collection - setFromElement( fromElement ); - super.setDataType( fromElement.getQueryableCollection().getElementType() ); - this.columns = resolveColumns( fromElement.getQueryableCollection() ); - initText( getColumns() ); - setFirstChild( null ); - // Don't resolve it - } - else { - resolveAsAlias(); - // Don't resolve it - } + if ( isResolved() ) { + return; + } + + if ( getWalker().getCurrentFromClause().isFromElementAlias( getText() ) ) { + final FromElement fromElement = getWalker().getCurrentFromClause().getFromElement( getText() ); + if ( fromElement.getQueryableCollection() != null && fromElement.getQueryableCollection().getElementType().isComponentType() ) { + if ( getWalker().isInSelect() ) { + // This is a reference to an element collection + setFromElement( fromElement ); + super.setDataType( fromElement.getQueryableCollection().getElementType() ); + this.columns = resolveColumns( fromElement.getQueryableCollection() ); + initText( getColumns() ); + setFirstChild( null ); + // Don't resolve it } - else if ( resolveAsAlias() ) { - setResolved(); - // We represent a from-clause alias + else { + resolveAsAlias(); + // Don't resolve it } } - else if ( - getColumns() != null - && ( getWalker().getAST() instanceof AbstractMapComponentNode || getWalker().getAST() instanceof IndexNode ) - && getWalker().getCurrentFromClause().isFromElementAlias( getOriginalText() ) - ) { - // We might have to revert our decision that this is naked element collection reference when we encounter it is embedded in a map function - setText( getOriginalText() ); - if ( resolveAsAlias() ) { - setResolved(); - } + else if ( resolveAsAlias() ) { + setResolved(); + // We represent a from-clause alias } - else if (parent != null && parent.getType() == SqlTokenTypes.DOT) { - DotNode dot = (DotNode) parent; - if (parent.getFirstChild() == this) { - if (resolveAsNakedComponentPropertyRefLHS(dot)) { - // we are the LHS of the DOT representing a naked comp-prop-ref - setResolved(); - } - } - else { - if (resolveAsNakedComponentPropertyRefRHS(dot)) { - // we are the RHS of the DOT representing a naked comp-prop-ref - setResolved(); - } + } + else if ( + getColumns() != null + && ( getWalker().getAST() instanceof AbstractMapComponentNode || getWalker().getAST() instanceof IndexNode ) + && getWalker().getCurrentFromClause().isFromElementAlias( getOriginalText() ) + ) { + // We might have to revert our decision that this is naked element collection reference when we encounter it is embedded in a map function + setText( getOriginalText() ); + if ( resolveAsAlias() ) { + setResolved(); + } + } + else if (parent != null && parent.getType() == SqlTokenTypes.DOT) { + DotNode dot = (DotNode) parent; + if (parent.getFirstChild() == this) { + if (resolveAsNakedComponentPropertyRefLHS(dot)) { + // we are the LHS of the DOT representing a naked comp-prop-ref + setResolved(); } } else { - DereferenceType result = resolveAsNakedPropertyRef(); - if (result == DereferenceType.PROPERTY_REF) { - // we represent a naked (simple) prop-ref + if (resolveAsNakedComponentPropertyRefRHS(dot)) { + // we are the RHS of the DOT representing a naked comp-prop-ref setResolved(); } - else if (result == DereferenceType.COMPONENT_REF) { - // EARLY EXIT!!! return so the resolve call explicitly coming from DotNode can - // resolve this... - return; - } } + } + else { + DereferenceType result = resolveAsNakedPropertyRef(); + if (result == DereferenceType.PROPERTY_REF) { + // we represent a naked (simple) prop-ref + setResolved(); + } + else if (result == DereferenceType.COMPONENT_REF) { + // EARLY EXIT!!! return so the resolve call explicitly coming from DotNode can + // resolve this... + return; + } + } - // if we are still not resolved, we might represent a constant. - // needed to add this here because the allowance of - // naked-prop-refs in the grammar collides with the - // definition of literals/constants ("nondeterminism"). - // TODO: cleanup the grammar so that "processConstants" is always just handled from here - if (!isResolved()) { - try { - getWalker().getLiteralProcessor().processConstant(this, false); - } - catch (Throwable ignore) { - // just ignore it for now, it'll get resolved later... - } + // if we are still not resolved, we might represent a constant. + // needed to add this here because the allowance of + // naked-prop-refs in the grammar collides with the + // definition of literals/constants ("nondeterminism"). + // TODO: cleanup the grammar so that "processConstants" is always just handled from here + if (!isResolved()) { + try { + getWalker().getLiteralProcessor().processConstant(this, false); + } + catch (Throwable ignore) { + // just ignore it for now, it'll get resolved later... } } + } private boolean resolveAsAlias() { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ImpliedFromElement.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ImpliedFromElement.java index ccb3b240cb61..583c3a663d1c 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ImpliedFromElement.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/ImpliedFromElement.java @@ -23,6 +23,8 @@ public class ImpliedFromElement extends FromElement { */ private boolean inProjectionList; + private boolean forcedNotFoundFetch; + /** * Here to add debug breakpoints */ @@ -31,6 +33,15 @@ public ImpliedFromElement() { super(); } + public void forceNotFoundFetch() { + getWalker().registerForcibleNotFoundImplicitJoin( this ); + forcedNotFoundFetch = true; + } + + public boolean isForcedNotFoundFetch() { + return forcedNotFoundFetch; + } + public boolean isImplied() { return true; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectClause.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectClause.java index 6d4dfb7d2359..4e159dee1cf4 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectClause.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/SelectClause.java @@ -15,7 +15,6 @@ import org.hibernate.hql.internal.antlr.SqlTokenTypes; import org.hibernate.hql.internal.ast.util.ASTAppender; import org.hibernate.hql.internal.ast.util.ASTIterator; -import org.hibernate.hql.internal.ast.util.ASTPrinter; import org.hibernate.hql.internal.ast.util.TokenPrinters; import org.hibernate.type.Type; @@ -108,7 +107,7 @@ public AggregatedSelectExpression getAggregatedSelectExpression() { /** * Prepares an explicitly defined select clause. * - * @param fromClause The from clause linked to this select clause. + * @param fromClause The from-clause linked to this select clause. * * @throws SemanticException indicates a semantic issue with the explicit select clause. */ @@ -133,8 +132,24 @@ public void initializeExplicitSelectClause(FromClause fromClause) throws Semanti ); } + if ( !getWalker().isShallowQuery() ) { + if ( getWalker().hasAnyForcibleNotFoundImplicitJoins() ) { + // we encountered implicit joins to at least one NotFound association mapping. + // find them and make sure they get added to the result-graph if their parent is + for ( SelectExpression selectExpression : selectExpressions ) { + if ( selectExpression instanceof FromReferenceNode ) { + final FromReferenceNode selectedPath = (FromReferenceNode) selectExpression; + if ( isFromElementSelection( selectedPath ) ) { + final FromElement fromElement = selectedPath.getFromElement(); + applyForcibleImplicitNotFoundJoins( fromElement ); + } + } + } + } + } + for ( SelectExpression selectExpression : selectExpressions ) { - if ( AggregatedSelectExpression.class.isInstance( selectExpression ) ) { + if ( selectExpression instanceof AggregatedSelectExpression ) { aggregatedSelectExpression = (AggregatedSelectExpression) selectExpression; queryReturnTypeList.addAll( aggregatedSelectExpression.getAggregatedSelectionTypeList() ); scalarSelect = true; @@ -253,6 +268,31 @@ public void initializeExplicitSelectClause(FromClause fromClause) throws Semanti finishInitialization( /*sqlResultTypeList,*/ queryReturnTypeList ); } + private boolean isFromElementSelection(FromReferenceNode selectedPath) { + if ( selectedPath.getType() == HqlSqlTokenTypes.ALIAS_REF ) { + return true; + } + + // ugh + return selectedPath instanceof SelectExpressionImpl; + } + + private void applyForcibleImplicitNotFoundJoins(FromElement fromElement) { + final List destinations = fromElement.getDestinations(); + for ( int i = 0; i < destinations.size(); i++ ) { + final FromElement destination = destinations.get( i ); + if ( destination instanceof ImpliedFromElement ) { + final ImpliedFromElement impliedJoin = (ImpliedFromElement) destination; + if ( impliedJoin.isForcedNotFoundFetch() ) { + impliedJoin.setInProjectionList( true ); + impliedJoin.setFetch( true ); + } + } + + applyForcibleImplicitNotFoundJoins( destination ); + } + } + private void finishInitialization(ArrayList queryReturnTypeList) { queryReturnTypes = (Type[]) queryReturnTypeList.toArray( new Type[queryReturnTypeList.size()] ); initializeColumnNames(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java index 0577c67096b7..f0e7330dd3d8 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java @@ -1379,11 +1379,6 @@ public void testExplicitToOneInnerJoinAndImplicitToOne() { } @Test - @FailureExpected( - jiraKey = "HHH-15060", - message = "HHH-15060 changes how associations mapped with `@NotFound` are handled " + - "which results in a (now expected) exception" - ) public void testNestedComponentIsNull() { // (1) From MapTest originally... // (2) Was then moved into HQLTest... diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java index 166bb1867bcf..beb71222b4bd 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java @@ -1371,11 +1371,6 @@ public void testCollectionOfComponents() throws Exception { } @Test - @FailureExpected( - jiraKey = "HHH-15060", - message = "HHH-15060 changes how associations mapped with `@NotFound` are handled " + - "which results in a (now expected) exception" - ) public void testNestedComponentIsNull() { // From MapTest... assertTranslation( "from Commento c where c.marelo.commento.mcompr is null" ); From 223856cfd0fe43249400da156cf476253ed96581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 24 Feb 2022 17:44:21 +0100 Subject: [PATCH 031/201] HHH-15090 Fix access to public field with extended bytecode enhancement returning null for entity lazy-loaded from polymorphic toOne association --- .../internal/bytebuddy/ByteBuddyState.java | 13 +++++++++---- .../pojo/bytebuddy/ByteBuddyProxyHelper.java | 15 ++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java index 90c5b78a0890..fa32e018b73e 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -24,6 +24,7 @@ import java.util.function.Function; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.bytecode.spi.BasicProxyFactory; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.proxy.ProxyConfiguration; @@ -248,7 +249,7 @@ public static class ProxyDefinitionHelpers { private final ElementMatcher groovyGetMetaClassFilter; private final ElementMatcher virtualNotFinalizerFilter; - private final ElementMatcher hibernateGeneratedMethodFilter; + private final ElementMatcher proxyNonInterceptedMethodFilter; private final MethodDelegation delegateToInterceptorDispatcherMethodDelegation; private final FieldAccessor.PropertyConfigurable interceptorFieldAccessor; @@ -256,7 +257,11 @@ private ProxyDefinitionHelpers() { this.groovyGetMetaClassFilter = isSynthetic().and( named( "getMetaClass" ) .and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ); this.virtualNotFinalizerFilter = isVirtual().and( not( isFinalizer() ) ); - this.hibernateGeneratedMethodFilter = nameStartsWith( "$$_hibernate_" ).and( isVirtual() ); + this.proxyNonInterceptedMethodFilter = nameStartsWith( "$$_hibernate_" ).and( isVirtual() ) + // HHH-15090: Don't apply extended enhancement reader/writer methods to the proxy; + // those need to be executed on the actual entity. + .and( not( nameStartsWith( EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX ) ) ) + .and( not( nameStartsWith( EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX ) ) ); PrivilegedAction delegateToInterceptorDispatcherMethodDelegationPrivilegedAction = new PrivilegedAction() { @@ -294,8 +299,8 @@ public ElementMatcher getVirtualNotFinalizerFilter() return virtualNotFinalizerFilter; } - public ElementMatcher getHibernateGeneratedMethodFilter() { - return hibernateGeneratedMethodFilter; + public ElementMatcher getProxyNonInterceptedMethodFilter() { + return proxyNonInterceptedMethodFilter; } public MethodDelegation getDelegateToInterceptorDispatcherMethodDelegation() { diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java index dc6687146043..47107dc09749 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java @@ -6,6 +6,8 @@ */ package org.hibernate.proxy.pojo.bytebuddy; +import static org.hibernate.internal.CoreLogging.messageLogger; + import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Type; @@ -31,8 +33,6 @@ import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; import net.bytebuddy.implementation.SuperMethodCall; -import static org.hibernate.internal.CoreLogging.messageLogger; - public class ByteBuddyProxyHelper implements Serializable { private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyHelper.class ); @@ -64,18 +64,19 @@ public DynamicType.Unloaded buildUnloadedProxy(final Class persistentClass, f } private Function> proxyBuilder(Class persistentClass, Class[] interfaces) { + ByteBuddyState.ProxyDefinitionHelpers helpers = byteBuddyState.getProxyDefinitionHelpers(); return byteBuddy -> byteBuddy - .ignore( byteBuddyState.getProxyDefinitionHelpers().getGroovyGetMetaClassFilter() ) + .ignore( helpers.getGroovyGetMetaClassFilter() ) .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getName() ) ) ) .subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING ) .implement( (Type[]) interfaces ) - .method( byteBuddyState.getProxyDefinitionHelpers().getVirtualNotFinalizerFilter() ) - .intercept( byteBuddyState.getProxyDefinitionHelpers().getDelegateToInterceptorDispatcherMethodDelegation() ) - .method( byteBuddyState.getProxyDefinitionHelpers().getHibernateGeneratedMethodFilter() ) + .method( helpers.getVirtualNotFinalizerFilter() ) + .intercept( helpers.getDelegateToInterceptorDispatcherMethodDelegation() ) + .method( helpers.getProxyNonInterceptedMethodFilter() ) .intercept( SuperMethodCall.INSTANCE ) .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) .implement( ProxyConfiguration.class ) - .intercept( byteBuddyState.getProxyDefinitionHelpers().getInterceptorFieldAccessor() ); + .intercept( helpers.getInterceptorFieldAccessor() ); } public HibernateProxy deserializeProxy(SerializableProxy serializableProxy) { From f0331f32e9a3a031a5a8c969a2d7605b819a56d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 24 Feb 2022 16:14:06 +0100 Subject: [PATCH 032/201] HHH-15090 Test lazy loading with extended bytecode enhancement and inheritance --- .../lazy/LazyLoadingAndInheritanceTest.java | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingAndInheritanceTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingAndInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingAndInheritanceTest.java new file mode 100644 index 000000000000..5127a77b7e5b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingAndInheritanceTest.java @@ -0,0 +1,109 @@ +/* + * 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 . + */ +package org.hibernate.test.bytecode.enhancement.lazy; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +import org.hibernate.Hibernate; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +@RunWith(BytecodeEnhancerRunner.class) +@TestForIssue(jiraKey = "HHH-15090") +public class LazyLoadingAndInheritanceTest extends BaseCoreFunctionalTestCase { + + private Long containingID; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Containing.class, Contained.class, ContainedExtended.class }; + } + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" ); + configuration.setProperty( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "true" ); + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + Containing containing = new Containing(); + ContainedExtended contained = new ContainedExtended( "George" ); + containing.contained = contained; + s.persist( contained ); + s.persist( containing ); + containingID = containing.id; + } ); + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + Containing containing = s.load( Containing.class, containingID ); + Contained contained = containing.contained; + assertThat( contained ).isNotNull(); + assertThat( Hibernate.isPropertyInitialized( contained, "name" ) ).isFalse(); + assertThat( contained.name ).isNotNull(); + } ); + } + + @Entity(name = "Containing") + private static class Containing { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + public Long id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + public Contained contained; + } + + @Entity(name = "Contained") + private static class Contained { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + public Long id; + + public String name; + + Contained() { + } + + Contained(String name) { + this.name = name; + } + } + + @Entity(name = "ContainedExtended") + private static class ContainedExtended extends Contained { + + ContainedExtended() { + } + + ContainedExtended(String name) { + this.name = name; + } + + } +} From 6ad45e28427cd5c41ef2b386e1eeecb0e82532c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 1 Mar 2022 16:14:38 +0100 Subject: [PATCH 033/201] HHH-15090 Allow passing unloaded types and a TypePool to ByteBuddyProxyHelper#buildUnloadedProxy This is necessary to solve this bug in Quarkus. --- .../internal/bytebuddy/ByteBuddyState.java | 4 +++ .../pojo/bytebuddy/ByteBuddyProxyHelper.java | 33 ++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java index fa32e018b73e..593773e6fda2 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -189,6 +189,10 @@ public Unloaded make(Function> makeProxyFun return make( makeProxyFunction.apply( byteBuddy ) ); } + public Unloaded make(TypePool typePool, Function> makeProxyFunction) { + return make( typePool, makeProxyFunction.apply( byteBuddy ) ); + } + private Unloaded make(DynamicType.Builder builder) { return make( null, builder ); } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java index 47107dc09749..40abc800be43 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java @@ -10,8 +10,8 @@ import java.io.Serializable; import java.lang.reflect.Method; -import java.lang.reflect.Type; import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; import java.util.Locale; import java.util.Set; @@ -29,9 +29,13 @@ import net.bytebuddy.NamingStrategy; import net.bytebuddy.TypeCache; import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.description.type.TypeDefinition; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.description.type.TypeList; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; import net.bytebuddy.implementation.SuperMethodCall; +import net.bytebuddy.pool.TypePool; public class ByteBuddyProxyHelper implements Serializable { @@ -52,24 +56,35 @@ public Class buildProxy(final Class persistentClass, final Class[] interfaces) { } key.addAll( Arrays.>asList( interfaces ) ); - return byteBuddyState.loadProxy( persistentClass, new TypeCache.SimpleKey( key ), proxyBuilder( persistentClass, interfaces ) ); + return byteBuddyState.loadProxy( persistentClass, new TypeCache.SimpleKey( key ), + proxyBuilder( TypeDescription.ForLoadedType.of( persistentClass ), new TypeList.Generic.ForLoadedTypes( interfaces ) ) ); + } + + /** + * @deprecated Use {@link #buildUnloadedProxy(TypePool, TypeDefinition, Collection)} instead. + */ + @Deprecated + public DynamicType.Unloaded buildUnloadedProxy(final Class persistentClass, final Class[] interfaces) { + return byteBuddyState.make( proxyBuilder( TypeDescription.ForLoadedType.of( persistentClass ), + new TypeList.Generic.ForLoadedTypes( interfaces ) ) ); } /** * Do not remove: used by Quarkus */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public DynamicType.Unloaded buildUnloadedProxy(final Class persistentClass, final Class[] interfaces) { - return byteBuddyState.make( proxyBuilder( persistentClass, interfaces ) ); + public DynamicType.Unloaded buildUnloadedProxy(TypePool typePool, TypeDefinition persistentClass, + Collection interfaces) { + return byteBuddyState.make( typePool, proxyBuilder( persistentClass, interfaces ) ); } - private Function> proxyBuilder(Class persistentClass, Class[] interfaces) { + private Function> proxyBuilder(TypeDefinition persistentClass, + Collection interfaces) { ByteBuddyState.ProxyDefinitionHelpers helpers = byteBuddyState.getProxyDefinitionHelpers(); return byteBuddy -> byteBuddy .ignore( helpers.getGroovyGetMetaClassFilter() ) - .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getName() ) ) ) - .subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING ) - .implement( (Type[]) interfaces ) + .with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getTypeName() ) ) ) + .subclass( interfaces.size() == 1 ? persistentClass : TypeDescription.OBJECT, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING ) + .implement( interfaces ) .method( helpers.getVirtualNotFinalizerFilter() ) .intercept( helpers.getDelegateToInterceptorDispatcherMethodDelegation() ) .method( helpers.getProxyNonInterceptedMethodFilter() ) From 9554ab9e410614fd49e739a31816a91700365f35 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Wed, 2 Mar 2022 21:27:34 -0600 Subject: [PATCH 034/201] HHH-15060 - Fix handling of associations with @NotFound - add new `{fk}` token for HQL (probably should be a new issue) --- hibernate-core/src/main/antlr/hql-sql.g | 1 + hibernate-core/src/main/antlr/hql.g | 3 + .../hql/internal/ast/tree/DotNode.java | 87 +++++- .../orm/test/notfound/FkRefTests.java | 249 ++++++++++++++++++ 4 files changed, 334 insertions(+), 6 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/notfound/FkRefTests.java diff --git a/hibernate-core/src/main/antlr/hql-sql.g b/hibernate-core/src/main/antlr/hql-sql.g index 142348258022..f7a1d9db8631 100644 --- a/hibernate-core/src/main/antlr/hql-sql.g +++ b/hibernate-core/src/main/antlr/hql-sql.g @@ -785,6 +785,7 @@ propertyName | CLASS | ELEMENTS | INDICES + | FK_REF ; propertyRef! diff --git a/hibernate-core/src/main/antlr/hql.g b/hibernate-core/src/main/antlr/hql.g index 3f2782263107..7784620f5c91 100644 --- a/hibernate-core/src/main/antlr/hql.g +++ b/hibernate-core/src/main/antlr/hql.g @@ -824,6 +824,7 @@ identPrimary #identPrimary = #( [ENTRY], path ); } } + | (DOT^ FK_REF) )? // Also allow special 'aggregate functions' such as count(), avg(), etc. | aggregate @@ -967,6 +968,8 @@ MOD: '%'; COLON: ':'; PARAM: '?'; +FK_REF: "{fk}"; + IDENT options { testLiterals=true; } : ID_START_LETTER ( ID_LETTER )* { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java index bdb7d933cead..cc2b7863291a 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java @@ -65,6 +65,8 @@ public QueryException buildIllegalCollectionDereferenceException(String property public static enum DereferenceType { UNKNOWN, ENTITY, + FK_REF, + FK_REF_LHS, COMPONENT, COLLECTION, PRIMITIVE, @@ -263,8 +265,43 @@ private void initText() { } private Type prepareLhs() throws SemanticException { - FromReferenceNode lhs = getLhs(); + final FromReferenceNode lhs = getLhs(); lhs.prepareForDot( propertyName ); + + if ( "{fk}".equals( propertyName ) ) { + // we are processing the {fk} node and "preparing" its + // lhs, which is the to-one mapping. it is significantly + // easiest to simply handle this case here. The other + // option would be to muck around with the PropertyMapping + // for the entity containing the to-one to add mapping for + // the {fk} path; but unfortunately that still means + // additional changes here + final Type toOneType = lhs.getDataType(); + assert toOneType instanceof EntityType; + + dereferenceType = DereferenceType.FK_REF; + + final String toOnePropertyPath; + if ( lhs instanceof DotNode ) { + final DotNode lhsAsDotNode = (DotNode) lhs; + toOnePropertyPath = lhsAsDotNode.propertyPath; + } + else { + assert lhs instanceof IdentNode; + final IdentNode lhsAsIdentNode = (IdentNode) lhs; + toOnePropertyPath = lhsAsIdentNode.getOriginalText(); + } + propertyPath = toOnePropertyPath + ".{fk}"; + + // resolve the fk key columns + final String tableAlias = getLhs().getFromElement().getTableAlias(); + this.columns = getFromElement().getElementType().toColumns( tableAlias, toOnePropertyPath, false ); + + // and the key type + final Type keyType = getFromElement().getElementType().getPropertyType( toOnePropertyPath, toOnePropertyPath ); + setDataType( keyType ); + } + return getDataType(); } @@ -368,7 +405,7 @@ private void dereferenceEntity( AST parent, AST parentPredicate) throws SemanticException { checkForCorrelatedSubquery( "dereferenceEntity" ); - // three general cases we check here as to whether to render a physical SQL join: + // three general cases we check whether to render a physical SQL join: // 1) is our parent a DotNode as well? If so, our property reference is // being further de-referenced... // 2) is this a DML statement @@ -393,11 +430,31 @@ private void dereferenceEntity( final boolean joinIsNeeded; if ( isDotNode( parent ) ) { - // our parent is another dot node, meaning we are being further dereferenced. - // thus we need to generate a join unless the association is non-nullable and - // parent refers to the associated entity's PK (because 'our' table would know the FK). parentAsDotNode = (DotNode) parent; + + // our parent is another dot node, meaning we are being further de-referenced. + // depending on the exact de-reference we may need to generate a physical join. + // generally we will need the join unless: + // * this is a to-one reference && either: + // * the de-reference path is the associated entity's identifier + // && the to-one is not marked with @NotFound + // * the de-reference is the special {fk} token + property = parentAsDotNode.propertyName; + + if ( "{fk}".equals( property ) ) { + if ( parentAsDotNode.getNextSibling() != null ) { + throw new QueryException( + "{fk} reference cannot be further de-referenced - " + propertyPath + " -> " + parentAsDotNode.getNextSibling().getText() + ); + } + + dereferenceType = DereferenceType.FK_REF_LHS; + +// dereferenceForeignKeyReference( toOneType, ( (DotNode) parentAsDotNode.getLhs() ).getPropertyPath() ); + return; + } + if ( generateJoin ) { if ( implicitJoin && ( toOneType.hasNotFoundAction() || toOneType.isNullable() ) ) { joinIsNeeded = true; @@ -436,11 +493,29 @@ else if ( parentPredicate != null ) { } - private static boolean isDotNode(AST n) { return n != null && n.getType() == SqlTokenTypes.DOT; } + private void dereferenceForeignKeyReference(EntityType toOneType, String toOnePropertyName) throws SemanticException { + dereferenceType = DereferenceType.FK_REF; + + if ( LOG.isDebugEnabled() ) { + LOG.debugf( "dereferenceForeignKeyReference() : %s", getPath() ); + } + + final String fkRefPath = propertyPath + ".{fk}"; + propertyPath = fkRefPath; + + // resolve the fk key columns + final String tableAlias = getLhs().getFromElement().getTableAlias(); + this.columns = getFromElement().getElementType().toColumns( tableAlias, fkRefPath, false ); + + // and the key type + setDataType( getFromElement().getElementType().getPropertyType( toOnePropertyName, fkRefPath ) ); + } + + private void dereferenceEntityJoin( String classAlias, EntityType toOneType, diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/FkRefTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/FkRefTests.java new file mode 100644 index 000000000000..678b958a0c2f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/FkRefTests.java @@ -0,0 +1,249 @@ +/* + * 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 . + */ +package org.hibernate.orm.test.notfound; + +import java.io.Serializable; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.hql.internal.ast.QuerySyntaxException; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for the new `{fk}` HQL token + * + * @author Steve Ebersole + */ +@DomainModel( annotatedClasses = { FkRefTests.Coin.class, FkRefTests.Currency.class } ) +@SessionFactory( useCollectingStatementInspector = true ) +public class FkRefTests { + + @Test + @JiraKey( "HHH-15060" ) + public void testSimplePredicateUse(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + // there is a Coin which has a currency_fk = 1 + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.currency.{fk} = 1"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 1 ); + assertThat( coins.get( 0 ) ).isNotNull(); + assertThat( coins.get( 0 ).getCurrency() ).isNull(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " ); + } ); + + statementInspector.clear(); + + // However, the "matching" Currency does not exist + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.currency.id = 1"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 0 ); + } ); + + statementInspector.clear(); + + // check using `currency` as a naked "property-ref" + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where currency.{fk} = 1"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 1 ); + assertThat( coins.get( 0 ) ).isNotNull(); + assertThat( coins.get( 0 ).getCurrency() ).isNull(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " ); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testNullnessPredicateUse(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + // there is one Coin (id=3) which has a null currency_fk + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where c.currency.{fk} is null"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 1 ); + assertThat( coins.get( 0 ) ).isNotNull(); + assertThat( coins.get( 0 ).getId() ).isEqualTo( 3 ); + assertThat( coins.get( 0 ).getCurrency() ).isNull(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " ); + } ); + + statementInspector.clear(); + + // check using `currency` as a naked "property-ref" + scope.inTransaction( (session) -> { + final String hql = "select c from Coin c where currency.{fk} is null"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + assertThat( coins ).hasSize( 1 ); + assertThat( coins.get( 0 ) ).isNotNull(); + assertThat( coins.get( 0 ).getId() ).isEqualTo( 3 ); + assertThat( coins.get( 0 ).getCurrency() ).isNull(); + + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " ); + } ); + } + + @Test + @JiraKey( "HHH-15060" ) + public void testFkRefDereferenceNotAllowed(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( (session) -> { + try { + final String hql = "select c from Coin c where c.currency.{fk}.something"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + } + catch (IllegalArgumentException expected) { + assertThat( expected.getCause() ).isInstanceOf( QuerySyntaxException.class ); + } + } ); + + scope.inTransaction( (session) -> { + try { + final String hql = "select c from Coin c where currency.{fk}.something"; + final List coins = session.createQuery( hql, Coin.class ).getResultList(); + } + catch (IllegalArgumentException expected) { + assertThat( expected.getCause() ).isInstanceOf( QuerySyntaxException.class ); + } + } ); + } + + @BeforeEach + public void prepareTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + Currency euro = new Currency( 1, "Euro" ); + Coin fiveC = new Coin( 1, "Five cents", euro ); + session.persist( euro ); + session.persist( fiveC ); + + Currency usd = new Currency( 2, "USD" ); + Coin penny = new Coin( 2, "Penny", usd ); + session.persist( usd ); + session.persist( penny ); + + Coin noCurrency = new Coin( 3, "N/A", null ); + session.persist( noCurrency ); + } ); + + scope.inTransaction( (session) -> { + session.createQuery( "delete Currency where id = 1" ).executeUpdate(); + } ); + } + + @AfterEach + public void cleanupTest(SessionFactoryScope scope) throws Exception { + scope.inTransaction( (session) -> { + session.createQuery( "delete Coin" ).executeUpdate(); + session.createQuery( "delete Currency" ).executeUpdate(); + } ); + } + + @Entity(name = "Coin") + public static class Coin { + private Integer id; + private String name; + private Currency currency; + + public Coin() { + } + + public Coin(Integer id, String name, Currency currency) { + this.id = id; + this.name = name; + this.currency = currency; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToOne(fetch = FetchType.LAZY) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn( name = "currency_fk" ) + public Currency getCurrency() { + return currency; + } + + public void setCurrency(Currency currency) { + this.currency = currency; + } + } + + @Entity(name = "Currency") + public static class Currency implements Serializable { + private Integer id; + private String name; + + public Currency() { + } + + public Currency(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} From c5e95e5a346de6d636cd53ad8f7f52de682a5bfe Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 8 Mar 2022 18:28:02 +0100 Subject: [PATCH 035/201] HHH-14817 Make sure *-jakarta modules generate source and javadoc jars --- .../hibernate-core-jakarta.gradle | 28 +++++ .../hibernate-envers-jakarta.gradle | 113 ++++++++++++++++- .../hibernate-testing-jakarta.gradle | 114 +++++++++++++++++- .../hibernate-jpamodelgen-jakarta.gradle | 114 +++++++++++++++++- 4 files changed, 360 insertions(+), 9 deletions(-) diff --git a/hibernate-core-jakarta/hibernate-core-jakarta.gradle b/hibernate-core-jakarta/hibernate-core-jakarta.gradle index 09e3e1bd5dc8..802fea183165 100644 --- a/hibernate-core-jakarta/hibernate-core-jakarta.gradle +++ b/hibernate-core-jakarta/hibernate-core-jakarta.gradle @@ -94,6 +94,8 @@ processResources.enabled false compileTestJava.enabled false processTestResources.enabled false jar.enabled false +javadocJar.enabled false +sourcesJar.enabled false ext { transformedJarName = project(':hibernate-core').tasks.jar.archiveFileName.get().replaceAll( 'hibernate-core', 'hibernate-core-jakarta' ) @@ -123,6 +125,26 @@ task transformJar(type: JakartaJarTransformation) { targetJar tasks.jar.archiveFile.get().asFile } +task transformSourcesJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-core sources jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-core').tasks.sourcesJar + mustRunAfter project(':hibernate-core').tasks.sourcesJar + + sourceJar project(':hibernate-core').tasks.sourcesJar.archiveFile + targetJar tasks.sourcesJar.archiveFile.get().asFile +} + +task transformJavadocJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-core javadoc jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-core').tasks.javadocJar + mustRunAfter project(':hibernate-core').tasks.javadocJar + + sourceJar project(':hibernate-core').tasks.javadocJar.archiveFile + targetJar tasks.javadocJar.archiveFile.get().asFile +} + configurations { [apiElements, runtimeElements].each { it.outgoing.artifacts.removeIf { @@ -131,6 +153,12 @@ configurations { it.outgoing.artifact(tasks.transformJar.targetJar) { builtBy tasks.transformJar } + it.outgoing.artifact(tasks.transformSourcesJar.targetJar) { + builtBy tasks.transformSourcesJar + } + it.outgoing.artifact(tasks.transformJavadocJar.targetJar) { + builtBy tasks.transformJavadocJar + } } } diff --git a/hibernate-envers-jakarta/hibernate-envers-jakarta.gradle b/hibernate-envers-jakarta/hibernate-envers-jakarta.gradle index 0a72f9ef9d08..4841a1e8bb35 100644 --- a/hibernate-envers-jakarta/hibernate-envers-jakarta.gradle +++ b/hibernate-envers-jakarta/hibernate-envers-jakarta.gradle @@ -1,4 +1,5 @@ import org.apache.tools.ant.filters.ReplaceTokens +import javax.inject.Inject /* * Hibernate, Relational Persistence for Idiomatic Java @@ -12,13 +13,22 @@ description = 'Hibernate\'s entity version (audit/history) support Jakarta editi apply from: rootProject.file( 'gradle/published-java-module.gradle' ) configurations { - jakartaeeTransformJars + jakartaeeTransformTool } +// we do not want the much of the normal java plugin's behavior +compileJava.enabled false +processResources.enabled false +compileTestJava.enabled false +processTestResources.enabled false +jar.enabled false +javadocJar.enabled false +sourcesJar.enabled false + dependencies { compile( project( ':hibernate-core-jakarta' ) ) - jakartaeeTransformJars 'biz.aQute.bnd:biz.aQute.bnd.transform:5.1.1', + jakartaeeTransformTool 'biz.aQute.bnd:biz.aQute.bnd.transform:5.1.1', 'commons-cli:commons-cli:1.4', 'org.slf4j:slf4j-simple:1.7.30', 'org.slf4j:slf4j-api:1.7.26', @@ -64,7 +74,7 @@ jar { } javaexec { - classpath configurations.jakartaeeTransformJars + classpath configurations.jakartaeeTransformTool main = 'org.eclipse.transformer.jakarta.JakartaTransformer' args = transformerArgs } @@ -72,6 +82,53 @@ jar { } } +task transformJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-envers jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-envers').tasks.jar + mustRunAfter project(':hibernate-envers').tasks.jar + + sourceJar project(':hibernate-envers').tasks.jar.archiveFile + targetJar tasks.jar.archiveFile.get().asFile +} + +task transformSourcesJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-envers sources jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-envers').tasks.sourcesJar + mustRunAfter project(':hibernate-envers').tasks.sourcesJar + + sourceJar project(':hibernate-envers').tasks.sourcesJar.archiveFile + targetJar tasks.sourcesJar.archiveFile.get().asFile +} + +task transformJavadocJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-envers javadoc jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-envers').tasks.javadocJar + mustRunAfter project(':hibernate-envers').tasks.javadocJar + + sourceJar project(':hibernate-envers').tasks.javadocJar.archiveFile + targetJar tasks.javadocJar.archiveFile.get().asFile +} + +configurations { + [apiElements, runtimeElements].each { + it.outgoing.artifacts.removeIf { + it.buildDependencies.getDependencies(null).contains(jar) + } + it.outgoing.artifact(tasks.transformJar.targetJar) { + builtBy tasks.transformJar + } + it.outgoing.artifact(tasks.transformSourcesJar.targetJar) { + builtBy tasks.transformSourcesJar + } + it.outgoing.artifact(tasks.transformJavadocJar.targetJar) { + builtBy tasks.transformJavadocJar + } + } +} + task unpackTestJar(type: Copy) { dependsOn jar fileTree(project.buildDir).matching { include 'libs/*-test.jar' }.each { @@ -98,3 +155,53 @@ test { jvmArgs( ['--add-opens', 'java.base/java.lang=ALL-UNNAMED'] ) } } + +@CacheableTask +abstract class JakartaJarTransformation extends DefaultTask { + private final RegularFileProperty sourceJar; + private final RegularFileProperty targetJar; + + @Inject + JakartaJarTransformation(ObjectFactory objectFactory) { + sourceJar = objectFactory.fileProperty(); + targetJar = objectFactory.fileProperty(); + } + + @InputFile + @PathSensitive( PathSensitivity.RELATIVE ) + RegularFileProperty getSourceJar() { + return sourceJar; + } + + void sourceJar(Object fileReference) { + sourceJar.set( project.file( fileReference ) ) + } + + @OutputFile + RegularFileProperty getTargetJar() { + return targetJar; + } + + void targetJar(Object fileReference) { + targetJar.set( project.file( fileReference ) ) + } + + @TaskAction + void transform() { + project.javaexec( new Action() { + @Override + void execute(JavaExecSpec javaExecSpec) { + javaExecSpec.classpath( getProject().getConfigurations().getByName( "jakartaeeTransformTool" ) ); + javaExecSpec.setMain( "org.eclipse.transformer.jakarta.JakartaTransformer" ); + javaExecSpec.args( + sourceJar.get().getAsFile().getAbsolutePath(), + targetJar.get().getAsFile().getAbsolutePath(), + "-q", + "-tr", getProject().getRootProject().file( "rules/jakarta-renames.properties" ).getAbsolutePath(), + "-tv", getProject().getRootProject().file( "rules/jakarta-versions.properties" ).getAbsolutePath(), + "-td", getProject().getRootProject().file( "rules/jakarta-direct.properties" ).getAbsolutePath() + ); + } + }); + } +} \ No newline at end of file diff --git a/hibernate-testing-jakarta/hibernate-testing-jakarta.gradle b/hibernate-testing-jakarta/hibernate-testing-jakarta.gradle index e734d0acbddd..de795aa1d217 100644 --- a/hibernate-testing-jakarta/hibernate-testing-jakarta.gradle +++ b/hibernate-testing-jakarta/hibernate-testing-jakarta.gradle @@ -1,3 +1,5 @@ +import javax.inject.Inject + /* * Hibernate, Relational Persistence for Idiomatic Java * @@ -10,9 +12,18 @@ description = 'Support for testing Hibernate ORM Jakarta functionality' apply from: rootProject.file( 'gradle/published-java-module.gradle' ) configurations { - jakartaeeTransformJars + jakartaeeTransformTool } +// we do not want the much of the normal java plugin's behavior +compileJava.enabled false +processResources.enabled false +compileTestJava.enabled false +processTestResources.enabled false +jar.enabled false +javadocJar.enabled false +sourcesJar.enabled false + dependencies { compile project( ':hibernate-core-jakarta' ) compile( libraries.jakarta_jta ) @@ -29,7 +40,7 @@ dependencies { transitive=false; } - jakartaeeTransformJars 'biz.aQute.bnd:biz.aQute.bnd.transform:5.1.1', + jakartaeeTransformTool 'biz.aQute.bnd:biz.aQute.bnd.transform:5.1.1', 'commons-cli:commons-cli:1.4', 'org.slf4j:slf4j-simple:1.7.30', 'org.slf4j:slf4j-api:1.7.26', @@ -70,10 +81,107 @@ jar { } javaexec { - classpath configurations.jakartaeeTransformJars + classpath configurations.jakartaeeTransformTool main = 'org.eclipse.transformer.jakarta.JakartaTransformer' args = transformerArgs } } } } + +task transformJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-testing jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-testing').tasks.jar + mustRunAfter project(':hibernate-testing').tasks.jar + + sourceJar project(':hibernate-testing').tasks.jar.archiveFile + targetJar tasks.jar.archiveFile.get().asFile +} + +task transformSourcesJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-testing sources jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-testing').tasks.sourcesJar + mustRunAfter project(':hibernate-testing').tasks.sourcesJar + + sourceJar project(':hibernate-testing').tasks.sourcesJar.archiveFile + targetJar tasks.sourcesJar.archiveFile.get().asFile +} + +task transformJavadocJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-testing javadoc jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-testing').tasks.javadocJar + mustRunAfter project(':hibernate-testing').tasks.javadocJar + + sourceJar project(':hibernate-testing').tasks.javadocJar.archiveFile + targetJar tasks.javadocJar.archiveFile.get().asFile +} + +configurations { + [apiElements, runtimeElements].each { + it.outgoing.artifacts.removeIf { + it.buildDependencies.getDependencies(null).contains(jar) + } + it.outgoing.artifact(tasks.transformJar.targetJar) { + builtBy tasks.transformJar + } + it.outgoing.artifact(tasks.transformSourcesJar.targetJar) { + builtBy tasks.transformSourcesJar + } + it.outgoing.artifact(tasks.transformJavadocJar.targetJar) { + builtBy tasks.transformJavadocJar + } + } +} + +@CacheableTask +abstract class JakartaJarTransformation extends DefaultTask { + private final RegularFileProperty sourceJar; + private final RegularFileProperty targetJar; + + @Inject + JakartaJarTransformation(ObjectFactory objectFactory) { + sourceJar = objectFactory.fileProperty(); + targetJar = objectFactory.fileProperty(); + } + + @InputFile + @PathSensitive( PathSensitivity.RELATIVE ) + RegularFileProperty getSourceJar() { + return sourceJar; + } + + void sourceJar(Object fileReference) { + sourceJar.set( project.file( fileReference ) ) + } + + @OutputFile + RegularFileProperty getTargetJar() { + return targetJar; + } + + void targetJar(Object fileReference) { + targetJar.set( project.file( fileReference ) ) + } + + @TaskAction + void transform() { + project.javaexec( new Action() { + @Override + void execute(JavaExecSpec javaExecSpec) { + javaExecSpec.classpath( getProject().getConfigurations().getByName( "jakartaeeTransformTool" ) ); + javaExecSpec.setMain( "org.eclipse.transformer.jakarta.JakartaTransformer" ); + javaExecSpec.args( + sourceJar.get().getAsFile().getAbsolutePath(), + targetJar.get().getAsFile().getAbsolutePath(), + "-q", + "-tr", getProject().getRootProject().file( "rules/jakarta-renames.properties" ).getAbsolutePath(), + "-tv", getProject().getRootProject().file( "rules/jakarta-versions.properties" ).getAbsolutePath(), + "-td", getProject().getRootProject().file( "rules/jakarta-direct.properties" ).getAbsolutePath() + ); + } + }); + } +} diff --git a/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle b/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle index 13d32b668564..ac9a622e8189 100644 --- a/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle +++ b/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle @@ -1,3 +1,5 @@ +import javax.inject.Inject + /* * Hibernate, Relational Persistence for Idiomatic Java * @@ -10,15 +12,24 @@ description = 'Annotation Processor to generate JPA 3 static metamodel classes' apply from: rootProject.file( 'gradle/published-java-module.gradle' ) configurations { - jakartaeeTransformJars + jakartaeeTransformTool } +// we do not want the much of the normal java plugin's behavior +compileJava.enabled false +processResources.enabled false +compileTestJava.enabled false +processTestResources.enabled false +jar.enabled false +javadocJar.enabled false +sourcesJar.enabled false + dependencies { // JAXB compile( libraries.jakarta_jaxb_api ) compile( libraries.jakarta_jaxb_runtime ) - jakartaeeTransformJars 'biz.aQute.bnd:biz.aQute.bnd.transform:5.1.1', + jakartaeeTransformTool 'biz.aQute.bnd:biz.aQute.bnd.transform:5.1.1', 'commons-cli:commons-cli:1.4', 'org.slf4j:slf4j-simple:1.7.30', 'org.slf4j:slf4j-api:1.7.26', @@ -59,10 +70,107 @@ jar { } javaexec { - classpath configurations.jakartaeeTransformJars + classpath configurations.jakartaeeTransformTool main = 'org.eclipse.transformer.jakarta.JakartaTransformer' args = transformerArgs } } } +} + +task transformJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-jpamodelgen jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-jpamodelgen').tasks.jar + mustRunAfter project(':hibernate-jpamodelgen').tasks.jar + + sourceJar project(':hibernate-jpamodelgen').tasks.jar.archiveFile + targetJar tasks.jar.archiveFile.get().asFile +} + +task transformSourcesJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-jpamodelgen sources jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-jpamodelgen').tasks.sourcesJar + mustRunAfter project(':hibernate-jpamodelgen').tasks.sourcesJar + + sourceJar project(':hibernate-jpamodelgen').tasks.sourcesJar.archiveFile + targetJar tasks.sourcesJar.archiveFile.get().asFile +} + +task transformJavadocJar(type: JakartaJarTransformation) { + description 'Transforms the hibernate-jpamodelgen javadoc jar using the JakartaTransformer tool' + + dependsOn project(':hibernate-jpamodelgen').tasks.javadocJar + mustRunAfter project(':hibernate-jpamodelgen').tasks.javadocJar + + sourceJar project(':hibernate-jpamodelgen').tasks.javadocJar.archiveFile + targetJar tasks.javadocJar.archiveFile.get().asFile +} + +configurations { + [apiElements, runtimeElements].each { + it.outgoing.artifacts.removeIf { + it.buildDependencies.getDependencies(null).contains(jar) + } + it.outgoing.artifact(tasks.transformJar.targetJar) { + builtBy tasks.transformJar + } + it.outgoing.artifact(tasks.transformSourcesJar.targetJar) { + builtBy tasks.transformSourcesJar + } + it.outgoing.artifact(tasks.transformJavadocJar.targetJar) { + builtBy tasks.transformJavadocJar + } + } +} + +@CacheableTask +abstract class JakartaJarTransformation extends DefaultTask { + private final RegularFileProperty sourceJar; + private final RegularFileProperty targetJar; + + @Inject + JakartaJarTransformation(ObjectFactory objectFactory) { + sourceJar = objectFactory.fileProperty(); + targetJar = objectFactory.fileProperty(); + } + + @InputFile + @PathSensitive( PathSensitivity.RELATIVE ) + RegularFileProperty getSourceJar() { + return sourceJar; + } + + void sourceJar(Object fileReference) { + sourceJar.set( project.file( fileReference ) ) + } + + @OutputFile + RegularFileProperty getTargetJar() { + return targetJar; + } + + void targetJar(Object fileReference) { + targetJar.set( project.file( fileReference ) ) + } + + @TaskAction + void transform() { + project.javaexec( new Action() { + @Override + void execute(JavaExecSpec javaExecSpec) { + javaExecSpec.classpath( getProject().getConfigurations().getByName( "jakartaeeTransformTool" ) ); + javaExecSpec.setMain( "org.eclipse.transformer.jakarta.JakartaTransformer" ); + javaExecSpec.args( + sourceJar.get().getAsFile().getAbsolutePath(), + targetJar.get().getAsFile().getAbsolutePath(), + "-q", + "-tr", getProject().getRootProject().file( "rules/jakarta-renames.properties" ).getAbsolutePath(), + "-tv", getProject().getRootProject().file( "rules/jakarta-versions.properties" ).getAbsolutePath(), + "-td", getProject().getRootProject().file( "rules/jakarta-direct.properties" ).getAbsolutePath() + ); + } + }); + } } \ No newline at end of file From 48d3da61e789d4494b9e6d5d3f16bd5a0c22f4c2 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 8 Mar 2022 18:29:02 +0100 Subject: [PATCH 036/201] HHH-15097 Report VARCHAR as recommended type for UUID to fix issues with AttributeConverters --- .../type/descriptor/java/UUIDTypeDescriptor.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/UUIDTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/UUIDTypeDescriptor.java index 694a5012b76c..30f8fe7b62f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/UUIDTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/UUIDTypeDescriptor.java @@ -7,10 +7,13 @@ package org.hibernate.type.descriptor.java; import java.io.Serializable; +import java.sql.Types; import java.util.UUID; import org.hibernate.internal.util.BytesHelper; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.spi.JdbcRecommendedSqlTypeMappingContext; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; /** * Descriptor for {@link UUID} handling. @@ -32,6 +35,11 @@ public UUID fromString(String string) { return ToStringTransformer.INSTANCE.parse( string ); } + @Override + public SqlTypeDescriptor getJdbcRecommendedSqlType(JdbcRecommendedSqlTypeMappingContext context) { + return context.getTypeConfiguration().getSqlTypeDescriptorRegistry().getDescriptor( Types.VARCHAR ); + } + @SuppressWarnings({ "unchecked" }) public X unwrap(UUID value, Class type, WrapperOptions options) { if ( value == null ) { From 2ced27148e4c1a320cbd1fb19927e8ba888c5879 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 8 Mar 2022 18:34:36 +0100 Subject: [PATCH 037/201] HHH-15097 Add test for boot error when using AttributeConverter for UUID --- .../test/jpa/convert/UUIDConverterTest.java | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/jpa/convert/UUIDConverterTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/jpa/convert/UUIDConverterTest.java b/hibernate-core/src/test/java/org/hibernate/test/jpa/convert/UUIDConverterTest.java new file mode 100644 index 000000000000..64f55753e4d0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/jpa/convert/UUIDConverterTest.java @@ -0,0 +1,104 @@ +/* + * 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 . + */ +package org.hibernate.test.jpa.convert; + +import java.util.UUID; +import javax.persistence.AttributeConverter; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Christian Beikov + */ +public class UUIDConverterTest extends BaseEntityManagerFunctionalTestCase { + + private UUID uuid = UUID.randomUUID(); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { TestEntity.class }; + } + + @Test + @TestForIssue(jiraKey = "HHH-15097") + public void testSqlTypeDescriptorForConverted() { + // persist the record. + Integer rowId = doInJPA( this::entityManagerFactory, entityManager -> { + TestEntity e = new TestEntity(); + e.setSomeValue( new SomeValue( uuid = UUID.randomUUID() ) ); + entityManager.persist( e ); + return e.getId(); + } ); + + // retrieve the record and verify values. + doInJPA( this::entityManagerFactory, entityManager -> { + final TestEntity e = entityManager.find( TestEntity.class, rowId ); + assertEquals( uuid, e.getSomeValue().uuid ); + } ); + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + @GeneratedValue + private Integer id; + + @Convert(converter = UUIDConverter.class) + private SomeValue someValue; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public SomeValue getSomeValue() { + return someValue; + } + + public void setSomeValue(SomeValue someValue) { + this.someValue = someValue; + } + + } + + public static class UUIDConverter implements AttributeConverter { + @Override + public UUID convertToDatabaseColumn(SomeValue attribute) { + return attribute == null ? null : attribute.uuid; + } + + @Override + public SomeValue convertToEntityAttribute(UUID dbData) { + return dbData == null ? null : new SomeValue( dbData ); + } + } + + public static class SomeValue { + private final UUID uuid; + + public SomeValue(UUID uuid) { + this.uuid = uuid; + } + + public UUID getUuid() { + return uuid; + } + } +} From 37ef0627c989ce292f027f68c0bd3d3b6fdc7066 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Tue, 8 Mar 2022 18:11:49 -0600 Subject: [PATCH 038/201] HHH-15106 - fk() HQL function --- hibernate-core/src/main/antlr/hql-sql.g | 24 +++- hibernate-core/src/main/antlr/hql.g | 12 +- hibernate-core/src/main/antlr/sql-gen.g | 1 + .../hql/internal/ast/SqlASTFactory.java | 4 + .../hql/internal/ast/tree/DotNode.java | 74 ---------- .../hql/internal/ast/tree/FkRefNode.java | 133 ++++++++++++++++++ .../orm/test/notfound/FkRefTests.java | 27 ++-- 7 files changed, 183 insertions(+), 92 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FkRefNode.java diff --git a/hibernate-core/src/main/antlr/hql-sql.g b/hibernate-core/src/main/antlr/hql-sql.g index f7a1d9db8631..c1ab78645d32 100644 --- a/hibernate-core/src/main/antlr/hql-sql.g +++ b/hibernate-core/src/main/antlr/hql-sql.g @@ -261,6 +261,15 @@ tokens return dot; } + protected AST lookupFkRefSource(AST path) throws SemanticException { + if ( path.getType() == DOT ) { + return lookupProperty( path, true, isInSelect() ); + } + else { + return lookupNonQualifiedProperty( path ); + } + } + protected boolean isNonQualifiedPropertyRef(AST ident) { return false; } protected AST lookupNonQualifiedProperty(AST property) throws SemanticException { return property; } @@ -746,12 +755,15 @@ identifier ; addrExpr! [ boolean root ] - : #(d:DOT lhs:addrExprLhs rhs:propertyName ) { + : #(d:DOT lhs:addrExprLhs rhs:propertyName ) { // This gives lookupProperty() a chance to transform the tree // to process collection properties (.elements, etc). #addrExpr = #(#d, #lhs, #rhs); #addrExpr = lookupProperty(#addrExpr,root,false); } + | fk_ref:fkRef { + #addrExpr = #fk_ref; + } | #(i:INDEX_OP lhs2:addrExprLhs rhs2:expr [ null ]) { #addrExpr = #(#i, #lhs2, #rhs2); processIndex(#addrExpr); @@ -776,6 +788,12 @@ addrExpr! [ boolean root ] } ; +fkRef + : #( r:FK_REF p:propertyRef ) { + #p = lookupProperty( #p, false, isInSelect() ); + } + ; + addrExprLhs : addrExpr [ false ] ; @@ -785,7 +803,6 @@ propertyName | CLASS | ELEMENTS | INDICES - | FK_REF ; propertyRef! @@ -798,8 +815,7 @@ propertyRef! #propertyRef = #(#d, #lhs, #rhs); #propertyRef = lookupProperty(#propertyRef,false,true); } - | - p:identifier { + | p:identifier { // In many cases, things other than property-refs are recognized // by this propertyRef rule. Some of those I have seen: // 1) select-clause from-aliases diff --git a/hibernate-core/src/main/antlr/hql.g b/hibernate-core/src/main/antlr/hql.g index 7784620f5c91..a8b32453c6e4 100644 --- a/hibernate-core/src/main/antlr/hql.g +++ b/hibernate-core/src/main/antlr/hql.g @@ -46,6 +46,7 @@ tokens EXISTS="exists"; FALSE="false"; FETCH="fetch"; + FK_REF; FROM="from"; FULL="full"; GROUP="group"; @@ -722,7 +723,8 @@ atom // level 0 - the basic element of an expression primaryExpression - : { validateSoftKeyword("function") && LA(2) == OPEN && LA(3) == QUOTED_STRING }? jpaFunctionSyntax + : { validateSoftKeyword("fk") && LA(2) == OPEN }? fkRefPath + | { validateSoftKeyword("function") && LA(2) == OPEN && LA(3) == QUOTED_STRING }? jpaFunctionSyntax | { validateSoftKeyword("cast") && LA(2) == OPEN }? castFunction | { validateSoftKeyword("size") && LA(2) == OPEN }? collectionSizeFunction | identPrimary ( options {greedy=true;} : DOT^ "class" )? @@ -731,6 +733,12 @@ primaryExpression | OPEN! (expressionOrVector | subQuery) CLOSE! ; +fkRefPath! + : "fk" OPEN p:identPrimary CLOSE { + #fkRefPath = #( [FK_REF], #p ); + } + ; + jpaFunctionSyntax! : i:IDENT OPEN n:QUOTED_STRING (COMMA a:exprList)? CLOSE { final String functionName = unquote( #n.getText() ); @@ -968,8 +976,6 @@ MOD: '%'; COLON: ':'; PARAM: '?'; -FK_REF: "{fk}"; - IDENT options { testLiterals=true; } : ID_START_LETTER ( ID_LETTER )* { diff --git a/hibernate-core/src/main/antlr/sql-gen.g b/hibernate-core/src/main/antlr/sql-gen.g index b59667c79658..a5cfb13ee592 100644 --- a/hibernate-core/src/main/antlr/sql-gen.g +++ b/hibernate-core/src/main/antlr/sql-gen.g @@ -509,6 +509,7 @@ parameter addrExpr : #(r:DOT . .) { out(r); } + | #(fk:FK_REF .) { out(fk); } | i:ALIAS_REF { out(i); } | j:INDEX_OP { out(j); } | v:RESULT_VARIABLE_REF { out(v); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlASTFactory.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlASTFactory.java index acf4146b1fcc..7b038ccc7bb0 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlASTFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/SqlASTFactory.java @@ -15,6 +15,7 @@ import org.hibernate.hql.internal.ast.tree.BinaryLogicOperatorNode; import org.hibernate.hql.internal.ast.tree.BooleanLiteralNode; import org.hibernate.hql.internal.ast.tree.CastFunctionNode; +import org.hibernate.hql.internal.ast.tree.FkRefNode; import org.hibernate.hql.internal.ast.tree.NullNode; import org.hibernate.hql.internal.ast.tree.SearchedCaseNode; import org.hibernate.hql.internal.ast.tree.SimpleCaseNode; @@ -196,6 +197,9 @@ public Class getASTNodeType(int tokenType) { case NULL : { return NullNode.class; } + case FK_REF: { + return FkRefNode.class; + } default: return SqlNode.class; } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java index cc2b7863291a..191bf7cd9fb1 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java @@ -65,8 +65,6 @@ public QueryException buildIllegalCollectionDereferenceException(String property public static enum DereferenceType { UNKNOWN, ENTITY, - FK_REF, - FK_REF_LHS, COMPONENT, COLLECTION, PRIMITIVE, @@ -267,41 +265,6 @@ private void initText() { private Type prepareLhs() throws SemanticException { final FromReferenceNode lhs = getLhs(); lhs.prepareForDot( propertyName ); - - if ( "{fk}".equals( propertyName ) ) { - // we are processing the {fk} node and "preparing" its - // lhs, which is the to-one mapping. it is significantly - // easiest to simply handle this case here. The other - // option would be to muck around with the PropertyMapping - // for the entity containing the to-one to add mapping for - // the {fk} path; but unfortunately that still means - // additional changes here - final Type toOneType = lhs.getDataType(); - assert toOneType instanceof EntityType; - - dereferenceType = DereferenceType.FK_REF; - - final String toOnePropertyPath; - if ( lhs instanceof DotNode ) { - final DotNode lhsAsDotNode = (DotNode) lhs; - toOnePropertyPath = lhsAsDotNode.propertyPath; - } - else { - assert lhs instanceof IdentNode; - final IdentNode lhsAsIdentNode = (IdentNode) lhs; - toOnePropertyPath = lhsAsIdentNode.getOriginalText(); - } - propertyPath = toOnePropertyPath + ".{fk}"; - - // resolve the fk key columns - final String tableAlias = getLhs().getFromElement().getTableAlias(); - this.columns = getFromElement().getElementType().toColumns( tableAlias, toOnePropertyPath, false ); - - // and the key type - final Type keyType = getFromElement().getElementType().getPropertyType( toOnePropertyPath, toOnePropertyPath ); - setDataType( keyType ); - } - return getDataType(); } @@ -434,27 +397,9 @@ private void dereferenceEntity( // our parent is another dot node, meaning we are being further de-referenced. // depending on the exact de-reference we may need to generate a physical join. - // generally we will need the join unless: - // * this is a to-one reference && either: - // * the de-reference path is the associated entity's identifier - // && the to-one is not marked with @NotFound - // * the de-reference is the special {fk} token property = parentAsDotNode.propertyName; - if ( "{fk}".equals( property ) ) { - if ( parentAsDotNode.getNextSibling() != null ) { - throw new QueryException( - "{fk} reference cannot be further de-referenced - " + propertyPath + " -> " + parentAsDotNode.getNextSibling().getText() - ); - } - - dereferenceType = DereferenceType.FK_REF_LHS; - -// dereferenceForeignKeyReference( toOneType, ( (DotNode) parentAsDotNode.getLhs() ).getPropertyPath() ); - return; - } - if ( generateJoin ) { if ( implicitJoin && ( toOneType.hasNotFoundAction() || toOneType.isNullable() ) ) { joinIsNeeded = true; @@ -497,25 +442,6 @@ private static boolean isDotNode(AST n) { return n != null && n.getType() == SqlTokenTypes.DOT; } - private void dereferenceForeignKeyReference(EntityType toOneType, String toOnePropertyName) throws SemanticException { - dereferenceType = DereferenceType.FK_REF; - - if ( LOG.isDebugEnabled() ) { - LOG.debugf( "dereferenceForeignKeyReference() : %s", getPath() ); - } - - final String fkRefPath = propertyPath + ".{fk}"; - propertyPath = fkRefPath; - - // resolve the fk key columns - final String tableAlias = getLhs().getFromElement().getTableAlias(); - this.columns = getFromElement().getElementType().toColumns( tableAlias, fkRefPath, false ); - - // and the key type - setDataType( getFromElement().getElementType().getPropertyType( toOnePropertyName, fkRefPath ) ); - } - - private void dereferenceEntityJoin( String classAlias, EntityType toOneType, diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FkRefNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FkRefNode.java new file mode 100644 index 000000000000..9d036aa49787 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FkRefNode.java @@ -0,0 +1,133 @@ +/* + * 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 . + */ +package org.hibernate.hql.internal.ast.tree; + +import org.hibernate.QueryException; +import org.hibernate.hql.internal.ast.InvalidPathException; +import org.hibernate.type.BasicType; +import org.hibernate.type.CompositeType; +import org.hibernate.type.ManyToOneType; +import org.hibernate.type.Type; + +import antlr.SemanticException; +import antlr.collections.AST; + +/** + * Represents a `fk()` pseudo-function + * + * @author Steve Ebersole + */ +public class FkRefNode + extends HqlSqlWalkerNode + implements ResolvableNode, DisplayableNode, PathNode { + private FromReferenceNode toOnePath; + + private Type fkType; + private String[] columns; + + private FromReferenceNode resolveToOnePath() { + if ( toOnePath == null ) { + try { + resolve( false, true ); + } + catch (SemanticException e) { + final String msg = "Unable to resolve to-one path `fk(" + toOnePath.getPath() + "`)"; + throw new QueryException( msg, new InvalidPathException( msg ) ); + } + } + + assert toOnePath != null; + return toOnePath; + } + + @Override + public String getDisplayText() { + final FromReferenceNode toOnePath = resolveToOnePath(); + return "fk(`" + toOnePath.getDisplayText() + "` )"; + } + + @Override + public String getPath() { + return toOnePath.getDisplayText() + ".{fk}"; + } + + @Override + public void resolve( + boolean generateJoin, + boolean implicitJoin) throws SemanticException { + if ( toOnePath != null ) { + return; + } + + final AST firstChild = getFirstChild(); + assert firstChild instanceof FromReferenceNode; + + toOnePath = (FromReferenceNode) firstChild; + toOnePath.resolve( false, true, null, toOnePath.getFromElement() ); + + final Type sourcePathDataType = toOnePath.getDataType(); + if ( ! ( sourcePathDataType instanceof ManyToOneType ) ) { + throw new InvalidPathException( + "Argument to fk() function must be a to-one path, but found " + sourcePathDataType + ); + } + final ManyToOneType toOneType = (ManyToOneType) sourcePathDataType; + final FromElement fromElement = toOnePath.getFromElement(); + + fkType = toOneType.getIdentifierOrUniqueKeyType( getSessionFactoryHelper().getFactory() ); + assert fkType instanceof BasicType + || fkType instanceof CompositeType; + + columns = fromElement.getElementType().toColumns( + fromElement.getTableAlias(), + toOneType.getPropertyName(), + getWalker().isInSelect() + ); + assert columns != null && columns.length > 0; + + setText( String.join( ", ", columns ) ); + } + + @Override + public void resolve( + boolean generateJoin, + boolean implicitJoin, + String classAlias, + AST parent, + AST parentPredicate) throws SemanticException { + resolve( false, true ); + } + + @Override + public void resolve( + boolean generateJoin, + boolean implicitJoin, + String classAlias, + AST parent) throws SemanticException { + resolve( false, true ); + } + + @Override + public void resolve( + boolean generateJoin, + boolean implicitJoin, + String classAlias) throws SemanticException { + resolve( false, true ); + } + + @Override + public void resolveInFunctionCall( + boolean generateJoin, + boolean implicitJoin) throws SemanticException { + resolve( false, true ); + } + + @Override + public void resolveIndex(AST parent) throws SemanticException { + throw new InvalidPathException( "fk() paths cannot be de-referenced as indexed path" ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/FkRefTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/FkRefTests.java index 678b958a0c2f..590e7322dc88 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/FkRefTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/FkRefTests.java @@ -14,9 +14,9 @@ import javax.persistence.JoinColumn; import javax.persistence.OneToOne; +import org.hibernate.QueryException; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; -import org.hibernate.hql.internal.ast.QuerySyntaxException; import org.hibernate.testing.jdbc.SQLStatementInspector; import org.hibernate.testing.orm.junit.DomainModel; @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; /** * Tests for the new `{fk}` HQL token @@ -39,14 +40,14 @@ public class FkRefTests { @Test - @JiraKey( "HHH-15060" ) + @JiraKey( "HHH-15106" ) public void testSimplePredicateUse(SessionFactoryScope scope) { final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); // there is a Coin which has a currency_fk = 1 scope.inTransaction( (session) -> { - final String hql = "select c from Coin c where c.currency.{fk} = 1"; + final String hql = "select c from Coin c where fk(c.currency) = 1"; final List coins = session.createQuery( hql, Coin.class ).getResultList(); assertThat( coins ).hasSize( 1 ); assertThat( coins.get( 0 ) ).isNotNull(); @@ -69,7 +70,7 @@ public void testSimplePredicateUse(SessionFactoryScope scope) { // check using `currency` as a naked "property-ref" scope.inTransaction( (session) -> { - final String hql = "select c from Coin c where currency.{fk} = 1"; + final String hql = "select c from Coin c where fk(currency) = 1"; final List coins = session.createQuery( hql, Coin.class ).getResultList(); assertThat( coins ).hasSize( 1 ); assertThat( coins.get( 0 ) ).isNotNull(); @@ -81,14 +82,14 @@ public void testSimplePredicateUse(SessionFactoryScope scope) { } @Test - @JiraKey( "HHH-15060" ) + @JiraKey( "HHH-15106" ) public void testNullnessPredicateUse(SessionFactoryScope scope) { final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); // there is one Coin (id=3) which has a null currency_fk scope.inTransaction( (session) -> { - final String hql = "select c from Coin c where c.currency.{fk} is null"; + final String hql = "select c from Coin c where fk(c.currency) is null"; final List coins = session.createQuery( hql, Coin.class ).getResultList(); assertThat( coins ).hasSize( 1 ); assertThat( coins.get( 0 ) ).isNotNull(); @@ -103,7 +104,7 @@ public void testNullnessPredicateUse(SessionFactoryScope scope) { // check using `currency` as a naked "property-ref" scope.inTransaction( (session) -> { - final String hql = "select c from Coin c where currency.{fk} is null"; + final String hql = "select c from Coin c where fk(currency) is null"; final List coins = session.createQuery( hql, Coin.class ).getResultList(); assertThat( coins ).hasSize( 1 ); assertThat( coins.get( 0 ) ).isNotNull(); @@ -116,18 +117,22 @@ public void testNullnessPredicateUse(SessionFactoryScope scope) { } @Test - @JiraKey( "HHH-15060" ) + @JiraKey( "HHH-15106" ) public void testFkRefDereferenceNotAllowed(SessionFactoryScope scope) { final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); scope.inTransaction( (session) -> { try { - final String hql = "select c from Coin c where c.currency.{fk}.something"; + final String hql = "select c from Coin c where fk(c.currency).something"; final List coins = session.createQuery( hql, Coin.class ).getResultList(); + fail( "Expecting failure" ); } catch (IllegalArgumentException expected) { - assertThat( expected.getCause() ).isInstanceOf( QuerySyntaxException.class ); + assertThat( expected.getCause() ).isInstanceOf( QueryException.class ); + } + catch (Exception e) { + fail( "Unexpected failure type : " + e ); } } ); @@ -137,7 +142,7 @@ public void testFkRefDereferenceNotAllowed(SessionFactoryScope scope) { final List coins = session.createQuery( hql, Coin.class ).getResultList(); } catch (IllegalArgumentException expected) { - assertThat( expected.getCause() ).isInstanceOf( QuerySyntaxException.class ); + assertThat( expected.getCause() ).isInstanceOf( QueryException.class ); } } ); } From 83b6e6e3d5bdd7d68fee5a025e5bdffc79563e8c Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 8 Mar 2022 22:28:08 +0100 Subject: [PATCH 039/201] HHH-15105 Test and fix for NPE when access default query cache region statistics --- .../cache/internal/EnabledCaching.java | 4 + .../test/cache/CacheRegionStatisticsTest.java | 114 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/cache/CacheRegionStatisticsTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java index 9ce34807a33b..ae218d636f83 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java @@ -491,6 +491,10 @@ public QueryResultsCache getQueryResultsCacheStrictly(String regionName) { return null; } + if ( regionName == null || regionName.equals( getDefaultQueryResultsCache().getRegion().getName() ) ) { + return getDefaultQueryResultsCache(); + } + return namedQueryResultsCacheMap.get( regionName ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/cache/CacheRegionStatisticsTest.java b/hibernate-core/src/test/java/org/hibernate/test/cache/CacheRegionStatisticsTest.java new file mode 100644 index 000000000000..5517650797d3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cache/CacheRegionStatisticsTest.java @@ -0,0 +1,114 @@ +/* + * 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 . + */ +package org.hibernate.test.cache; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.CacheRegionStatistics; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.cache.CachingRegionFactory; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * @author Christian Beikov + */ +public class CacheRegionStatisticsTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + @TestForIssue( jiraKey = "HHH-15105") + public void testAccessDefaultQueryRegionStatistics() { + final Statistics statistics = sessionFactory().getStatistics(); + final CacheRegionStatistics queryRegionStatistics = statistics.getQueryRegionStatistics( + "default-query-results-region" + ); + doInHibernate( + this::sessionFactory, session -> { + List resultList = session.createQuery( "from Dog", Dog.class ) + .setCacheable( true ) + .getResultList(); + + assertEquals( 1, queryRegionStatistics.getMissCount() ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, true ); + ssrb.applySetting( AvailableSettings.USE_QUERY_CACHE, true ); + ssrb.applySetting( AvailableSettings.CACHE_REGION_FACTORY, new CachingRegionFactory() ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void applyMetadataSources(MetadataSources metadataSources) { + super.applyMetadataSources( metadataSources ); + metadataSources.addAnnotatedClass( Dog.class ); + } + + @Before + public void setupData() { + doInHibernate( + this::sessionFactory, session -> { + Dog yogi = new Dog( "Yogi" ); + yogi.nickNames.add( "The Yog" ); + yogi.nickNames.add( "Little Boy" ); + yogi.nickNames.add( "Yogaroni Macaroni" ); + Dog irma = new Dog( "Irma" ); + irma.nickNames.add( "Squirmy" ); + irma.nickNames.add( "Bird" ); + session.persist( yogi ); + session.persist( irma ); + } + ); + } + + @After + public void cleanupData() { + doInHibernate( + this::sessionFactory, session -> { + List dogs = session.createQuery( "from Dog", Dog.class ).getResultList(); + for ( Dog dog : dogs ) { + session.delete( dog ); + } + } + ); + } + + @Entity(name = "Dog") + public static class Dog { + @Id + private String name; + + @ElementCollection + private Set nickNames = new HashSet<>(); + + public Dog(String name) { + this.name = name; + } + + public Dog() { + } + } +} From c3cefd74ca7dfecd84023fbc1f7456252bec9882 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 11 Mar 2022 08:56:51 +0100 Subject: [PATCH 040/201] HHH-15115 Deleting an entity with Joined inheritance and default schema set is throwing and error --- .../AbstractCteValuesListBulkIdHandler.java | 2 +- .../GlobalTemporaryTableBulkIdStrategy.java | 2 +- .../LocalTemporaryTableBulkIdStrategy.java | 2 +- .../PersistentTableBulkIdStrategy.java | 2 +- .../JoinedInheritanceDeletionTest.java | 124 ++++++++++++++++++ 5 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/inheritance/JoinedInheritanceDeletionTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/AbstractCteValuesListBulkIdHandler.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/AbstractCteValuesListBulkIdHandler.java index c8c118cf15a6..732b07087862 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/AbstractCteValuesListBulkIdHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/AbstractCteValuesListBulkIdHandler.java @@ -72,7 +72,7 @@ protected String determineIdTableName(Queryable persister) { "HT_" + StringHelper.unquote( persister.getTableName(), jdbcEnvironment.getDialect() ) ).render(); - return persister.getFactory().getSqlStringGenerationContext().format( + return persister.getFactory().getSqlStringGenerationContext().formatWithoutDefaults( new QualifiedTableName( Identifier.toIdentifier( catalog ), Identifier.toIdentifier( schema ), diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/global/GlobalTemporaryTableBulkIdStrategy.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/global/GlobalTemporaryTableBulkIdStrategy.java index 17dbd9de3107..b9995532786a 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/global/GlobalTemporaryTableBulkIdStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/global/GlobalTemporaryTableBulkIdStrategy.java @@ -112,7 +112,7 @@ protected IdTableInfoImpl buildIdTableInfo( context.dropStatements.add( buildIdTableDropStatement( idTable, sqlStringGenerationContext ) ); } - final String renderedName = sqlStringGenerationContext.format( idTable.getQualifiedTableName() ); + final String renderedName = sqlStringGenerationContext.formatWithoutDefaults( idTable.getQualifiedTableName() ); return new IdTableInfoImpl( renderedName ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/local/LocalTemporaryTableBulkIdStrategy.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/local/LocalTemporaryTableBulkIdStrategy.java index 715cb7fc9361..e8f936e2d53a 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/local/LocalTemporaryTableBulkIdStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/local/LocalTemporaryTableBulkIdStrategy.java @@ -119,7 +119,7 @@ protected IdTableInfoImpl buildIdTableInfo( context.dropStatements.add( dropStatement ); } return new IdTableInfoImpl( - sqlStringGenerationContext.format( idTable.getQualifiedTableName() ), + sqlStringGenerationContext.formatWithoutDefaults( idTable.getQualifiedTableName() ), buildIdTableCreateStatement( idTable, metadata, sqlStringGenerationContext ), dropStatement ); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/persistent/PersistentTableBulkIdStrategy.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/persistent/PersistentTableBulkIdStrategy.java index dec96832e55d..98a3c7bd4d56 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/persistent/PersistentTableBulkIdStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/persistent/PersistentTableBulkIdStrategy.java @@ -127,7 +127,7 @@ protected IdTableInfoImpl buildIdTableInfo( MetadataImplementor metadata, PreparationContextImpl context, SqlStringGenerationContext sqlStringGenerationContext) { - final String renderedName = sqlStringGenerationContext.format( idTable.getQualifiedTableName() ); + final String renderedName = sqlStringGenerationContext.formatWithoutDefaults( idTable.getQualifiedTableName() ); context.creationStatements.add( buildIdTableCreateStatement( idTable, metadata, sqlStringGenerationContext diff --git a/hibernate-core/src/test/java/org/hibernate/test/inheritance/JoinedInheritanceDeletionTest.java b/hibernate-core/src/test/java/org/hibernate/test/inheritance/JoinedInheritanceDeletionTest.java new file mode 100644 index 000000000000..43ce1a8b3440 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/inheritance/JoinedInheritanceDeletionTest.java @@ -0,0 +1,124 @@ +package org.hibernate.test.inheritance; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.PostgreSQL81Dialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +@RequiresDialect(PostgreSQL81Dialect.class) +@TestForIssue( jiraKey = "HHH-15115") +public class JoinedInheritanceDeletionTest extends BaseCoreFunctionalTestCase { + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty( AvailableSettings.DEFAULT_SCHEMA, "public" ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Employee.class, + Customer.class + }; + } + + @Before + public void setUp() { + inTransaction( + session -> { + Person person = new Person( 1, "Bob" ); + Employee employee = new Employee( 2, "Chris", "Software Engineer" ); + Customer customer = new Customer( 3, "Miriam", "" ); + + session.save( person ); + session.save( employee ); + session.save( customer ); + } + ); + } + + @Test + public void testDelete() { + inTransaction( + session -> { + session.createQuery( "delete from Person" ).executeUpdate(); + } + ); + } + + @Entity(name = "Person") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Person { + + @Id + private Integer id; + + private String name; + + public Person() { + } + + public Person(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + } + + @Entity(name = "Customer") + public static class Customer extends Person { + + private String comments; + + public Customer() { + } + + public Customer(Integer id, String name, String comments) { + super( id, name ); + this.comments = comments; + } + + public String getComments() { + return comments; + } + + } + + @Entity(name = "Employee") + public static class Employee extends Person { + + private String title; + + public Employee() { + } + + public Employee(Integer id, String name, String title) { + super( id, name ); + this.title = title; + } + + public String getTitle() { + return title; + } + } + +} From f78ae8b4c65d994510316fb0884117b8b45bf9c6 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 9 Mar 2022 15:19:22 +0100 Subject: [PATCH 041/201] HHH-15113 Exception setting ParameterExpressions on Update Queries --- .../criteria/internal/CriteriaUpdateImpl.java | 13 ++- .../internal/compile/CriteriaCompiler.java | 8 +- .../expression/ParameterExpressionImpl.java | 5 +- .../CriteriaUpdateWithParametersTest.java | 100 ++++++++++++++++++ 4 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/query/CriteriaUpdateWithParametersTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaUpdateImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaUpdateImpl.java index 3e4b9a0afde9..682c9d9e05e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaUpdateImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaUpdateImpl.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; +import javax.persistence.Parameter; import javax.persistence.criteria.CriteriaUpdate; import javax.persistence.criteria.Expression; import javax.persistence.criteria.Path; @@ -69,9 +70,15 @@ public CriteriaUpdate set(Path attributePath, Expression @SuppressWarnings("unchecked") public CriteriaUpdate set(String attributeName, Object value) { final Path attributePath = getRoot().get( attributeName ); - final Expression valueExpression = value == null - ? criteriaBuilder().nullLiteral( attributePath.getJavaType() ) - : criteriaBuilder().literal( value ); + final Expression valueExpression; + if ( value instanceof Expression ) { + valueExpression = (Expression) value; + } + else { + valueExpression = value == null + ? criteriaBuilder().nullLiteral( attributePath.getJavaType() ) + : criteriaBuilder().literal( value ); + } addAssignment( attributePath, valueExpression ); return this; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java index e00e8ad5b222..b41adfa6377d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java @@ -11,6 +11,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.persistence.Parameter; import javax.persistence.TypedQuery; import javax.persistence.criteria.ParameterExpression; @@ -22,6 +23,7 @@ import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.query.criteria.LiteralHandlingMode; +import org.hibernate.query.criteria.internal.expression.ParameterExpressionImpl; import org.hibernate.query.criteria.internal.expression.function.FunctionExpression; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.sql.ast.Clause; @@ -107,8 +109,9 @@ else if ( criteriaQueryParameter.getPosition() != null ) { ); } else { + final String name = generateParameterName(); parameterInfo = new ExplicitParameterInfo( - generateParameterName(), + name, null, criteriaQueryParameter.getJavaType() ); @@ -132,6 +135,9 @@ public Class getJavaType() { } public void bind(TypedQuery typedQuery) { + if ( literal instanceof Parameter ) { + return; + } typedQuery.setParameter( parameterName, literal ); } }; diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java index 3bac2a93ccf1..7875341724e7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java @@ -23,7 +23,7 @@ public class ParameterExpressionImpl extends ExpressionImpl implements ParameterExpression, Serializable { - private final String name; + private String name; private final Integer position; public ParameterExpressionImpl( @@ -75,6 +75,9 @@ public void registerParameters(ParameterRegistry registry) { @Override public String render(RenderingContext renderingContext) { final ExplicitParameterInfo parameterInfo = renderingContext.registerExplicitParameter( this ); + if ( name == null && position == null ) { + name = parameterInfo.getName(); + } return parameterInfo.render(); } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/CriteriaUpdateWithParametersTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/CriteriaUpdateWithParametersTest.java new file mode 100644 index 000000000000..c1cda6c51ad6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/CriteriaUpdateWithParametersTest.java @@ -0,0 +1,100 @@ +package org.hibernate.jpa.test.query; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.Test; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Query; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaUpdate; +import javax.persistence.criteria.ParameterExpression; +import javax.persistence.criteria.Root; +import javax.persistence.metamodel.EntityType; + +@Jpa( + annotatedClasses = CriteriaUpdateWithParametersTest.Person.class +) +@TestForIssue( jiraKey = "HHH-15113") +public class CriteriaUpdateWithParametersTest { + + @Test + public void testCriteriaUpdate(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + final CriteriaUpdate criteriaUpdate = criteriaBuilder.createCriteriaUpdate( Person.class ); + final Root root = criteriaUpdate.from( Person.class ); + + final ParameterExpression intValueParameter = criteriaBuilder.parameter( Integer.class ); + final ParameterExpression stringValueParameter = criteriaBuilder.parameter( String.class ); + + final EntityType personEntityType = entityManager.getMetamodel().entity( Person.class ); + + criteriaUpdate.set( root.get( personEntityType.getSingularAttribute( "age", Integer.class ) ), intValueParameter ); + + criteriaUpdate.where( criteriaBuilder.equal( + root.get( personEntityType.getSingularAttribute( "name", String.class ) ), + stringValueParameter + ) ); + + final Query query = entityManager.createQuery( criteriaUpdate ); + query.setParameter( intValueParameter, 9 ); + query.setParameter( stringValueParameter, "Luigi" ); + + query.executeUpdate(); + } + ); + } + + @Test + public void testCriteriaUpdate2(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + final CriteriaUpdate criteriaUpdate = criteriaBuilder.createCriteriaUpdate( Person.class ); + final Root root = criteriaUpdate.from( Person.class ); + + final ParameterExpression intValueParameter = criteriaBuilder.parameter( Integer.class ); + final ParameterExpression stringValueParameter = criteriaBuilder.parameter( String.class ); + + criteriaUpdate.set( "age", intValueParameter ); + criteriaUpdate.where( criteriaBuilder.equal( root.get( "name" ), stringValueParameter ) ); + + final Query query = entityManager.createQuery( criteriaUpdate ); + query.setParameter( intValueParameter, 9 ); + query.setParameter( stringValueParameter, "Luigi" ); + + query.executeUpdate(); + } + ); + } + + @Entity(name = "Person") + public static class Person { + + @Id + private String id; + + private String name; + + private Integer age; + + public Person() { + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public Integer getAge() { + return age; + } + } +} From c030dc4205ad2dbae4aee47704496a22d6252793 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 22 Dec 2021 11:19:50 +0000 Subject: [PATCH 042/201] HHH-14996 Upgrade to JBoss Logging Processor (and matching Annotations) 2.2.1.Final --- gradle/libraries.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 1cbb98fc34b6..6f4a61a520c0 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -91,8 +91,8 @@ ext { // logging logging: 'org.jboss.logging:jboss-logging:3.4.3.Final', - logging_annotations: 'org.jboss.logging:jboss-logging-annotations:2.1.0.Final', - logging_processor: 'org.jboss.logging:jboss-logging-processor:2.1.0.Final', + logging_annotations: 'org.jboss.logging:jboss-logging-annotations:2.2.1.Final', + logging_processor: 'org.jboss.logging:jboss-logging-processor:2.2.1.Final', // jaxb task jaxb_api: "javax.xml.bind:jaxb-api:${jaxbApiVersion}", From dba829e88d4dcc753bd8840a0ef0fb9c5a08a990 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 14 Mar 2022 23:32:25 +0000 Subject: [PATCH 043/201] HHH-15119 Upgrade to ByteBuddy 1.12.8 --- gradle/libraries.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 6f4a61a520c0..2392f5c96973 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -26,7 +26,7 @@ ext { weldVersion = '3.1.5.Final' jakartaWeldVersion = '4.0.1.SP1' - byteBuddyVersion = '1.12.7' + byteBuddyVersion = '1.12.8' agroalVersion = '1.9' From b54ffe3384c1af577822ad0bbeb07448b4af65da Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 26 Jan 2022 09:53:01 +0100 Subject: [PATCH 044/201] HHH-15051 Add property mapping for target FK attributes for associations with id classes --- .../entity/AbstractPropertyMapping.java | 4 + .../org/hibernate/query/GroupByAliasTest.java | 4 +- .../VirtualKeyManyToOnePropertyPathTest.java | 75 +++++++++++++++++++ 3 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/hql/VirtualKeyManyToOnePropertyPathTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java index fd37e9955bdb..fab1ad661088 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java @@ -26,6 +26,7 @@ import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.CompositeType; +import org.hibernate.type.EmbeddedComponentType; import org.hibernate.type.EntityType; import org.hibernate.type.ManyToOneType; import org.hibernate.type.OneToOneType; @@ -410,6 +411,9 @@ protected void initIdentifierPropertyPaths( addPropertyPath( idpath2, idtype, columns, columnReaders, columnReaderTemplates, formulaTemplates, factory ); initPropertyPaths( idpath2, idtype, columns, columnReaders, columnReaderTemplates, formulaTemplates, factory ); } + else if ( (! etype.isNullable() ) && idtype.isComponentType() && idtype instanceof EmbeddedComponentType ) { + initComponentPropertyPaths( path, (CompositeType) idtype, columns, columnReaders, columnReaderTemplates, formulaTemplates, factory ); + } } private boolean hasNonIdentifierPropertyNamedId(final EntityType entityType, final Mapping factory) { diff --git a/hibernate-core/src/test/java/org/hibernate/query/GroupByAliasTest.java b/hibernate-core/src/test/java/org/hibernate/query/GroupByAliasTest.java index a4199263c0cc..1aaeebf5d899 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/GroupByAliasTest.java +++ b/hibernate-core/src/test/java/org/hibernate/query/GroupByAliasTest.java @@ -89,8 +89,8 @@ public void testCompoundIdAlias() { List list = doInJPA(this::entityManagerFactory, entityManager -> { return entityManager.createQuery( - "select p.association as id_alias, sum(p.age) " + - "from Person p group by id_alias, p.association.id, p.association.name order by id_alias", Tuple.class) + "select a as id_alias, sum(p.age) " + + "from Person p join p.association a group by id_alias, a.id, a.name order by id_alias", Tuple.class) .getResultList(); }); diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/VirtualKeyManyToOnePropertyPathTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/VirtualKeyManyToOnePropertyPathTest.java new file mode 100644 index 000000000000..d61fb5182115 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/VirtualKeyManyToOnePropertyPathTest.java @@ -0,0 +1,75 @@ +/* + * 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 . + */ +package org.hibernate.test.hql; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.boot.SessionFactoryBuilder; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertFalse; + +/** + * @author Christian Beikov + */ +public class VirtualKeyManyToOnePropertyPathTest extends BaseNonConfigCoreFunctionalTestCase { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {Item.class, OrderItem.class}; + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + sqlStatementInterceptor = new SQLStatementInterceptor( sfb ); + } + + @Test + @TestForIssue(jiraKey = "HHH-15051") + public void tstPropertyPathVirtualIdOfKeyManyToOneProducesNoJoin() { + doInHibernate( this::sessionFactory, session -> { + sqlStatementInterceptor.clear(); + session.createQuery( "SELECT o.item.id1 FROM OrderItem o", Long.class ).getResultList(); + sqlStatementInterceptor.assertExecutedCount( 1 ); + assertFalse( sqlStatementInterceptor.getSqlQueries().get( 0 ).contains( " join " ) ); + } ); + } + + @Entity(name = "Item") + public static class Item implements Serializable { + @Id + Long id1; + @Id + Long id2; + + public Item() { + } + + } + + @Entity(name = "OrderItem") + public static class OrderItem implements Serializable { + @Id + long id; + @Id + @ManyToOne + Item item; + + public OrderItem() { + } + } +} From 7bb26851eddd8ff398a31dfbd819f7405f7ee7e1 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 15 Mar 2022 13:46:20 +0000 Subject: [PATCH 045/201] Disable uploading to Sourceforge --- release/release.gradle | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/release/release.gradle b/release/release.gradle index 7c95227d6879..66877458e9e5 100644 --- a/release/release.gradle +++ b/release/release.gradle @@ -249,29 +249,6 @@ task buildBundles(type: Task, dependsOn: [distZip,distTar]) { description = "Builds all release bundles" } -task uploadBundlesSourceForge(type: Exec, dependsOn: buildBundles) { - description = "Uploads release bundles to SourceForge" - - final String url = "frs.sourceforge.net:/home/frs/project/hibernate/hibernate-orm/${version}"; - - executable 'rsync' - args '-vr', '-e ssh', "${project.buildDir}/distributions/", url - - doFirst { - if ( rootProject.ormVersion.isSnapshot ) { - logger.error( "Cannot perform upload of SNAPSHOT bundles to SourceForge" ); - throw new RuntimeException( "Cannot perform upload of SNAPSHOT bundles to SourceForge" ) - } - else { - logger.lifecycle( "Uploading release bundles to SourceForge..." ) - } - } - - doLast { - logger.lifecycle( 'Done uploading release bundles to SourceForge' ) - } -} - configurations { bundles { description = 'Configuration used to group the archives output from the distribution plugin.' @@ -283,7 +260,7 @@ artifacts { bundles distZip } -task release( dependsOn: [releaseChecks, uploadDocumentation, uploadBundlesSourceForge] ) +task release( dependsOn: [releaseChecks, uploadDocumentation] ) task changeLogFile( dependsOn: [releaseChecks] ) { group = "Release" From 64272ba56173c5a723353741eb64a80c4b7ded58 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Tue, 15 Mar 2022 13:49:25 +0000 Subject: [PATCH 046/201] 5.6.6.Final --- changelog.txt | 27 +++++++++++++++++++++++++++ gradle/version.properties | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 5849eccb6de3..6a3279401611 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,33 @@ Hibernate 5 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 5.6.6.Final (March 15, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32031 + +** Bug + * [HHH-15115] - Deleting an entity with Joined inheritance and default schema set is throwing and error + * [HHH-15113] - Exception setting ParameterExpressions on Update Queries + * [HHH-15105] - Getting the CacheRegionStatistics before executing a query leads to a NPE later on + * [HHH-15097] - Hibernate fails to detect SQL type for AttributeConverter to UUID + * [HHH-15084] - JpaCompliantLifecycleStrategy uses deprecated BeanManager method that's gone in CDI 4.0 + * [HHH-15082] - JDBC Statement leaks after exceptions other than SQLException during insert/update/... + * [HHH-15069] - Backwards-incompatible changes in SequenceStyleGenerator (and others) following default_schema changes + * [HHH-15060] - Fix handling of associations with @NotFound + * [HHH-15051] - Association with id class misses property mapping for target FK attributes + * [HHH-14932] - Spatial support for PostgreSQL 10+ uses invalid WKB dialect + * [HHH-14817] - hibernate-core-jakarta source jar does not contain source + +** Improvement + * [HHH-15106] - fk() SQM function + * [HHH-15094] - Handle http://hibernate.org and https://* for all DTDs in LocalXmlResourceResolver + +** Task + * [HHH-15119] - Upgrade to ByteBuddy 1.12.8 + * [HHH-14996] - Upgrade to JBoss Logging Processor (and matching Annotations) 2.2.1.Final + + Changes in 5.6.5.Final (January 25, 2022) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/version.properties b/gradle/version.properties index 33771ce08ccb..b7687522cff7 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=5.6.6-SNAPSHOT \ No newline at end of file +hibernateVersion=5.6.6.Final \ No newline at end of file From 7d9fc870fa9bb21e37b743f9594b95ccb4f252ec Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Tue, 15 Mar 2022 13:53:05 +0000 Subject: [PATCH 047/201] 5.6.7-SNAPSHOT --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index b7687522cff7..fd8ec464b2a7 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=5.6.6.Final \ No newline at end of file +hibernateVersion=5.6.7-SNAPSHOT \ No newline at end of file From beae63ae2ec4e85ce6286f5f6ce7c91962d98ee0 Mon Sep 17 00:00:00 2001 From: Krzysztof Debski Date: Tue, 15 Mar 2022 17:22:35 +0100 Subject: [PATCH 048/201] remove a deprecation that was probably an accident --- .../hibernate/event/spi/AbstractPreDatabaseOperationEvent.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractPreDatabaseOperationEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractPreDatabaseOperationEvent.java index bd58a1aa317e..31603da59d9d 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractPreDatabaseOperationEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/AbstractPreDatabaseOperationEvent.java @@ -47,11 +47,8 @@ public AbstractPreDatabaseOperationEvent( * Retrieves the entity involved in the database operation. * * @return The entity. - * - * @deprecated Support for JACC will be removed in 6.0 */ @Override - @Deprecated public Object getEntity() { return entity; } From 6a7afe8efce936a2aea45af8217ea615cf24ee43 Mon Sep 17 00:00:00 2001 From: blafond Date: Wed, 2 Feb 2022 13:28:54 -0600 Subject: [PATCH 049/201] =?UTF-8?q?HHH-15067:=20Set=20add()=20method=20as?= =?UTF-8?q?=20public=20to=20allow=20non-nullable=20associations=20in=20hib?= =?UTF-8?q?e=E2=80=A6=20=E2=80=A6rnate-reactive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../engine/internal/NonNullableTransientDependencies.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/NonNullableTransientDependencies.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/NonNullableTransientDependencies.java index 4d064c73a194..36df2d7afae9 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/NonNullableTransientDependencies.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/NonNullableTransientDependencies.java @@ -24,7 +24,7 @@ public final class NonNullableTransientDependencies { // for the map value. private Map> propertyPathsByTransientEntity; // lazily initialized - void add(String propertyName, Object transientEntity) { + public void add(String propertyName, Object transientEntity) { if ( propertyPathsByTransientEntity == null ) { propertyPathsByTransientEntity = new IdentityHashMap<>(); } From 8c8a097e125e65f5f7ce5972ba2bf9b2ccbff7bf Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 16 Mar 2022 13:26:09 +0000 Subject: [PATCH 050/201] HHH-15124 Relax usage of DeprecationLogger: avoid some confusing reports --- .../org/hibernate/internal/FastSessionServices.java | 7 +------ .../main/java/org/hibernate/internal/SessionImpl.java | 7 +------ .../container/internal/CdiBeanContainerBuilder.java | 7 +------ .../schema/spi/SchemaManagementToolCoordinator.java | 10 ++++------ 4 files changed, 7 insertions(+), 24 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index bef97b2fecf6..e7fd5b0f943f 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -246,12 +246,7 @@ private static FlushMode initializeDefaultFlushMode(Map defaultS () -> defaultSessionProperties.get( AvailableSettings.FLUSH_MODE ), () -> { final Object oldSetting = defaultSessionProperties.get( org.hibernate.jpa.AvailableSettings.FLUSH_MODE ); - if ( oldSetting != null ) { - DeprecationLogger.DEPRECATION_LOGGER.deprecatedSetting( - org.hibernate.jpa.AvailableSettings.FLUSH_MODE, - AvailableSettings.FLUSH_MODE - ); - } + //Not invoking the DeprecationLogger in this case as the user can't avoid using this property (the string value is the same) return oldSetting; } ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 6c4b3f92693d..7ca07f615f53 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -274,12 +274,7 @@ public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { () -> getSessionProperty( AvailableSettings.FLUSH_MODE ), () -> { final Object oldSetting = getSessionProperty( org.hibernate.jpa.AvailableSettings.FLUSH_MODE ); - if ( oldSetting != null ) { - DeprecationLogger.DEPRECATION_LOGGER.deprecatedSetting( - org.hibernate.jpa.AvailableSettings.FLUSH_MODE, - AvailableSettings.FLUSH_MODE - ); - } + //Not invoking the DeprecationLogger in this case as the user can't avoid using this property (the string value is the same) return oldSetting; } ); diff --git a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerBuilder.java b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerBuilder.java index bf79c91cd334..11d05aa4baa1 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/beans/container/internal/CdiBeanContainerBuilder.java @@ -62,12 +62,7 @@ public static BeanContainer fromBeanManagerReference( () -> cfgService.getSetting( AvailableSettings.DELAY_CDI_ACCESS, StandardConverters.BOOLEAN ), () -> { final Boolean oldSetting = cfgService.getSetting( org.hibernate.jpa.AvailableSettings.DELAY_CDI_ACCESS, StandardConverters.BOOLEAN ); - if ( oldSetting != null ) { - DeprecationLogger.DEPRECATION_LOGGER.deprecatedSetting( - org.hibernate.jpa.AvailableSettings.DELAY_CDI_ACCESS, - AvailableSettings.DELAY_CDI_ACCESS - ); - } + //Not invoking the DeprecationLogger in this case as the user can't avoid using this property (the string value is the same) return oldSetting; }, () -> false diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java index a35a47f835af..bd9f662dc8e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/spi/SchemaManagementToolCoordinator.java @@ -564,9 +564,8 @@ private static Action determineJpaDbActionSetting(Map configurationValues) () -> configurationValues.get( JAKARTA_HBM2DDL_DATABASE_ACTION ), () -> { final Object setting = configurationValues.get( HBM2DDL_DATABASE_ACTION ); - if ( setting != null ) { - DEPRECATION_LOGGER.deprecatedSetting( HBM2DDL_DATABASE_ACTION, JAKARTA_HBM2DDL_DATABASE_ACTION ); - } + //Not using the DEPRECATION_LOGGER as while this branch understands Jakarta configuration, + //it's not meant to be the primary one yet. return setting; } ); @@ -579,9 +578,8 @@ private static Action determineJpaScriptActionSetting(Map configurationValu () -> configurationValues.get( JAKARTA_HBM2DDL_SCRIPTS_ACTION ), () -> { final Object setting = configurationValues.get( HBM2DDL_SCRIPTS_ACTION ); - if ( setting != null ) { - DEPRECATION_LOGGER.deprecatedSetting( HBM2DDL_SCRIPTS_ACTION, JAKARTA_HBM2DDL_SCRIPTS_ACTION ); - } + //Not using the DEPRECATION_LOGGER as while this branch understands Jakarta configuration, + //it's not meant to be the primary one yet. return setting; } ); From 8c2b9609f1d995c272c5e47ee46675eb756ff479 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Wed, 16 Mar 2022 17:21:35 +0000 Subject: [PATCH 051/201] 5.6.7.Final --- changelog.txt | 10 ++++++++++ gradle/version.properties | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 6a3279401611..33f336428ffb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,16 @@ Hibernate 5 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 5.6.7.Final (March 16, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32053 + +** Improvement + * [HHH-15124] - Relax usage of DeprecationLogger: avoid some confusing reports + * [HHH-15067] - Make NonNullableTransientDependencies.(String propertyName, Object transientEntity) method public + + Changes in 5.6.6.Final (March 15, 2022) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/version.properties b/gradle/version.properties index fd8ec464b2a7..b7701efbb60e 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=5.6.7-SNAPSHOT \ No newline at end of file +hibernateVersion=5.6.7.Final \ No newline at end of file From 3b9fbdcaab8250e7a3a964f966f3e8c2994392ea Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Wed, 16 Mar 2022 17:25:20 +0000 Subject: [PATCH 052/201] 5.6.8-SNAPSHOT --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index b7701efbb60e..437e1d8d928b 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=5.6.7.Final \ No newline at end of file +hibernateVersion=5.6.8-SNAPSHOT \ No newline at end of file From 6d21f7eed29850fea89919c8bf5b18120f1f0dfd Mon Sep 17 00:00:00 2001 From: CHAPEL Guillaume Date: Sat, 12 Mar 2022 11:42:10 +0100 Subject: [PATCH 053/201] HHH-15118 Fix duplicate ids with PooledOptimizer when sequence value is initialValue --- .../envers/EntityTypeChangeAuditTest.java | 1 - .../id/enhanced/PooledOptimizer.java | 13 ++-- .../id/enhanced/OptimizerUnitTest.java | 7 +- .../PooledForcedTableSequenceTest.java | 77 +++++++++++-------- .../enhanced/sequence/PooledSequenceTest.java | 70 +++++++++++------ .../idgen/enhanced/table/PooledTableTest.java | 70 +++++++++++------ 6 files changed, 148 insertions(+), 90 deletions(-) diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java index b6fc6d3bdc5e..65d7480b6fb7 100644 --- a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java @@ -9,7 +9,6 @@ import java.util.Arrays; import java.util.Date; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import javax.persistence.Column; diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledOptimizer.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledOptimizer.java index 59e90f1cc30d..1531b5c5e914 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledOptimizer.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledOptimizer.java @@ -70,22 +70,21 @@ public synchronized Serializable generate(AccessCallback callback) { final GenerationState generationState = locateGenerationState( callback.getTenantIdentifier() ); if ( generationState.hiValue == null ) { - generationState.value = callback.getNextValue(); + generationState.hiValue = callback.getNextValue(); // unfortunately not really safe to normalize this // to 1 as an initial value like we do for the others // because we would not be able to control this if // we are using a sequence... - if ( generationState.value.lt( 1 ) ) { - log.pooledOptimizerReportedInitialValue( generationState.value ); + if ( generationState.hiValue.lt( 1 ) ) { + log.pooledOptimizerReportedInitialValue( generationState.hiValue ); } // the call to obtain next-value just gave us the initialValue if ( ( initialValue == -1 - && generationState.value.lt( incrementSize ) ) - || generationState.value.eq( initialValue ) ) { - generationState.hiValue = callback.getNextValue(); + && generationState.hiValue.lt( incrementSize ) ) + || generationState.hiValue.eq( initialValue ) ) { + generationState.value = generationState.hiValue.copy(); } else { - generationState.hiValue = generationState.value; generationState.value = generationState.hiValue.copy().subtract( incrementSize - 1 ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/id/enhanced/OptimizerUnitTest.java b/hibernate-core/src/test/java/org/hibernate/id/enhanced/OptimizerUnitTest.java index 05579cbd995e..6e9dada1dae3 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/enhanced/OptimizerUnitTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/enhanced/OptimizerUnitTest.java @@ -226,10 +226,15 @@ public void testRecoveredPooledOptimizerUsage() { Long next = ( Long ) optimizer.generate( sequence ); assertEquals( 1, next.intValue() ); + assertEquals( 1, sequence.getTimesCalled() ); + assertEquals( 1, sequence.getCurrentValue() ); + + next = ( Long ) optimizer.generate( sequence ); + assertEquals( 2, next.intValue() ); assertEquals( 2, sequence.getTimesCalled() ); assertEquals( 4, sequence.getCurrentValue() ); - // app ends, and starts back up (we should "lose" only 2 and 3 as id values) + // app ends, and starts back up (we should "lose" only 3 and 4 as id values) final Optimizer optimizer2 = buildPooledOptimizer( 1, 3 ); next = ( Long ) optimizer2.generate( sequence ); assertEquals( 5, next.intValue() ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/forcedtable/PooledForcedTableSequenceTest.java b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/forcedtable/PooledForcedTableSequenceTest.java index fd9d68fd47d0..2e7df00aa0dc 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/forcedtable/PooledForcedTableSequenceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/forcedtable/PooledForcedTableSequenceTest.java @@ -7,6 +7,7 @@ package org.hibernate.test.idgen.enhanced.forcedtable; import org.junit.Test; +import org.junit.jupiter.api.Assertions; import org.hibernate.Session; import org.hibernate.id.IdentifierGeneratorHelper.BasicHolder; @@ -15,6 +16,7 @@ import org.hibernate.id.enhanced.TableStructure; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -23,6 +25,9 @@ * @author Steve Ebersole */ public class PooledForcedTableSequenceTest extends BaseCoreFunctionalTestCase { + + private static final long INITIAL_VALUE = 1; + public String[] getMappings() { return new String[] { "idgen/enhanced/forcedtable/Pooled.hbm.xml" }; } @@ -46,37 +51,47 @@ public void testNormalBoundary() { PooledOptimizer optimizer = (PooledOptimizer) generator.getOptimizer(); int increment = optimizer.getIncrementSize(); - Entity[] entities = new Entity[ increment + 2 ]; - Session s = openSession(); - s.beginTransaction(); - for ( int i = 0; i <= increment; i++ ) { - entities[i] = new Entity( "" + ( i + 1 ) ); - s.save( entities[i] ); - long expectedId = i + 1; - assertEquals( expectedId, entities[i].getId().longValue() ); - // NOTE : initialization calls table twice - assertEquals( 2, generator.getDatabaseStructure().getTimesAccessed() ); - assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); - assertEquals( i + 1, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); - assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); - } - // now force a "clock over" - entities[increment + 1] = new Entity( "" + increment ); - s.save( entities[increment + 1] ); - long expectedId = optimizer.getIncrementSize() + 2; - assertEquals( expectedId, entities[ increment + 1 ].getId().longValue() ); - // initialization (2) + clock over - assertEquals( 3, generator.getDatabaseStructure().getTimesAccessed() ); - assertEquals( ( increment * 2 ) + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); - assertEquals( increment + 2, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); - s.getTransaction().commit(); - s.beginTransaction(); - for ( int i = 0; i < entities.length; i++ ) { - assertEquals( i + 1, entities[i].getId().intValue() ); - s.delete( entities[i] ); - } - s.getTransaction().commit(); - s.close(); + TransactionUtil.doInHibernate( + this::sessionFactory, + s -> { + // The value that we get from the callback is the high value (PooledOptimizer by default) + // When first increment is initialValue, we can only generate one id from it -> id 1 + Entity entity = new Entity( "" + INITIAL_VALUE ); + s.save( entity ); + + long expectedId = INITIAL_VALUE; + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 1, generator.getDatabaseStructure().getTimesAccessed() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + + // now start a full range of values, callback give us hiValue 11 + // id : 2,3,4...,11 + for ( int i = 1; i <= increment; i++ ) { + entity = new Entity( "" + ( i + INITIAL_VALUE ) ); + s.save( entity ); + + expectedId = i + INITIAL_VALUE; + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 2, generator.getDatabaseStructure().getTimesAccessed() ); + assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + } + + // now force a "clock over" + expectedId++; + entity = new Entity( "" + expectedId ); + s.save( entity ); + + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 3, generator.getDatabaseStructure().getTimesAccessed() ); + assertEquals( increment * 2L + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + + s.createQuery( "delete Entity" ).executeUpdate(); + } + ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/PooledSequenceTest.java b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/PooledSequenceTest.java index 79f644cc58dd..b84463d19186 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/PooledSequenceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/PooledSequenceTest.java @@ -13,6 +13,7 @@ import org.hibernate.id.enhanced.SequenceStyleGenerator; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; import static org.hibernate.id.IdentifierGeneratorHelper.BasicHolder; import static org.hibernate.testing.junit4.ExtraAssertions.assertClassAssignability; @@ -22,6 +23,9 @@ * @author Steve Ebersole */ public class PooledSequenceTest extends BaseCoreFunctionalTestCase { + + private static final long INITIAL_VALUE = 1; + @Override public String[] getMappings() { return new String[] { "idgen/enhanced/sequence/Pooled.hbm.xml" }; @@ -36,31 +40,47 @@ public void testNormalBoundary() { PooledOptimizer optimizer = (PooledOptimizer) generator.getOptimizer(); int increment = optimizer.getIncrementSize(); - Entity[] entities = new Entity[ increment + 2 ]; - Session s = openSession(); - s.beginTransaction(); - for ( int i = 0; i <= increment; i++ ) { - entities[i] = new Entity( "" + ( i + 1 ) ); - s.save( entities[i] ); - assertEquals( 2, generator.getDatabaseStructure().getTimesAccessed() ); // initialization calls seq twice - assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); // initialization calls seq twice - assertEquals( i + 1, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); - assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); - } - // now force a "clock over" - entities[ increment + 1 ] = new Entity( "" + increment ); - s.save( entities[ increment + 1 ] ); - assertEquals( 3, generator.getDatabaseStructure().getTimesAccessed() ); // initialization (2) + clock over - assertEquals( ( increment * 2 ) + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); // initialization (2) + clock over - assertEquals( increment + 2, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); - s.getTransaction().commit(); - s.beginTransaction(); - for ( int i = 0; i < entities.length; i++ ) { - assertEquals( i + 1, entities[i].getId().intValue() ); - s.delete( entities[i] ); - } - s.getTransaction().commit(); - s.close(); + TransactionUtil.doInHibernate( + this::sessionFactory, + s -> { + // The value that we get from the callback is the high value (PooledOptimizer by default) + // When first increment is initialValue, we can only generate one id from it -> id 1 + Entity entity = new Entity( "" + INITIAL_VALUE ); + s.save( entity ); + + long expectedId = INITIAL_VALUE; + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 1, generator.getDatabaseStructure().getTimesAccessed() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + + // now start a full range of values, callback give us hiValue 11 + // id : 2,3,4...,11 + for ( int i = 1; i <= increment; i++ ) { + entity = new Entity( "" + ( i + INITIAL_VALUE ) ); + s.save( entity ); + + expectedId = i + INITIAL_VALUE; + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 2, generator.getDatabaseStructure().getTimesAccessed() ); + assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + } + + // now force a "clock over" + expectedId++; + entity = new Entity( "" + expectedId ); + s.save( entity ); + + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 3, generator.getDatabaseStructure().getTimesAccessed() ); + assertEquals( increment * 2L + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + + s.createQuery( "delete Entity" ).executeUpdate(); + } + ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/table/PooledTableTest.java b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/table/PooledTableTest.java index 8575845ddf64..c08051ffeab2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/table/PooledTableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/table/PooledTableTest.java @@ -13,6 +13,7 @@ import org.hibernate.id.enhanced.TableGenerator; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; import static org.hibernate.id.IdentifierGeneratorHelper.BasicHolder; import static org.hibernate.testing.junit4.ExtraAssertions.assertClassAssignability; @@ -22,6 +23,9 @@ * @author Steve Ebersole */ public class PooledTableTest extends BaseCoreFunctionalTestCase { + + private static final long INITIAL_VALUE = 1; + @Override public String[] getMappings() { return new String[] { "idgen/enhanced/table/Pooled.hbm.xml" }; @@ -36,31 +40,47 @@ public void testNormalBoundary() { PooledOptimizer optimizer = (PooledOptimizer) generator.getOptimizer(); int increment = optimizer.getIncrementSize(); - Entity[] entities = new Entity[ increment + 2 ]; - Session s = openSession(); - s.beginTransaction(); - for ( int i = 0; i <= increment; i++ ) { - entities[i] = new Entity( "" + ( i + 1 ) ); - s.save( entities[i] ); - assertEquals( 2, generator.getTableAccessCount() ); // initialization calls seq twice - assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); // initialization calls seq twice - assertEquals( i + 1, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); - assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); - } - // now force a "clock over" - entities[ increment + 1 ] = new Entity( "" + increment ); - s.save( entities[ increment + 1 ] ); - assertEquals( 3, generator.getTableAccessCount() ); // initialization (2) + clock over - assertEquals( ( increment * 2 ) + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); // initialization (2) + clock over - assertEquals( increment + 2, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); - s.getTransaction().commit(); - s.beginTransaction(); - for ( int i = 0; i < entities.length; i++ ) { - assertEquals( i + 1, entities[i].getId().intValue() ); - s.delete( entities[i] ); - } - s.getTransaction().commit(); - s.close(); + TransactionUtil.doInHibernate( + this::sessionFactory, + s -> { + // The value that we get from the callback is the high value (PooledOptimizer by default) + // When first increment is initialValue, we can only generate one id from it -> id 1 + Entity entity = new Entity( "" + INITIAL_VALUE ); + s.save( entity ); + + long expectedId = INITIAL_VALUE; + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 1, generator.getTableAccessCount() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + + // now start a full range of values, callback give us hiValue 11 + // id : 2,3,4...,11 + for ( int i = 1; i <= increment; i++ ) { + entity = new Entity( "" + ( i + INITIAL_VALUE ) ); + s.save( entity ); + + expectedId = i + INITIAL_VALUE; + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 2, generator.getTableAccessCount() ); + assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + } + + // now force a "clock over" + expectedId++; + entity = new Entity( "" + expectedId ); + s.save( entity ); + + assertEquals( expectedId, entity.getId().longValue() ); + assertEquals( 3, generator.getTableAccessCount() ); + assertEquals( increment * 2L + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); + assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() ); + + s.createQuery( "delete Entity" ).executeUpdate(); + } + ); } } From e4e35732da403032414a8b9436516a14330cdbe5 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 18 Mar 2022 12:04:15 +0100 Subject: [PATCH 054/201] HHH-13694 fix numeric overflow exception for large sequence min values --- ...nformationExtractorOracleDatabaseImpl.java | 30 ++++- .../OracleDialectSequenceInformationTest.java | 103 ++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleDialectSequenceInformationTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorOracleDatabaseImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorOracleDatabaseImpl.java index b4706177b904..0279fcd96b93 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorOracleDatabaseImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorOracleDatabaseImpl.java @@ -6,6 +6,7 @@ */ package org.hibernate.tool.schema.extract.internal; +import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.SQLException; @@ -18,6 +19,9 @@ public class SequenceInformationExtractorOracleDatabaseImpl extends SequenceInfo */ public static final SequenceInformationExtractorOracleDatabaseImpl INSTANCE = new SequenceInformationExtractorOracleDatabaseImpl(); + private static final BigDecimal MIN_VALUE = BigDecimal.valueOf( Long.MIN_VALUE ); + private static final BigDecimal MAX_VALUE = BigDecimal.valueOf( Long.MAX_VALUE ); + @Override protected String sequenceCatalogColumn() { return null; @@ -38,9 +42,33 @@ protected String sequenceMinValueColumn() { return "min_value"; } + @Override + protected String sequenceMaxValueColumn() { + return "max_value"; + } + + @Override + protected Long resultSetMinValue(ResultSet resultSet) throws SQLException { + final BigDecimal asDecimal = resultSet.getBigDecimal( sequenceMinValueColumn() ); + + // BigDecimal.longValue() may return a result with the opposite sign + if ( asDecimal.compareTo( MIN_VALUE ) == -1 ) { + return Long.MIN_VALUE; + } + + return asDecimal.longValue(); + } + @Override protected Long resultSetMaxValue(ResultSet resultSet) throws SQLException { - return resultSet.getBigDecimal( "max_value" ).longValue(); + final BigDecimal asDecimal = resultSet.getBigDecimal( sequenceMaxValueColumn() ); + + // BigDecimal.longValue() may return a result with the opposite sign + if ( asDecimal.compareTo( MAX_VALUE ) == 1 ) { + return Long.MAX_VALUE; + } + + return asDecimal.longValue(); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleDialectSequenceInformationTest.java b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleDialectSequenceInformationTest.java new file mode 100644 index 000000000000..11bdfe869798 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleDialectSequenceInformationTest.java @@ -0,0 +1,103 @@ +/* + * 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 . + */ +package org.hibernate.test.dialect.functional; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Optional; +import java.util.stream.StreamSupport; + +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorOracleDatabaseImpl; +import org.hibernate.tool.schema.extract.spi.ExtractionContext; +import org.hibernate.tool.schema.extract.spi.SequenceInformation; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInAutoCommit; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RequiresDialect(value = { Oracle8iDialect.class }) +@TestForIssue(jiraKey = "HHH-13694") +public class OracleDialectSequenceInformationTest extends BaseNonConfigCoreFunctionalTestCase { + + private static final String MIN_SEQUENCE_NAME = "SEQ_MIN_TEST"; + private static final String MAX_SEQUENCE_NAME = "SEQ_MAX_TEST"; + private static final String MIN_VALUE = "-99999999999999999999999999"; + private static final String MAX_VALUE = "99999999999999999999999999"; + + @Before + public void prepareTest() throws Exception { + doInAutoCommit( + "DROP SEQUENCE " + MIN_SEQUENCE_NAME, + "CREATE SEQUENCE " + MIN_SEQUENCE_NAME + " MINVALUE " + MIN_VALUE + " MAXVALUE -1 INCREMENT BY -1", + "DROP SEQUENCE " + MAX_SEQUENCE_NAME, + "CREATE SEQUENCE " + MAX_SEQUENCE_NAME + " MINVALUE 0 MAXVALUE " + MAX_VALUE + " INCREMENT BY 1" ); + } + + @After + public void cleanupTest() throws Exception { + doInAutoCommit( + "DROP SEQUENCE " + MIN_SEQUENCE_NAME, + "DROP SEQUENCE " + MAX_SEQUENCE_NAME ); + } + + @Test + public void testExtractSequenceWithMinValueLowerThanLongMinValue() throws SQLException { + SequenceInformation sequence = fetchSequenceInformation( MIN_SEQUENCE_NAME ); + + assertEquals( -1L, sequence.getIncrementValue().longValue() ); + assertEquals( Long.MIN_VALUE, sequence.getMinValue().longValue() ); + } + + @Test + public void testExtractSequenceWithMaxValueGreaterThanLongMaxValue() throws SQLException { + SequenceInformation sequence = fetchSequenceInformation( MAX_SEQUENCE_NAME ); + + assertEquals( 1L, sequence.getIncrementValue().longValue() ); + assertEquals( Long.MAX_VALUE, sequence.getMaxValue().longValue() ); + } + + private SequenceInformation fetchSequenceInformation(String sequenceName) throws SQLException { + try ( Connection connection = sessionFactory().getJdbcServices() + .getBootstrapJdbcConnectionAccess() + .obtainConnection() ) { + JdbcEnvironment jdbcEnvironment = sessionFactory().getJdbcServices().getJdbcEnvironment(); + SequenceInformationExtractorOracleDatabaseImpl sequenceExtractor = SequenceInformationExtractorOracleDatabaseImpl.INSTANCE; + Iterable sequenceInformations = sequenceExtractor.extractMetadata( + new ExtractionContext.EmptyExtractionContext() { + + @Override + public Connection getJdbcConnection() { + return connection; + } + + @Override + public JdbcEnvironment getJdbcEnvironment() { + return jdbcEnvironment; + } + } ); + + // lets skip system sequences + Optional foundSequence = StreamSupport.stream( sequenceInformations.spliterator(), false ) + .filter( sequence -> sequenceName.equals( sequence.getSequenceName().getSequenceName().getText().toUpperCase() ) ) + .findFirst(); + + assertTrue( sequenceName + " not found", foundSequence.isPresent() ); + + return foundSequence.get(); + } + } +} \ No newline at end of file From adcc54febdb331084c6794d60e25ea7b178dac17 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 18 Mar 2022 11:52:04 +0100 Subject: [PATCH 055/201] HHH-14487 Fix usage of wrong Map in PropertyAccessStrategyMapImpl --- .../PropertyAccessStrategyMapImpl.java | 5 ++-- .../PropertyAccessStrategyMapTest.java | 4 ++-- .../sql/hand/query/NativeSQLQueriesTest.java | 23 +++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessStrategyMapImpl.java b/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessStrategyMapImpl.java index 539e26a0fc55..92b6fcc19ba2 100644 --- a/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessStrategyMapImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessStrategyMapImpl.java @@ -6,7 +6,8 @@ */ package org.hibernate.property.access.internal; -import org.hibernate.mapping.Map; +import java.util.Map; + import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.property.access.spi.PropertyAccessStrategy; @@ -24,7 +25,7 @@ public class PropertyAccessStrategyMapImpl implements PropertyAccessStrategy { public PropertyAccess buildPropertyAccess(Class containerJavaType, String propertyName) { // Sometimes containerJavaType is null, but if it isn't, make sure it's a Map. - if (containerJavaType != null && !Map.class.isAssignableFrom(containerJavaType)) { + if (containerJavaType != null && !Map.class.isAssignableFrom( containerJavaType)) { throw new IllegalArgumentException( String.format( "Expecting class: [%1$s], but containerJavaType is of type: [%2$s] for propertyName: [%3$s]", diff --git a/hibernate-core/src/test/java/org/hibernate/property/PropertyAccessStrategyMapTest.java b/hibernate-core/src/test/java/org/hibernate/property/PropertyAccessStrategyMapTest.java index 1571ba547163..534d79caa348 100644 --- a/hibernate-core/src/test/java/org/hibernate/property/PropertyAccessStrategyMapTest.java +++ b/hibernate-core/src/test/java/org/hibernate/property/PropertyAccessStrategyMapTest.java @@ -8,8 +8,8 @@ import java.util.Date; import java.util.HashMap; +import java.util.Map; -import org.hibernate.mapping.Map; import org.hibernate.property.access.internal.PropertyAccessStrategyMapImpl; import org.hibernate.property.access.spi.PropertyAccess; @@ -42,7 +42,7 @@ public void testNonMap() { } catch (IllegalArgumentException e) { assertEquals( - "Expecting class: [org.hibernate.mapping.Map], but containerJavaType is of type: [java.util.Date] for propertyName: [time]", + "Expecting class: [java.util.Map], but containerJavaType is of type: [java.util.Date] for propertyName: [time]", e.getMessage() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java index abbc3a7fcf80..0c0f6b041765 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java @@ -42,6 +42,7 @@ import org.hibernate.testing.FailureExpected; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.test.sql.hand.Dimension; import org.hibernate.test.sql.hand.Employment; @@ -885,6 +886,28 @@ public void testEscapeColonInSQL() throws QueryException { s.close(); } + @Test + @TestForIssue( jiraKey = "HHH-14487") + public void testAliasToBeanMap() { + Person gavin = new Person( "Gavin" ); + + Session s = openSession(); + Transaction t = s.beginTransaction(); + s.persist( gavin ); + t.commit(); + s.close(); + + s = openSession(); + t = s.beginTransaction(); + HashMap result = (HashMap) session.createNativeQuery( "select * from PERSON" ) + .setResultTransformer( Transformers.aliasToBean( HashMap.class ) ) + .uniqueResult(); + assertEquals( "Gavin", result.get( "NAME" ) == null ? result.get( "name" ) : result.get( "NAME" ) ); + session.delete( gavin ); + t.commit(); + s.close(); + } + private String buildLongString(int size, char baseChar) { StringBuilder buff = new StringBuilder(); for( int i = 0; i < size; i++ ) { From 3b9c9ae3b30bcc445e83d1cc96219a591749aacc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 28 Mar 2022 14:35:11 +0200 Subject: [PATCH 056/201] HHH-15147 Fix jpamodelgen-jakarta annotation processor processing javax.* annotations instead of jakarta.* annotations --- .../hibernate-jpamodelgen-jakarta.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle b/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle index ac9a622e8189..83c5be6abb56 100644 --- a/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle +++ b/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle @@ -168,7 +168,7 @@ abstract class JakartaJarTransformation extends DefaultTask { "-q", "-tr", getProject().getRootProject().file( "rules/jakarta-renames.properties" ).getAbsolutePath(), "-tv", getProject().getRootProject().file( "rules/jakarta-versions.properties" ).getAbsolutePath(), - "-td", getProject().getRootProject().file( "rules/jakarta-direct.properties" ).getAbsolutePath() + "-td", getProject().getRootProject().file( "rules/jakarta-direct-modelgen.properties" ).getAbsolutePath() ); } }); From 3fba2f2322da650ac14bb26a1eceea1a1b711b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 28 Mar 2022 11:39:26 +0200 Subject: [PATCH 057/201] HHH-15146 Run tests for hibernate-jpamodelgen-jakarta --- .../hibernate-jpamodelgen-jakarta.gradle | 129 +++++++++++++++++- 1 file changed, 127 insertions(+), 2 deletions(-) diff --git a/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle b/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle index 83c5be6abb56..6314ef17d075 100644 --- a/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle +++ b/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle @@ -18,12 +18,19 @@ configurations { // we do not want the much of the normal java plugin's behavior compileJava.enabled false processResources.enabled false -compileTestJava.enabled false -processTestResources.enabled false jar.enabled false javadocJar.enabled false sourcesJar.enabled false +ext { + transformedJarName = project(':hibernate-jpamodelgen').tasks.jar.archiveFileName.get(). + replaceAll( 'hibernate-jpamodelgen', 'hibernate-jpamodelgen-jakarta' ) + + originalTestSrcDir = "${project(':hibernate-jpamodelgen').projectDir}/src/test" + transformedTestSrcDirRelative = 'generated-src/test' + transformedTestSrcDir = "${buildDir}/${transformedTestSrcDirRelative}" +} + dependencies { // JAXB compile( libraries.jakarta_jaxb_api ) @@ -35,7 +42,18 @@ dependencies { 'org.slf4j:slf4j-api:1.7.26', 'org.eclipse.transformer:org.eclipse.transformer:0.2.0', 'org.eclipse.transformer:org.eclipse.transformer.cli:0.2.0' + + testCompile project(':hibernate-testing-jakarta') testCompile fileTree(dir: 'libs', include: '*.jar') + testCompile libraries.junit + testCompile libraries.jakarta_jpa + testCompile libraries.jakarta_validation +} + +// +sourceSets.test { + java.srcDir "${project.transformedTestSrcDir}/java" + resources.srcDir "${project.transformedTestSrcDir}/resources" } jar { @@ -108,6 +126,18 @@ task transformJavadocJar(type: JakartaJarTransformation) { targetJar tasks.javadocJar.archiveFile.get().asFile } +// jpamodelgen tests need access to test sources, so we transform test sources instead of the test JAR. +task transformTestSources(type: JakartaSourcesTransformation) { + description 'Transforms the hibernate-jpamodelgen test sources using the JakartaTransformer tool' + + // Only run this if JavaEE tests compile + dependsOn project(':hibernate-jpamodelgen').tasks.compileTestJava + mustRunAfter project(':hibernate-jpamodelgen').tasks.compileTestJava + + sourceDir project.originalTestSrcDir + targetDir project.transformedTestSrcDir +} + configurations { [apiElements, runtimeElements].each { it.outgoing.artifacts.removeIf { @@ -122,6 +152,48 @@ configurations { it.outgoing.artifact(tasks.transformJavadocJar.targetJar) { builtBy tasks.transformJavadocJar } + it.outgoing.artifact(tasks.transformTestSources.targetDir) { + builtBy tasks.transformTestSources + } + } +} + +compileTestJava { + dependsOn tasks.transformJar + dependsOn tasks.transformTestSources + + mustRunAfter tasks.transformJar + mustRunAfter tasks.transformTestSources + + classpath += files( + "${buildDir}/libs/${project.transformedJarName}" + ) + + options.compilerArgs += [ + "-proc:none" + ] +} + +test { + classpath += files( + "${buildDir}/libs/${project.transformedJarName}" + ) + + systemProperty 'file.encoding', 'utf-8' + systemProperty 'sourceBaseDir', "${project.transformedTestSrcDir}/java" + + if ( gradle.ext.javaVersions.test.launcher.asInt() >= 9 ) { + // Weld needs this to generate proxies + jvmArgs( ['--add-opens', 'java.base/java.security=ALL-UNNAMED'] ) + jvmArgs( ['--add-opens', 'java.base/java.lang=ALL-UNNAMED'] ) + } + + maxHeapSize = '3G' + // Allow to exclude specific tests + if (project.hasProperty('excludeTests')) { + filter { + excludeTestsMatching project.property('excludeTests').toString() + } } } @@ -173,4 +245,57 @@ abstract class JakartaJarTransformation extends DefaultTask { } }); } +} + +@CacheableTask +abstract class JakartaSourcesTransformation extends DefaultTask { + private final DirectoryProperty sourceDir; + private final DirectoryProperty targetDir; + + @Inject + JakartaSourcesTransformation(ObjectFactory objectFactory) { + sourceDir = objectFactory.directoryProperty(); + targetDir = objectFactory.directoryProperty(); + } + + @InputDirectory + @PathSensitive( PathSensitivity.RELATIVE ) + DirectoryProperty getSourceDir() { + return sourceDir; + } + + void sourceDir(Object directoryReference) { + sourceDir.set( project.file( directoryReference ) ) + } + + @OutputDirectory + DirectoryProperty getTargetDir() { + return targetDir; + } + + void targetDir(Object directoryReference) { + targetDir.set( project.file( directoryReference ) ) + } + + @TaskAction + void transform() { + project.javaexec( new Action() { + @Override + void execute(JavaExecSpec javaExecSpec) { + javaExecSpec.classpath( getProject().getConfigurations().getByName( "jakartaeeTransformTool" ) ); + javaExecSpec.setMain( "org.eclipse.transformer.jakarta.JakartaTransformer" ); + javaExecSpec.args( + sourceDir.get().getAsFile().getAbsolutePath(), + targetDir.get().getAsFile().getAbsolutePath(), + // The transformer won't run if the target directory exist, + // except if we allow it to overwrite the target directory through this option. + '-o', + "-q", + "-tr", getProject().getRootProject().file( "rules/jakarta-renames.properties" ).getAbsolutePath(), + "-tv", getProject().getRootProject().file( "rules/jakarta-versions.properties" ).getAbsolutePath(), + "-td", getProject().getRootProject().file( "rules/jakarta-direct-modelgen.properties" ).getAbsolutePath() + ); + } + }); + } } \ No newline at end of file From 0c5486a1f272fe80609295e5a3111026094a8743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 28 Mar 2022 16:09:45 +0200 Subject: [PATCH 058/201] HHH-15146 Remove bogus artifact from hibernate-jpamodelgen-jakarta --- .../hibernate-jpamodelgen-jakarta.gradle | 3 --- 1 file changed, 3 deletions(-) diff --git a/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle b/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle index 6314ef17d075..7a8dda7385d3 100644 --- a/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle +++ b/tooling/metamodel-generator-jakarta/hibernate-jpamodelgen-jakarta.gradle @@ -152,9 +152,6 @@ configurations { it.outgoing.artifact(tasks.transformJavadocJar.targetJar) { builtBy tasks.transformJavadocJar } - it.outgoing.artifact(tasks.transformTestSources.targetDir) { - builtBy tasks.transformTestSources - } } } From 4b45bebf5ea76a665c944e6b84f2259120286951 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 21 Mar 2022 12:22:40 +0100 Subject: [PATCH 059/201] HHH-14819 Add test for issue --- .../test/limit/Oracle12LimitTest.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/limit/Oracle12LimitTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/limit/Oracle12LimitTest.java b/hibernate-core/src/test/java/org/hibernate/test/limit/Oracle12LimitTest.java new file mode 100644 index 000000000000..27f167380a9a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/limit/Oracle12LimitTest.java @@ -0,0 +1,87 @@ +package org.hibernate.test.limit; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import org.hibernate.dialect.Oracle8iDialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +@RequiresDialect(Oracle8iDialect.class) +@TestForIssue(jiraKey = "HHH-14819") +public class Oracle12LimitTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + UserFunctionalArea.class + }; + } + + @Test + public void testLimit() { + inTransaction( + session -> { + final CriteriaBuilder criteriabuilder = session.getCriteriaBuilder(); + final CriteriaQuery criteriaquery = criteriabuilder.createQuery(); + final Root personRoot = criteriaquery.from( Person.class ); + final Join functionalArea = personRoot.join( + "functionalArea", + JoinType.LEFT + ); + + List predicates = new ArrayList<>(); + predicates.add( criteriabuilder.or( criteriabuilder.equal( personRoot.get( "name" ), "A" ) ) ); + + List notNullPredicate = predicates.parallelStream().filter( Objects::nonNull ) + .collect( Collectors.toList() ); + criteriaquery.select( personRoot ).where( notNullPredicate.toArray( new Predicate[] {} ) ).distinct( + true ); + criteriaquery.orderBy( criteriabuilder.desc( criteriabuilder.upper( functionalArea.get( + "userAreaName" ) ) ) ); + + final TypedQuery createQuery = session.createQuery( criteriaquery ); + createQuery.setFirstResult( 0 ).setMaxResults( 10 ).getResultList(); + } + ); + } + + @Entity + public class Person { + @Id + private Long id; + + @OneToMany +// @JoinColumn(name = "USER_KEY", referencedColumnName = "USER_KEY") + private List functionalArea; + + private String name; + } + + @Entity + public class UserFunctionalArea { + @Id + @Column(name = "USER_KEY") + private Integer id; + + private String userAreaName; + } +} From cb738ceb800eed9b2d3a2c6ab0f8f66792ab6951 Mon Sep 17 00:00:00 2001 From: andygithubmf <78627199+andygithubmf@users.noreply.github.com> Date: Thu, 7 Apr 2022 13:54:52 -0400 Subject: [PATCH 060/201] HHH-15196 - Use default locale when lowercasing Locale.ROOT does not correctly lowercase Turkish "I"s. --- .../src/main/java/org/hibernate/criterion/LikeExpression.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/criterion/LikeExpression.java b/hibernate-core/src/main/java/org/hibernate/criterion/LikeExpression.java index e266a70d784d..b846cacc80d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/criterion/LikeExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/criterion/LikeExpression.java @@ -78,7 +78,7 @@ public String toSqlString(Criteria criteria,CriteriaQuery criteriaQuery) { @Override public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) { - final String matchValue = ignoreCase ? value.toString().toLowerCase(Locale.ROOT) : value.toString(); + final String matchValue = ignoreCase ? value.toString().toLowerCase() : value.toString(); return new TypedValue[] { criteriaQuery.getTypedValue( criteria, propertyName, matchValue ) }; } From 39e0cca3e7733cdc49688c0ac20d68cefe3f437b Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Sun, 10 Apr 2022 11:50:23 +0200 Subject: [PATCH 061/201] Gradle release script removed --protocol=28 for rsync --- release/release.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release/release.gradle b/release/release.gradle index 66877458e9e5..536c5aa19c36 100644 --- a/release/release.gradle +++ b/release/release.gradle @@ -111,7 +111,7 @@ task uploadDocumentation(type:Exec, dependsOn: assembleDocumentation) { final String url = "filemgmt.jboss.org:/docs_htdocs/hibernate/orm/${rootProject.ormVersion.family}"; executable 'rsync' - args '-avz', '--links', '--protocol=28', "${documentationDir.absolutePath}/", url + args '-avz', '--links', "${documentationDir.absolutePath}/", url doFirst { if ( rootProject.ormVersion.isSnapshot ) { From 03f1cce6f117e375f4d91e34794a9528a648b0b6 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 11 Apr 2022 10:57:51 +0200 Subject: [PATCH 062/201] Gradle changed uploadDocumentation rsync url and port --- release/release.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release/release.gradle b/release/release.gradle index 536c5aa19c36..baf392788248 100644 --- a/release/release.gradle +++ b/release/release.gradle @@ -108,10 +108,10 @@ task assembleDocumentation(type: Task, dependsOn: [rootProject.project( 'documen task uploadDocumentation(type:Exec, dependsOn: assembleDocumentation) { description = "Uploads documentation to the JBoss doc server" - final String url = "filemgmt.jboss.org:/docs_htdocs/hibernate/orm/${rootProject.ormVersion.family}"; + final String url = "filemgmt-prod-sync.jboss.org:/docs_htdocs/hibernate/orm/${rootProject.ormVersion.family}"; executable 'rsync' - args '-avz', '--links', "${documentationDir.absolutePath}/", url + args '--port=2222', '-avz', '--links', "${documentationDir.absolutePath}/", url doFirst { if ( rootProject.ormVersion.isSnapshot ) { From 06a95721ea3d3f1a56e4052a39799cf00436da67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 13 Apr 2022 09:45:24 +0200 Subject: [PATCH 063/201] HHH-15209 Upgrade to bytebuddy 1.12.9 --- gradle/libraries.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 2392f5c96973..250a4f813a1f 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -26,7 +26,7 @@ ext { weldVersion = '3.1.5.Final' jakartaWeldVersion = '4.0.1.SP1' - byteBuddyVersion = '1.12.8' + byteBuddyVersion = '1.12.9' agroalVersion = '1.9' From 527103d8b9e3cd3a7b9ede87180982da010285bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 24 Mar 2022 15:58:10 +0100 Subject: [PATCH 064/201] HHH-15141 Test bytecode enhancement on cross-package inheritance tree with protected @Embedded field --- ...ageMappedSuperclassWithEmbeddableTest.java | 48 +++++++++++++++++++ .../proxy/crosspackage/base/BaseEntity.java | 33 +++++++++++++ .../crosspackage/base/EmbeddableType.java | 19 ++++++++ .../crosspackage/derived/TestEntity.java | 10 ++++ 4 files changed, 110 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/CrossPackageMappedSuperclassWithEmbeddableTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/base/BaseEntity.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/base/EmbeddableType.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/derived/TestEntity.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/CrossPackageMappedSuperclassWithEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/CrossPackageMappedSuperclassWithEmbeddableTest.java new file mode 100644 index 000000000000..f037f57bf264 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/CrossPackageMappedSuperclassWithEmbeddableTest.java @@ -0,0 +1,48 @@ +/* + * 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 + */ +package org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.crosspackage; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.crosspackage.base.EmbeddableType; +import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.crosspackage.derived.TestEntity; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(BytecodeEnhancerRunner.class) +@EnhancementOptions(lazyLoading = true, inlineDirtyChecking = true) +public class CrossPackageMappedSuperclassWithEmbeddableTest extends BaseNonConfigCoreFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { TestEntity.class }; + } + + @Test + @TestForIssue(jiraKey = "HHH-15141") + public void testIt() { + // Just a smoke test; the original failure happened during bytecode enhancement. + Long id = fromTransaction( s -> { + TestEntity testEntity = new TestEntity(); + EmbeddableType embedded = new EmbeddableType(); + embedded.setField( "someValue" ); + testEntity.setEmbeddedField( embedded ); + s.persist( testEntity ); + return testEntity.getId(); + } ); + inTransaction( s -> { + TestEntity testEntity = s.find( TestEntity.class, id ); + assertThat( testEntity.getEmbeddedField().getField() ).isEqualTo( "someValue" ); + } ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/base/BaseEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/base/BaseEntity.java new file mode 100644 index 000000000000..41fd00dcf80f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/base/BaseEntity.java @@ -0,0 +1,33 @@ +package org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.crosspackage.base; + +import javax.persistence.Embedded; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +@MappedSuperclass +public abstract class BaseEntity { + + @Id + @GeneratedValue + private Long id; + + @Embedded + protected EmbeddableType embeddedField; + + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public EmbeddableType getEmbeddedField() { + return embeddedField; + } + + public void setEmbeddedField(final EmbeddableType embeddedField) { + this.embeddedField = embeddedField; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/base/EmbeddableType.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/base/EmbeddableType.java new file mode 100644 index 000000000000..12e2eff07dcf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/base/EmbeddableType.java @@ -0,0 +1,19 @@ +package org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.crosspackage.base; + +import javax.persistence.Column; +import javax.persistence.Embeddable; + +@Embeddable +public class EmbeddableType { + + @Column + private String field; + + public String getField() { + return field; + } + + public void setField(final String field) { + this.field = field; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/derived/TestEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/derived/TestEntity.java new file mode 100644 index 000000000000..965fbf0cb2b4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/proxy/crosspackage/derived/TestEntity.java @@ -0,0 +1,10 @@ +package org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.crosspackage.derived; + +import javax.persistence.Entity; + +import org.hibernate.orm.test.bytecode.enhancement.lazy.proxy.crosspackage.base.BaseEntity; + +@Entity +public class TestEntity extends BaseEntity { + +} From 96907698e124098193ffa5b8935539d34a379131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 13 Apr 2022 17:08:38 +0200 Subject: [PATCH 065/201] Use annotated tags for release Mainly because it's handled better in git-related tools such as tig. See also https://git-scm.com/book/en/v2/Git-Basics-Tagging#_creating_tags --- release/release.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release/release.gradle b/release/release.gradle index baf392788248..0984ccb5027d 100644 --- a/release/release.gradle +++ b/release/release.gradle @@ -307,7 +307,7 @@ task ciRelease( dependsOn: [releaseChecks, addVersionCommit, release] ) { tag = tag.replace( ".Final", "" ) } logger.lifecycle( "Tagging '${tag}'..." ) - executeGitCommand( 'tag', tag ) + executeGitCommand( 'tag', '-a', tag, '-m', "Release $project.ormVersion.fullName" ) } logger.lifecycle( "Adding commit to update version to '${project.developmentVersion}'..." ) From 0b86f5a98d89a153f06276e81c8029f5c0e79c63 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Wed, 13 Apr 2022 15:22:35 +0000 Subject: [PATCH 066/201] 5.6.8.Final --- changelog.txt | 17 +++++++++++++++++ gradle/version.properties | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 33f336428ffb..09d59316ede5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,23 @@ Hibernate 5 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 5.6.8.Final (April 13, 2022) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32056 + +** Bug + * [HHH-15147] - hibernate-jpamodelgen-jakarta annotation processor ignores jakarta.* annotations + * [HHH-15141] - Bytecode enhancement fails for a protected, embedded field in a MappedSuperclass from a different package than the entity + * [HHH-15118] - PooledOptimizer generates duplicate ids when several JVMs initialize optimizer and sequence value is the initial value + * [HHH-14487] - PropertyAccessStrategyMapImpl imports wrong class + * [HHH-13694] - Numeric Overflow Exception when retrieving the Meta-data for sequences from Oracle Database + +** Task + * [HHH-15209] - Upgrade to bytebuddy 1.12.9 + * [HHH-15146] - Run tests against hibernate-jpamodelgen-jakarta + + Changes in 5.6.7.Final (March 16, 2022) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/version.properties b/gradle/version.properties index 437e1d8d928b..26ed6c884d3b 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=5.6.8-SNAPSHOT \ No newline at end of file +hibernateVersion=5.6.8.Final \ No newline at end of file From 323f7253f579f82f9fbdbcb2dff2be6cf15425a3 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Wed, 13 Apr 2022 15:26:14 +0000 Subject: [PATCH 067/201] 5.6.9-SNAPSHOT --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 26ed6c884d3b..b56682fd9010 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=5.6.8.Final \ No newline at end of file +hibernateVersion=5.6.9-SNAPSHOT \ No newline at end of file From ebfc6b4254eb3066e70948fb941a74c58d8bfa8c Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 6 Apr 2022 12:23:55 +0200 Subject: [PATCH 068/201] HHH-15178 Backport Jenkinsfile and GH actions --- .github/workflows/contributor-build.yml | 74 +--- Jenkinsfile | 380 ++++++++++++++++++ ci/build.sh | 12 +- ci/database-start.sh | 8 +- docker_db.sh | 366 ++++++++++++++--- .../mapping/basic/BitSetUserTypeTest.java | 4 +- gradle/databases.gradle | 36 +- gradle/java-module.gradle | 13 +- gradle/libraries.gradle | 1 + .../EntityGraphAttributeResolutionTest.java | 2 +- .../query/hhh14112/HHH14112Test.java | 2 +- .../EntityProxySerializationTest.java | 2 +- .../DefaultCatalogAndSchemaTest.java | 2 + .../collection/list/PersistentListTest.java | 2 +- .../test/collection/map/Mappings.hbm.xml | 2 +- .../test/collection/set/Mappings.hbm.xml | 2 +- .../collection/set/MappingsNonLazy.hbm.xml | 2 +- .../test/component/basic/ComponentTest.java | 4 +- .../test/querycache/QueryCacheTest.java | 10 +- .../test/reattachment/Mappings.hbm.xml | 2 +- .../sql/autodiscovery/AutoDiscoveryTest.java | 2 +- .../hand/custom/sqlserver/Mappings.hbm.xml | 12 +- .../sql/hand/query/NativeSQLQueries.hbm.xml | 4 +- .../sql/hand/query/NativeSQLQueriesTest.java | 2 +- ...calDateCustomSessionLevelTimeZoneTest.java | 111 ----- .../LazyManyToManyNonUniqueIdWhereTest.java | 4 +- .../LazyOneToManyNonUniqueIdWhereTest.java | 2 +- ...LazyManyToManyNonUniqueIdWhereTest.hbm.xml | 4 +- .../LazyOneToManyNonUniqueIdWhereTest.hbm.xml | 2 +- .../customtype/UnspecifiedEnumTypeTest.java | 2 +- .../multiplerelations/GroupMemberTest.java | 2 +- .../cleaner/SQLServerDatabaseCleaner.java | 8 +- 32 files changed, 814 insertions(+), 267 deletions(-) create mode 100644 Jenkinsfile delete mode 100644 hibernate-core/src/test/java/org/hibernate/test/timestamp/LocalDateCustomSessionLevelTimeZoneTest.java diff --git a/.github/workflows/contributor-build.yml b/.github/workflows/contributor-build.yml index e6ccfdb63ed3..ee6ef4aec6bf 100644 --- a/.github/workflows/contributor-build.yml +++ b/.github/workflows/contributor-build.yml @@ -1,4 +1,4 @@ -# The main CI of Hibernate ORM is https://ci.hibernate.org/job/hibernate-orm-6.0-h2-main/. +# The main CI of Hibernate ORM is https://ci.hibernate.org/job/hibernate-orm-5.6-h2/. # However, Hibernate ORM builds run on GitHub actions regularly # to check that it still works and can be used in GitHub forks. # See https://docs.github.com/en/free-pro-team@latest/actions @@ -13,35 +13,38 @@ on: pull_request: branches: - '5.6' + +# See https://github.com/hibernate/hibernate-orm/pull/4615 for a description of the behavior we're getting. +concurrency: + # Consider that two builds are in the same concurrency group (cannot run concurrently) + # if they use the same workflow and are about the same branch ("ref") or pull request. + group: "workflow = ${{ github.workflow }}, ref = ${{ github.event.ref }}, pr = ${{ github.event.pull_request.id }}" + # Cancel previous builds in the same concurrency group even if they are in process + # for pull requests or pushes to forks (not the upstream repository). + cancel-in-progress: ${{ github.event_name == 'pull_request' || github.repository != 'hibernate/hibernate-orm' }} + jobs: build: name: Java 8 runs-on: ubuntu-latest - # We want to know the test results of all matrix entries - continue-on-error: true strategy: fail-fast: false matrix: - # When GitHub Actions supports it: https://github.com/actions/toolkit/issues/399 - # We will use the experimental flag as indicator whether a failure should cause a workflow failure include: - rdbms: h2 - experimental: false +# - rdbms: hsqldb - rdbms: derby - experimental: true + - rdbms: mysql8 - rdbms: mariadb - experimental: true - - rdbms: postgresql - experimental: true + - rdbms: postgresql_9_5 + - rdbms: postgresql_13 - rdbms: oracle - experimental: true - rdbms: db2 - experimental: true - rdbms: mssql - experimental: true +# Testing against Sybase requires many backports so let's skip it for now +# - rdbms: sybase # Running with HANA requires at least 8GB memory just for the database, which we don't have on GH Actions runners # - rdbms: hana -# experimental: true steps: - uses: actions/checkout@v2 with: @@ -85,45 +88,4 @@ jobs: ./**/target/reports/tests/ ./**/target/reports/checkstyle/ - name: Omit produced artifacts from build cache - run: ./ci/before-cache.sh - build11: - name: Java 11 - runs-on: ubuntu-latest - # We want to know the test results of all matrix entries - continue-on-error: true - steps: - - uses: actions/checkout@v2 - with: - persist-credentials: false - - name: Set up Java 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Get year/month for cache key - id: get-date - run: | - echo "::set-output name=yearmonth::$(/bin/date -u "+%Y-%m")" - shell: bash - - name: Cache Maven local repository - uses: actions/cache@v2 - id: cache-maven - with: - path: | - ~/.m2/repository - ~/.gradle/caches/ - ~/.gradle/wrapper/ - # refresh cache every month to avoid unlimited growth - key: maven-localrepo-${{ steps.get-date.outputs.yearmonth }} - - name: Run build script - run: ./ci/build-github.sh - shell: bash - - name: Upload test reports (if Gradle failed) - uses: actions/upload-artifact@v2 - if: failure() - with: - name: test-reports-java11 - path: | - ./**/target/reports/tests/ - ./**/target/reports/checkstyle/ - - name: Omit produced artifacts from build cache - run: ./ci/before-cache.sh + run: ./ci/before-cache.sh \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 000000000000..ffabe649af9e --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,380 @@ +/* + * 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 . + */ + +import groovy.transform.Field +import io.jenkins.blueocean.rest.impl.pipeline.PipelineNodeGraphVisitor +import io.jenkins.blueocean.rest.impl.pipeline.FlowNodeWrapper +import org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper + +/* + * See https://github.com/hibernate/hibernate-jenkins-pipeline-helpers + */ +@Library('hibernate-jenkins-pipeline-helpers@1.5') _ +import org.hibernate.jenkins.pipeline.helpers.job.JobHelper + +@Field final String NODE_PATTERN_BASE = 'Worker&&Containers' +@Field List environments + +this.helper = new JobHelper(this) + +helper.runWithNotification { +def defaultJdk = '8' +stage('Configure') { + this.environments = [ +// buildEnv(defaultJdk, 'h2'), +// buildEnv(defaultJdk, 'hsqldb'), +// buildEnv(defaultJdk, 'derby'), +// buildEnv(defaultJdk, 'mysql8'), +// buildEnv(defaultJdk, 'mariadb'), +// buildEnv(defaultJdk, 'postgresql_9_5'), +// buildEnv(defaultJdk, 'postgresql_13'), +// buildEnv(defaultJdk, 'oracle'), + buildEnv(defaultJdk, 'oracle_ee'), +// buildEnv(defaultJdk, 'db2'), +// buildEnv(defaultJdk, 'mssql'), +// buildEnv(defaultJdk, 'sybase'), + buildEnv(defaultJdk, 'hana', 'HANA'), +// buildEnv(defaultJdk, 's390x', 's390x'), +// buildEnv(defaultJdk, 'tidb', 'tidb', 'tidb_hibernate@pingcap.com'), + // Disable EDB for now as the image is not available anymore +// buildEnv(defaultJdk, 'edb') + jdkBuildEnv(defaultJdk, '11'), + jdkBuildEnv(defaultJdk, '17'), + jdkBuildEnv(defaultJdk, '18'), + jdkBuildEnv(defaultJdk, '19'), + ]; + + helper.configure { + file 'job-configuration.yaml' + // We don't require the following, but the build helper plugin apparently does + jdk { + defaultTool "OpenJDK ${defaultJdk} Latest" + } + maven { + defaultTool 'Apache Maven 3.8' + } + } + properties([ + buildDiscarder( + logRotator(daysToKeepStr: '30', numToKeepStr: '10') + ), + // If two builds are about the same branch or pull request, + // the older one will be aborted when the newer one starts. + disableConcurrentBuilds(abortPrevious: true), + helper.generateNotificationProperty() + ]) +} + +// Avoid running the pipeline on branch indexing +if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { + print "INFO: Build skipped due to trigger being Branch Indexing" + currentBuild.result = 'ABORTED' + return +} + +stage('Build') { + Map executions = [:] + Map> state = [:] + environments.each { BuildEnvironment buildEnv -> + // Don't build environments for newer JDKs when this is a PR + if ( buildEnv.getVersion() != defaultJdk ) { + if ( helper.scmSource.pullRequest ) { + return + } + } + state[buildEnv.tag] = [:] + executions.put(buildEnv.tag, { + runBuildOnNode(buildEnv.node) { + // Use withEnv instead of setting env directly, as that is global! + // See https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md + withEnv(["JAVA_HOME=${tool buildEnv.buildJdkTool}", "PATH+JAVA=${tool buildEnv.buildJdkTool}/bin", "TEST_JAVA_HOME=${tool buildEnv.testJdkTool}"]) { + if ( buildEnv.getVersion() != defaultJdk ) { + state[buildEnv.tag]['additionalOptions'] = " -Ptest.jdk.version=${buildEnv.getTestVersion()} -Porg.gradle.java.installations.paths=${JAVA_HOME},${TEST_JAVA_HOME}"; + } + else { + state[buildEnv.tag]['additionalOptions'] = ""; + } + state[buildEnv.tag]['containerName'] = null; + stage('Checkout') { + checkout scm + } + try { + stage('Start database') { + switch (buildEnv.dbName) { + case "mysql8": + docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { + docker.image('mysql:8.0.21').pull() + } + sh "./docker_db.sh mysql_8_0" + state[buildEnv.tag]['containerName'] = "mysql" + break; + case "mariadb": + docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { + docker.image('mariadb:10.5.8').pull() + } + sh "./docker_db.sh mariadb" + state[buildEnv.tag]['containerName'] = "mariadb" + break; + case "postgresql_9_5": + // use the postgis image to enable the PGSQL GIS (spatial) extension + docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { + docker.image('postgis/postgis:9.5-2.5').pull() + } + sh "./docker_db.sh postgresql_9_5" + state[buildEnv.tag]['containerName'] = "postgres" + break; + case "postgresql_13": + // use the postgis image to enable the PGSQL GIS (spatial) extension + docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { + docker.image('postgis/postgis:13-3.1').pull() + } + sh "./docker_db.sh postgresql_13" + state[buildEnv.tag]['containerName'] = "postgres" + break; + case "oracle": + docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { + docker.image('quillbuilduser/oracle-18-xe').pull() + } + sh "./docker_db.sh oracle_18" + state[buildEnv.tag]['containerName'] = "oracle" + break; + case "db2": + docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { + docker.image('ibmcom/db2:11.5.7.0').pull() + } + sh "./docker_db.sh db2" + state[buildEnv.tag]['containerName'] = "db2" + break; + case "mssql": + docker.image('mcr.microsoft.com/mssql/server:2017-CU13').pull() + sh "./docker_db.sh mssql" + state[buildEnv.tag]['containerName'] = "mssql" + break; + case "sybase": + docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { + docker.image('nguoianphu/docker-sybase').pull() + } + sh "./docker_db.sh sybase" + state[buildEnv.tag]['containerName'] = "sybase" + break; + case "edb": + docker.withRegistry('https://containers.enterprisedb.com', 'hibernateci.containers.enterprisedb.com') { + // withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'hibernateci.containers.enterprisedb.com', + // usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD']]) { + // sh 'docker login -u "$USERNAME" -p "$PASSWORD" https://containers.enterprisedb.com' + docker.image('containers.enterprisedb.com/edb/edb-as-lite:v11').pull() + } + sh "./docker_db.sh edb" + state[buildEnv.tag]['containerName'] = "edb" + break; + } + } + stage('Test') { + switch (buildEnv.dbName) { + case "h2": + case "derby": + case "hsqldb": + runTest("-Pdb=${buildEnv.dbName}${state[buildEnv.tag]['additionalOptions']}") + break; + case "mysql8": + runTest("-Pdb=mysql_ci${state[buildEnv.tag]['additionalOptions']}") + break; + case "tidb": + runTest("-Pdb=tidb -DdbHost=localhost:4000${state[buildEnv.tag]['additionalOptions']}", 'TIDB') + break; + case "postgresql_9_5": + case "postgresql_13": + runTest("-Pdb=pgsql_ci${state[buildEnv.tag]['additionalOptions']}") + break; + case "oracle": + runTest("-Pdb=oracle_ci -PexcludeTests=**.LockTest.testQueryTimeout*${state[buildEnv.tag]['additionalOptions']}") + break; + case "oracle_ee": + runTest("-Pdb=oracle_jenkins${state[buildEnv.tag]['additionalOptions']}", 'ORACLE_RDS') + break; + case "hana": + runTest("-Pdb=hana_jenkins${state[buildEnv.tag]['additionalOptions']}", 'HANA') + break; + case "edb": + runTest("-Pdb=edb_ci -DdbHost=localhost:5433${state[buildEnv.tag]['additionalOptions']}") + break; + case "s390x": + runTest("-Pdb=h2${state[buildEnv.tag]['additionalOptions']}") + break; + default: + runTest("-Pdb=${buildEnv.dbName}_ci${state[buildEnv.tag]['additionalOptions']}") + break; + } + } + } + finally { + if ( state[buildEnv.tag]['containerName'] != null ) { + sh "docker rm -f ${state[buildEnv.tag]['containerName']}" + } + // Skip this for PRs + if ( !env.CHANGE_ID && buildEnv.notificationRecipients != null ) { + handleNotifications(currentBuild, buildEnv) + } + } + } + } + }) + } + parallel(executions) +} + +} // End of helper.runWithNotification + +// Job-specific helpers + +BuildEnvironment buildEnv(String version, String dbName) { + return new BuildEnvironment( version, version, dbName, NODE_PATTERN_BASE, null ); +} + +BuildEnvironment buildEnv(String version, String dbName, String node) { + return new BuildEnvironment( version, version, dbName, node, null ); +} + +BuildEnvironment buildEnv(String version, String dbName, String node, String notificationRecipients) { + return new BuildEnvironment( version, version, dbName, node, notificationRecipients ); +} + +BuildEnvironment jdkBuildEnv(String version, String testVersion) { + return new BuildEnvironment( version,testVersion, "h2", NODE_PATTERN_BASE, null ); +} + +BuildEnvironment jdkBuildEnv(String version, String testVersion, String notificationRecipients) { + return new BuildEnvironment( version,testVersion, "h2", NODE_PATTERN_BASE, notificationRecipients ); +} + +public class BuildEnvironment { + private String version; + private String testVersion; + private String buildJdkTool; + private String testJdkTool; + private String dbName; + private String node; + private String notificationRecipients; + + public BuildEnvironment(String version, String testVersion, String dbName, String node, String notificationRecipients) { + this.version = version; + this.testVersion = testVersion; + this.dbName = dbName; + this.node = node; + this.notificationRecipients = notificationRecipients; + this.buildJdkTool = "OpenJDK ${version} Latest"; + this.testJdkTool = "OpenJDK ${testVersion} Latest"; + } + String toString() { getTag() } + String getTag() { "jdk_${testVersion}_${dbName}" } + String getNode() { node } + String getVersion() { version } + String getTestVersion() { testVersion } + String getNotificationRecipients() { notificationRecipients } +} + +void runBuildOnNode(String label, Closure body) { + node( label ) { + pruneDockerContainers() + try { + timeout( [time: 200, unit: 'MINUTES'], body ) + } + finally { + // If this is a PR, we clean the workspace at the end + if ( env.CHANGE_BRANCH != null ) { + cleanWs() + } + pruneDockerContainers() + } + } +} +void pruneDockerContainers() { + if ( !sh( script: 'command -v docker || true', returnStdout: true ).trim().isEmpty() ) { + sh 'docker container prune -f || true' + sh 'docker image prune -f || true' + sh 'docker network prune -f || true' + sh 'docker volume prune -f || true' + } +} +// Clean by default otherwise the PackagedEntityManager tests fail on a node that previously ran a different DB +void runTest(String goal, String lockableResource = null, boolean clean = true) { + String cmd = "./gradlew" + (clean ? " clean" : "") + " check ${goal} -Plog-test-progress=true --stacktrace"; + try { + if (lockableResource == null) { + sh cmd + } + else { + lock(lockableResource) { + sh cmd + } + } + } + finally { + junit '**/target/test-results/test/*.xml,**/target/test-results/testKitTest/*.xml' + } +} + +void handleNotifications(currentBuild, buildEnv) { + def currentResult = getParallelResult(currentBuild, buildEnv.tag) + boolean success = currentResult == 'SUCCESS' || currentResult == 'UNKNOWN' + def previousResult = currentBuild.previousBuild == null ? null : getParallelResult(currentBuild.previousBuild, buildEnv.tag) + + // Ignore success after success + if ( !( success && previousResult == 'SUCCESS' ) ) { + def subject + def body + if ( success ) { + if ( previousResult != 'SUCCESS' && previousResult != null ) { + subject = "${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Fixed" + body = """

    ${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Fixed:

    +

    Check console output at ${env.BUILD_URL} to view the results.

    """ + } + else { + subject = "${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Success" + body = """

    ${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Success:

    +

    Check console output at ${env.BUILD_URL} to view the results.

    """ + } + } + else if ( currentResult == 'FAILURE' ) { + if ( previousResult != null && previousResult == "FAILURE" ) { + subject = "${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Still failing" + body = """

    ${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Still failing:

    +

    Check console output at ${env.BUILD_URL} to view the results.

    """ + } + else { + subject = "${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Failure" + body = """

    ${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Failure:

    +

    Check console output at ${env.BUILD_URL} to view the results.

    """ + } + } + else { + subject = "${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - ${currentResult}" + body = """

    ${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - ${currentResult}:

    +

    Check console output at ${env.BUILD_URL} to view the results.

    """ + } + + emailext( + subject: subject, + body: body, + to: buildEnv.notificationRecipients + ) + } +} + +@NonCPS +String getParallelResult( RunWrapper build, String parallelBranchName ) { + def visitor = new PipelineNodeGraphVisitor( build.rawBuild ) + def branch = visitor.pipelineNodes.find{ it.type == FlowNodeWrapper.NodeType.PARALLEL && parallelBranchName == it.displayName } + if ( branch == null ) { + echo "Couldn't find parallel branch name '$parallelBranchName'. Available parallel branch names:" + visitor.pipelineNodes.findAll{ it.type == FlowNodeWrapper.NodeType.PARALLEL }.each{ + echo " - ${it.displayName}" + } + return null; + } + return branch.status.result +} \ No newline at end of file diff --git a/ci/build.sh b/ci/build.sh index 30176554d921..49f32442aed7 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -3,9 +3,17 @@ goal= if [ "$RDBMS" == "derby" ]; then goal="-Pdb=derby" +elif [ "$RDBMS" == "hsqldb" ]; then + goal="-Pdb=hsqldb" +elif [ "$RDBMS" == "mysql8" ]; then + goal="-Pdb=mysql_ci" +elif [ "$RDBMS" == "mysql" ]; then + goal="-Pdb=mysql_ci" elif [ "$RDBMS" == "mariadb" ]; then goal="-Pdb=mariadb_ci" -elif [ "$RDBMS" == "postgresql" ]; then +elif [ "$RDBMS" == "postgresql_9_5" ]; then + goal="-Pdb=pgsql_ci" +elif [ "$RDBMS" == "postgresql_13" ]; then goal="-Pdb=pgsql_ci" elif [ "$RDBMS" == "oracle" ]; then # I have no idea why, but these tests don't work on GH Actions @@ -16,6 +24,8 @@ elif [ "$RDBMS" == "mssql" ]; then goal="-Pdb=mssql_ci" elif [ "$RDBMS" == "hana" ]; then goal="-Pdb=hana_ci" +elif [ "$RDBMS" == "sybase" ]; then + goal="-Pdb=sybase_ci" fi exec ./gradlew check ${goal} -Plog-test-progress=true --stacktrace diff --git a/ci/database-start.sh b/ci/database-start.sh index e603a9631bfe..997a450ee9f0 100755 --- a/ci/database-start.sh +++ b/ci/database-start.sh @@ -8,14 +8,18 @@ elif [ "$RDBMS" == 'mysql8' ]; then bash $DIR/../docker_db.sh mysql_8_0 elif [ "$RDBMS" == 'mariadb' ]; then bash $DIR/../docker_db.sh mariadb -elif [ "$RDBMS" == 'postgresql' ]; then +elif [ "$RDBMS" == 'postgresql_9_5' ]; then bash $DIR/../docker_db.sh postgresql_9_5 +elif [ "$RDBMS" == 'postgresql_13' ]; then + bash $DIR/../docker_db.sh postgresql_13 elif [ "$RDBMS" == 'db2' ]; then bash $DIR/../docker_db.sh db2 elif [ "$RDBMS" == 'oracle' ]; then - bash $DIR/../docker_db.sh oracle + bash $DIR/../docker_db.sh oracle_18 elif [ "$RDBMS" == 'mssql' ]; then bash $DIR/../docker_db.sh mssql elif [ "$RDBMS" == 'hana' ]; then bash $DIR/../docker_db.sh hana +elif [ "$RDBMS" == 'sybase' ]; then + bash $DIR/../docker_db.sh sybase fi \ No newline at end of file diff --git a/docker_db.sh b/docker_db.sh index e88589cada38..6317b5c8755e 100755 --- a/docker_db.sh +++ b/docker_db.sh @@ -1,45 +1,123 @@ #! /bin/bash +if command -v podman > /dev/null; then + CONTAINER_CLI=$(command -v podman) + HEALTCHECK_PATH="{{.State.Healthcheck.Status}}" + # Only use sudo for podman + if command -v sudo > /dev/null; then + PRIVILEGED_CLI="sudo" + else + PRIVILEGED_CLI="" + fi +else + CONTAINER_CLI=$(command -v docker) + HEALTCHECK_PATH="{{.State.Health.Status}}" + PRIVILEGED_CLI="" +fi + mysql_5_7() { - docker rm -f mysql || true - docker run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -p3306:3306 -d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + $CONTAINER_CLI rm -f mysql || true + $CONTAINER_CLI run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d docker.io/mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake --log-bin-trust-function-creators=1 + # Give the container some time to start + OUTPUT= + n=0 + until [ "$n" -ge 5 ] + do + # Need to access STDERR. Thanks for the snippet https://stackoverflow.com/a/56577569/412446 + { OUTPUT="$( { $CONTAINER_CLI logs mysql; } 2>&1 1>&3 3>&- )"; } 3>&1; + if [[ $OUTPUT == *"ready for connections"* ]]; then + break; + fi + n=$((n+1)) + echo "Waiting for MySQL to start..." + sleep 3 + done + if [ "$n" -ge 5 ]; then + echo "MySQL failed to start and configure after 15 seconds" + else + echo "MySQL successfully started" + fi } mysql_8_0() { - docker rm -f mysql || true - docker run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -p3306:3306 -d mysql:8.0.21 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + $CONTAINER_CLI rm -f mysql || true + $CONTAINER_CLI run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d docker.io/mysql:8.0.21 --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_as_cs --skip-character-set-client-handshake --log-bin-trust-function-creators=1 + # Give the container some time to start + OUTPUT= + n=0 + until [ "$n" -ge 5 ] + do + # Need to access STDERR. Thanks for the snippet https://stackoverflow.com/a/56577569/412446 + { OUTPUT="$( { $CONTAINER_CLI logs mysql; } 2>&1 1>&3 3>&- )"; } 3>&1; + if [[ $OUTPUT == *"ready for connections"* ]]; then + break; + fi + n=$((n+1)) + echo "Waiting for MySQL to start..." + sleep 3 + done + if [ "$n" -ge 5 ]; then + echo "MySQL failed to start and configure after 15 seconds" + else + echo "MySQL successfully started" + fi } mariadb() { - docker rm -f mariadb || true - docker run --name mariadb -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d mariadb:10.5.8 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + $CONTAINER_CLI rm -f mariadb || true + $CONTAINER_CLI run --name mariadb -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d docker.io/mariadb:10.5.8 --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake + OUTPUT= + n=0 + until [ "$n" -ge 5 ] + do + # Need to access STDERR. Thanks for the snippet https://stackoverflow.com/a/56577569/412446 + { OUTPUT="$( { $CONTAINER_CLI logs mariadb; } 2>&1 1>&3 3>&- )"; } 3>&1; + if [[ $OUTPUT == *"ready for connections"* ]]; then + break; + fi + n=$((n+1)) + echo "Waiting for MariaDB to start..." + sleep 3 + done + if [ "$n" -ge 5 ]; then + echo "MariaDB failed to start and configure after 15 seconds" + else + echo "MariaDB successfully started" + fi } postgresql_9_5() { - docker rm -f postgres || true - docker run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 -d postgres:9.5 + $CONTAINER_CLI rm -f postgres || true + $CONTAINER_CLI run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 -d docker.io/postgis/postgis:9.5-2.5 } -postgis(){ - docker rm -f postgis || true - docker run --name postgis -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 -d postgis/postgis:11-2.5 +postgresql_13() { + $CONTAINER_CLI rm -f postgres || true + $CONTAINER_CLI run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 -d docker.io/postgis/postgis:13-3.1 +} + +edb() { + #$CONTAINER_CLI login containers.enterprisedb.com + $CONTAINER_CLI rm -f edb || true + $CONTAINER_CLI run --name edb -e ACCEPT_EULA=Yes -e DATABASE_USER=hibernate_orm_test -e DATABASE_USER_PASSWORD=hibernate_orm_test -e ENTERPRISEDB_PASSWORD=hibernate_orm_test -e DATABASE_NAME=hibernate_orm_test -e PGPORT=5433 -p 5433:5433 --mount type=tmpfs,destination=/edbvolume -d containers.enterprisedb.com/edb/edb-as-lite:v11 } db2() { - docker rm -f db2 || true - docker run --name db2 --privileged -e DB2INSTANCE=orm_test -e DB2INST1_PASSWORD=orm_test -e DBNAME=orm_test -e LICENSE=accept -e AUTOCONFIG=false -e ARCHIVE_LOGS=false -e TO_CREATE_SAMPLEDB=false -e REPODB=false -p 50000:50000 -d ibmcom/db2:11.5.5.0 + echo $CONTAINER_CLI + $PRIVILEGED_CLI $CONTAINER_CLI rm -f db2 || true + $PRIVILEGED_CLI $CONTAINER_CLI run --name db2 --privileged -e DB2INSTANCE=orm_test -e DB2INST1_PASSWORD=orm_test -e DBNAME=orm_test -e LICENSE=accept -e AUTOCONFIG=false -e ARCHIVE_LOGS=false -e TO_CREATE_SAMPLEDB=false -e REPODB=false -p 50000:50000 -d docker.io/ibmcom/db2:11.5.7.0 # Give the container some time to start OUTPUT= while [[ $OUTPUT != *"INSTANCE"* ]]; do echo "Waiting for DB2 to start..." sleep 10 - OUTPUT=$(docker logs db2) + OUTPUT=$($PRIVILEGED_CLI $CONTAINER_CLI logs db2) done - docker exec -t db2 su - orm_test bash -c ". /database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2 'connect to orm_test' && /database/config/orm_test/sqllib/bin/db2 'CREATE USER TEMPORARY TABLESPACE usr_tbsp MANAGED BY AUTOMATIC STORAGE'" + $PRIVILEGED_CLI $CONTAINER_CLI exec -t db2 su - orm_test bash -c ". /database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2 'connect to orm_test' && /database/config/orm_test/sqllib/bin/db2 'CREATE USER TEMPORARY TABLESPACE usr_tbsp MANAGED BY AUTOMATIC STORAGE'" } db2_spatial() { - docker rm -f db2spatial || true + $PRIVILEGED_CLI $CONTAINER_CLI rm -f db2spatial || true temp_dir=$(mktemp -d) cat <${temp_dir}/ewkt.sql create or replace function db2gse.asewkt(geometry db2gse.st_geometry) @@ -78,35 +156,35 @@ CREATE TRANSFORM FOR db2gse.ST_Geometry DB2_PROGRAM ( TO SQL WITH FUNCTION db2gse.geomfromewkt(varchar(32000)) ) ; EOF - docker run --name db2spatial --privileged -e DB2INSTANCE=orm_test -e DB2INST1_PASSWORD=orm_test -e DBNAME=orm_test -e LICENSE=accept -e AUTOCONFIG=false -e ARCHIVE_LOGS=false -e TO_CREATE_SAMPLEDB=false -e REPODB=false \ + $PRIVILEGED_CLI $CONTAINER_CLI run --name db2spatial --privileged -e DB2INSTANCE=orm_test -e DB2INST1_PASSWORD=orm_test -e DBNAME=orm_test -e LICENSE=accept -e AUTOCONFIG=false -e ARCHIVE_LOGS=false -e TO_CREATE_SAMPLEDB=false -e REPODB=false \ -v ${temp_dir}:/conf \ - -p 50000:50000 -d ibmcom/db2:11.5.5.0 + -p 50000:50000 -d docker.io/ibmcom/db2:11.5.5.0 # Give the container some time to start OUTPUT= while [[ $OUTPUT != *"Setup has completed."* ]]; do echo "Waiting for DB2 to start..." sleep 10 - OUTPUT=$(docker logs db2spatial) + OUTPUT=$($PRIVILEGED_CLI $CONTAINER_CLI logs db2spatial) done sleep 10 echo "Enabling spatial extender" - docker exec -t db2spatial su - orm_test bash -c "/database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2se enable_db orm_test" + $PRIVILEGED_CLI $CONTAINER_CLI exec -t db2spatial su - orm_test bash -c "/database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2se enable_db orm_test" echo "Installing required transform group" - docker exec -t db2spatial su - orm_test bash -c "/database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2 'connect to orm_test' && /database/config/orm_test/sqllib/bin/db2 -tvf /conf/ewkt.sql" + $PRIVILEGED_CLI $CONTAINER_CLI exec -t db2spatial su - orm_test bash -c "/database/config/orm_test/sqllib/db2profile && /database/config/orm_test/sqllib/bin/db2 'connect to orm_test' && /database/config/orm_test/sqllib/bin/db2 -tvf /conf/ewkt.sql" } mssql() { - docker rm -f mssql || true - docker run --name mssql -d -p 1433:1433 -e "SA_PASSWORD=Hibernate_orm_test" -e ACCEPT_EULA=Y mcr.microsoft.com/mssql/server:2017-CU13 + $CONTAINER_CLI rm -f mssql || true + $CONTAINER_CLI run --name mssql -d -p 1433:1433 -e "SA_PASSWORD=Hibernate_orm_test" -e ACCEPT_EULA=Y mcr.microsoft.com/mssql/server:2017-CU13 sleep 5 n=0 until [ "$n" -ge 5 ] do # We need a database that uses a non-lock based MVCC approach # https://github.com/microsoft/homebrew-mssql-release/issues/2#issuecomment-682285561 - docker exec mssql bash -c 'echo "create database hibernate_orm_test collate SQL_Latin1_General_CP1_CI_AS; alter database hibernate_orm_test set READ_COMMITTED_SNAPSHOT ON" | /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Hibernate_orm_test -i /dev/stdin' && break + $CONTAINER_CLI exec mssql bash -c 'echo "create database hibernate_orm_test collate SQL_Latin1_General_CP1_CS_AS; alter database hibernate_orm_test set READ_COMMITTED_SNAPSHOT ON" | /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Hibernate_orm_test -i /dev/stdin' && break echo "Waiting for SQL Server to start..." n=$((n+1)) sleep 5 @@ -118,19 +196,146 @@ mssql() { fi } -oracle() { - docker rm -f oracle || true - # We need to use the defaults - # SYSTEM/Oracle18 - docker run --shm-size=1536m --name oracle -d -p 1521:1521 --ulimit nofile=1048576:1048576 quillbuilduser/oracle-18-xe - until [ "`docker inspect -f {{.State.Health.Status}} oracle`" == "healthy" ]; +sybase() { + $CONTAINER_CLI rm -f sybase || true + # Yup, that sucks, but on ubuntu we need to use -T11889 as per: https://github.com/DataGrip/docker-env/issues/12 + $CONTAINER_CLI run -d -p 5000:5000 -p 5001:5001 --name sybase --entrypoint /bin/bash docker.io/nguoianphu/docker-sybase -c "source /opt/sybase/SYBASE.sh +/opt/sybase/ASE-16_0/bin/dataserver \ +-d/opt/sybase/data/master.dat \ +-e/opt/sybase/ASE-16_0/install/MYSYBASE.log \ +-c/opt/sybase/ASE-16_0/MYSYBASE.cfg \ +-M/opt/sybase/ASE-16_0 \ +-N/opt/sybase/ASE-16_0/sysam/MYSYBASE.properties \ +-i/opt/sybase \ +-sMYSYBASE \ +-T11889 +RET=\$? +exit 0 +" + + sybase_check() { + $CONTAINER_CLI exec sybase bash -c "source /opt/sybase/SYBASE.sh; +/opt/sybase/OCS-16_0/bin/isql -Usa -P myPassword -S MYSYBASE < 0 +go +quit +EOF +" +} + START_STATUS=0 + j=1 + while (( $j < 30 )); do + echo "Waiting for Sybase to start..." + sleep 1 + j=$((j+1)) + START_STATUS=$(sybase_check | grep '(0 rows affected)' | wc -c) + if (( $START_STATUS > 0 )); then + break + fi + done + if (( $j == 30 )); then + echo "Failed starting Sybase" + $CONTAINER_CLI ps -a + $CONTAINER_CLI logs sybase + sybase_check + exit 1 + fi + + export SYBASE_DB=hibernate_orm_test + export SYBASE_USER=hibernate_orm_test + export SYBASE_PASSWORD=hibernate_orm_test + $CONTAINER_CLI exec sybase bash -c "source /opt/sybase/SYBASE.sh; +cat <<-EOSQL > init1.sql +use master +go +disk resize name='master', size='256m' +go +create database $SYBASE_DB on master = '96m' +go +sp_dboption $SYBASE_DB, \"single user\", true +go +alter database $SYBASE_DB log on master = '50m' +go +use $SYBASE_DB +go +exec sp_extendsegment logsegment, $SYBASE_DB, master +go +use master +go +sp_dboption $SYBASE_DB, \"single user\", false +go +use $SYBASE_DB +go +checkpoint +go +use master +go +create login $SYBASE_USER with password $SYBASE_PASSWORD +go +exec sp_dboption $SYBASE_DB, 'abort tran on log full', true +go +exec sp_dboption $SYBASE_DB, 'allow nulls by default', true +go +exec sp_dboption $SYBASE_DB, 'ddl in tran', true +go +exec sp_dboption $SYBASE_DB, 'trunc log on chkpt', true +go +exec sp_dboption $SYBASE_DB, 'full logging for select into', true +go +exec sp_dboption $SYBASE_DB, 'full logging for alter table', true +go +sp_dboption $SYBASE_DB, \"select into\", true +go +sp_dboption tempdb, 'ddl in tran', true +go +EOSQL + +/opt/sybase/OCS-16_0/bin/isql -Usa -P myPassword -S MYSYBASE -i ./init1.sql + +echo =============== CREATING DB ========================== +cat <<-EOSQL > init2.sql +use $SYBASE_DB +go +sp_adduser '$SYBASE_USER', '$SYBASE_USER', null +go +grant create default to $SYBASE_USER +go +grant create table to $SYBASE_USER +go +grant create view to $SYBASE_USER +go +grant create rule to $SYBASE_USER +go +grant create function to $SYBASE_USER +go +grant create procedure to $SYBASE_USER +go +commit +go +EOSQL + +/opt/sybase/OCS-16_0/bin/isql -Usa -P myPassword -S MYSYBASE -i ./init2.sql" + echo "Sybase successfully started" +} + +oracle_setup() { + HEALTHSTATUS= + until [ "$HEALTHSTATUS" == "healthy" ]; do echo "Waiting for Oracle to start..." - sleep 10; + sleep 5; + # On WSL, health-checks intervals don't work for Podman, so run them manually + if command -v podman > /dev/null; then + $CONTAINER_CLI healthcheck run oracle > /dev/null + fi + HEALTHSTATUS="`$CONTAINER_CLI inspect -f $HEALTCHECK_PATH oracle`" + HEALTHSTATUS=${HEALTHSTATUS##+( )} #Remove longest matching series of spaces from the front + HEALTHSTATUS=${HEALTHSTATUS%%+( )} #Remove longest matching series of spaces from the back done + sleep 2; echo "Oracle successfully started" # We increase file sizes to avoid online resizes as that requires lots of CPU which is restricted in XE - docker exec oracle bash -c "source /home/oracle/.bashrc; bash -c \" + $CONTAINER_CLI exec oracle bash -c "source /home/oracle/.bashrc; bash -c \" cat <$temp_dir/password.json chmod 777 -R $temp_dir - docker rm -f hana || true - docker run -d --name hana -p 39013:39013 -p 39017:39017 -p 39041-39045:39041-39045 -p 1128-1129:1128-1129 -p 59013-59014:59013-59014 \ + $CONTAINER_CLI rm -f hana || true + $CONTAINER_CLI run -d --name hana -p 39013:39013 -p 39017:39017 -p 39041-39045:39041-39045 -p 1128-1129:1128-1129 -p 59013-59014:59013-59014 \ --memory=8g \ --ulimit nofile=1048576:1048576 \ --sysctl kernel.shmmax=1073741824 \ @@ -204,7 +461,7 @@ hana() { --sysctl kernel.shmmni=4096 \ --sysctl kernel.shmall=8388608 \ -v $temp_dir:/config \ - store/saplabs/hanaexpress:2.00.045.00.20200121.1 \ + docker.io/store/saplabs/hanaexpress:2.00.045.00.20200121.1 \ --passwords-url file:///config/password.json \ --agree-to-sap-license # Give the container some time to start @@ -212,22 +469,22 @@ hana() { while [[ $OUTPUT != *"Startup finished"* ]]; do echo "Waiting for HANA to start..." sleep 10 - OUTPUT=$(docker logs hana) + OUTPUT=$($CONTAINER_CLI logs hana) done echo "HANA successfully started" } cockroachdb() { - docker rm -f cockroach || true - docker run -d --name=cockroach -p 26257:26257 -p 8080:8080 cockroachdb/cockroach:v20.2.4 start-single-node --insecure + $CONTAINER_CLI rm -f cockroach || true + $CONTAINER_CLI run -d --name=cockroach -p 26257:26257 -p 8080:8080 docker.io/cockroachdb/cockroach:v20.2.4 start-single-node --insecure OUTPUT= while [[ $OUTPUT != *"CockroachDB node starting"* ]]; do echo "Waiting for CockroachDB to start..." sleep 10 - OUTPUT=$(docker logs cockroach) + OUTPUT=$($CONTAINER_CLI logs cockroach) done echo "Enabling experimental box2d operators" - docker exec -it cockroach bash -c "cat <[] getAnnotatedClasses() { @Entity(name = "Super") @Inheritance(strategy = InheritanceType.JOINED) - @Where(clause = "DELETED = false") + @Where(clause = "deleted = false") public static class Super { @Id @GeneratedValue(strategy = GenerationType.AUTO) diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java index 566e3681166a..510ec9c20e65 100644 --- a/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/serialization/EntityProxySerializationTest.java @@ -67,7 +67,7 @@ public void prepare() { final Transaction t = s.beginTransaction(); try { - final Number count = (Number) s.createQuery("SELECT count(ID) FROM SimpleEntity").getSingleResult(); + final Number count = (Number) s.createQuery("SELECT count(e.id) FROM SimpleEntity e").getSingleResult(); if (count.longValue() > 0L) { // entity already added previously return; diff --git a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java index 09c84f2a8975..b623df317629 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java @@ -55,6 +55,7 @@ import org.hibernate.dialect.MariaDB102Dialect; import org.hibernate.dialect.MariaDB10Dialect; import org.hibernate.dialect.MariaDB53Dialect; +import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.SQLServer2012Dialect; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport; @@ -85,6 +86,7 @@ @RunWith(CustomParameterized.class) @TestForIssue(jiraKey = { "HHH-14921", "HHH-14922" }) +@SkipForDialect(value = MySQLDialect.class, comment = "MySQL doesn't support sequences") @SkipForDialect(value = MariaDB53Dialect.class, strictMatching = true, comment = "MariaDB < 10.3 doesn't support sequences") @SkipForDialect(value = MariaDB10Dialect.class, strictMatching = true, diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/list/PersistentListTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/list/PersistentListTest.java index 6f315ba71d43..6712614db28c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/list/PersistentListTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/list/PersistentListTest.java @@ -131,7 +131,7 @@ public void execute(Connection connection) throws SQLException { final QueryableCollection queryableCollection = (QueryableCollection) collectionPersister; SimpleSelect select = new SimpleSelect( getDialect() ) .setTableName( queryableCollection.getTableName() ) - .addColumn( "ORDER_ID" ) + .addColumn( "order_id" ) .addColumn( "INDX" ) .addColumn( "PRD_CODE" ); PreparedStatement preparedStatement = ((SessionImplementor)session2).getJdbcCoordinator().getStatementPreparer().prepareStatement( select.toStatementString() ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/map/Mappings.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/collection/map/Mappings.hbm.xml index a0815864dbb9..0c44de49bbce 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/map/Mappings.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/map/Mappings.hbm.xml @@ -24,7 +24,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/set/Mappings.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/collection/set/Mappings.hbm.xml index 6c446b8d3e50..a364109d2afd 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/set/Mappings.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/set/Mappings.hbm.xml @@ -22,7 +22,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/set/MappingsNonLazy.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/collection/set/MappingsNonLazy.hbm.xml index df17f526c903..36ba14773290 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/set/MappingsNonLazy.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/set/MappingsNonLazy.hbm.xml @@ -22,7 +22,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/component/basic/ComponentTest.java b/hibernate-core/src/test/java/org/hibernate/test/component/basic/ComponentTest.java index a176368a688a..ea80dd6615af 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/component/basic/ComponentTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/component/basic/ComponentTest.java @@ -232,7 +232,7 @@ public void testCustomColumnReadAndWrite() { // Value returned by Oracle native query is a Types.NUMERIC, which is mapped to a BigDecimalType; // Cast returned value to Number then call Number.doubleValue() so it works on all dialects. Double heightViaSql = - ( (Number)s.createSQLQuery("select height_centimeters from T_USER where T_USER.username='steve'").uniqueResult()) + ( (Number)s.createSQLQuery("select height_centimeters from T_USER where T_USER.userName='steve'").uniqueResult()) .doubleValue(); assertEquals(HEIGHT_CENTIMETERS, heightViaSql, 0.01d); @@ -257,7 +257,7 @@ public void testCustomColumnReadAndWrite() { u.getPerson().setHeightInches(1); s.flush(); heightViaSql = - ( (Number)s.createSQLQuery("select height_centimeters from T_USER where T_USER.username='steve'").uniqueResult() ) + ( (Number)s.createSQLQuery("select height_centimeters from T_USER where T_USER.userName='steve'").uniqueResult() ) .doubleValue(); assertEquals(2.54d, heightViaSql, 0.01d); s.delete(u); diff --git a/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java b/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java index 8e23e2cd2f58..27ea000b1fb2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java @@ -368,15 +368,11 @@ public void testQueryCacheInvalidation() throws Exception { } @Test - @RequiresDialectFeature( - value = DialectChecks.CaseSensitiveCheck.class, - comment = "i.name='widget' should not match on case sensitive database." - ) - public void testCaseInsensitiveComparison() { + public void testComparison() { Session s = openSession(); s.beginTransaction(); Item i = new Item(); - i.setName( "Widget" ); + i.setName( "widget" ); i.setDescription( "A really top-quality, full-featured widget." ); s.save( i ); s.getTransaction().commit(); @@ -387,7 +383,7 @@ public void testCaseInsensitiveComparison() { List result = s.createQuery( queryString ).list(); assertEquals(1, result.size()); i = (Item) s.get( Item.class, new Long(i.getId()) ); - assertEquals( i.getName(), "Widget" ); + assertEquals( i.getName(), "widget" ); s.delete(i); s.getTransaction().commit(); s.close(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/reattachment/Mappings.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/reattachment/Mappings.hbm.xml index 86aee42aa5c3..7e1e6d76bb47 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/reattachment/Mappings.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/reattachment/Mappings.hbm.xml @@ -22,7 +22,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/autodiscovery/AutoDiscoveryTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/autodiscovery/AutoDiscoveryTest.java index a7d901699ae2..57bbedda03ee 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/autodiscovery/AutoDiscoveryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/autodiscovery/AutoDiscoveryTest.java @@ -33,7 +33,7 @@ */ public class AutoDiscoveryTest extends BaseCoreFunctionalTestCase { private static final String QUERY_STRING = - "select u.name as username, g.name as groupname, m.joindate " + + "select u.name as username, g.name as groupname, m.joinDate " + "from t_membership m " + " inner join t_user u on m.member_id = u.id " + " inner join t_group g on m.group_id = g.id"; diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sqlserver/Mappings.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sqlserver/Mappings.hbm.xml index 0407a4125862..52928b26b2e3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sqlserver/Mappings.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sqlserver/Mappings.hbm.xml @@ -70,11 +70,11 @@ DELETE FROM EMPLOYMENT WHERE EMPID=? - - + + - + INSERT INTO TEXTHOLDER @@ -85,11 +85,11 @@ DELETE FROM TEXTHOLDER WHERE ID=? - - + + - + INSERT INTO IMAGEHOLDER diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueries.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueries.hbm.xml index 221ef07fe11b..4764a02a818e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueries.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueries.hbm.xml @@ -127,14 +127,14 @@ - + - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java index 0c0f6b041765..fdf460e33278 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java @@ -103,7 +103,7 @@ protected String getEmploymentSQL() { } protected String getEmploymentSQLMixedScalarEntity() { - return "SELECT e.*, e.employer as employerid FROM EMPLOYMENT e" ; + return "SELECT e.*, e.EMPLOYER as employerid FROM EMPLOYMENT e" ; } protected String getOrgEmpRegionSQL() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/timestamp/LocalDateCustomSessionLevelTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/test/timestamp/LocalDateCustomSessionLevelTimeZoneTest.java deleted file mode 100644 index 050149e7e5e2..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/timestamp/LocalDateCustomSessionLevelTimeZoneTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 . - */ -package org.hibernate.test.timestamp; - -import java.time.LocalDate; -import java.util.Map; -import java.util.TimeZone; -import javax.persistence.Entity; -import javax.persistence.Id; - -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.dialect.MySQL5Dialect; - -import org.hibernate.testing.RequiresDialect; -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.jdbc.ConnectionProviderDelegate; -import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; -import org.junit.Test; - -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernateSessionBuilder; -import static org.junit.Assert.assertEquals; - -/** - * @author Vlad Mihalcea - */ -@RequiresDialect(MySQL5Dialect.class) -public class LocalDateCustomSessionLevelTimeZoneTest - extends BaseNonConfigCoreFunctionalTestCase { - - private static final TimeZone TIME_ZONE = TimeZone.getTimeZone( - "Europe/Berlin" ); - - private ConnectionProviderDelegate connectionProvider = new ConnectionProviderDelegate() { - @Override - public void configure(Map configurationValues) { - String url = (String) configurationValues.get( AvailableSettings.URL ); - if(!url.contains( "?" )) { - url += "?"; - } - else if(!url.endsWith( "&" )) { - url += "&"; - } - - url += "useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Europe/Berlin"; - - configurationValues.put( AvailableSettings.URL, url); - super.configure( configurationValues ); - } - }; - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Person.class - }; - } - - @Override - protected void addSettings(Map settings) { - settings.put( - AvailableSettings.CONNECTION_PROVIDER, - connectionProvider - ); - } - - @Override - protected void releaseResources() { - super.releaseResources(); - connectionProvider.stop(); - } - - @Test - @TestForIssue( jiraKey = "HHH-11396" ) - public void testTimeZone() { - TimeZone old = TimeZone.getDefault(); - try { - // The producer (MySQL) Berlin and returns 1980-01-01 - TimeZone jdbcTimeZone = TimeZone.getTimeZone( "Europe/Berlin" ); - TimeZone.setDefault( jdbcTimeZone ); - - //hibernate.connection.url jdbc:mysql://localhost/hibernate_orm_test - doInHibernateSessionBuilder( () -> sessionFactory().withOptions().jdbcTimeZone( TIME_ZONE ), s -> { - Person person = new Person(); - person.id = 1L; - s.persist( person ); - } ); - - doInHibernateSessionBuilder( () -> sessionFactory().withOptions().jdbcTimeZone( TIME_ZONE ), s -> { - Person person = s.find( Person.class, 1L ); - assertEquals( LocalDate.of( 2017, 3, 7 ), person.createdOn ); - } ); - } - finally { - TimeZone.setDefault( old ); - } - } - - @Entity(name = "Person") - public static class Person { - - @Id - private Long id; - - private LocalDate createdOn = LocalDate.of( 2017, 3, 7 ); - } -} - diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java index 574f2614edb7..00fb2c690689 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyManyToManyNonUniqueIdWhereTest.java @@ -300,7 +300,7 @@ public void setSizesFromCombined(Set sizesFromCombined) { inverseJoinColumns = { @JoinColumn( name = "ASSOCIATION_ID" ) } ) @WhereJoinTable( clause = "MAIN_CODE='MATERIAL' AND ASSOCIATION_CODE='RATING'" ) - @Where( clause = "name = 'high' or name = 'medium'" ) + @Where( clause = "NAME = 'high' or NAME = 'medium'" ) @Immutable public List getMediumOrHighRatingsFromCombined() { return mediumOrHighRatingsFromCombined; @@ -387,7 +387,7 @@ public void setRatingsFromCombined(Set ratingsFromCombined) { joinColumns = { @JoinColumn( name = "BUILDING_ID") }, inverseJoinColumns = { @JoinColumn( name = "RATING_ID" ) } ) - @Where( clause = "name = 'high' or name = 'medium'" ) + @Where( clause = "NAME = 'high' or NAME = 'medium'" ) @Immutable public List getMediumOrHighRatings() { return mediumOrHighRatings; diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyOneToManyNonUniqueIdWhereTest.java b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyOneToManyNonUniqueIdWhereTest.java index c30d9bf6db3f..c4a8d43e73c2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyOneToManyNonUniqueIdWhereTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/where/annotations/LazyOneToManyNonUniqueIdWhereTest.java @@ -197,7 +197,7 @@ public void setSizesFromCombined(Set sizesFromCombined) { @OneToMany @JoinColumn( name = "MATERIAL_OWNER_ID") - @Where( clause = "name = 'high' or name = 'medium'" ) + @Where( clause = "NAME = 'high' or NAME = 'medium'" ) @Immutable public List getMediumOrHighRatingsFromCombined() { return mediumOrHighRatingsFromCombined; diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.hbm.xml index d0b365853c51..8bba29ab56ce 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyManyToManyNonUniqueIdWhereTest.hbm.xml @@ -22,7 +22,7 @@ where="MAIN_CODE='MATERIAL' AND ASSOCIATION_CODE='RATING'"> + where="NAME = 'high' or NAME = 'medium'"/> @@ -54,7 +54,7 @@ + where="NAME = 'high' or NAME = 'medium'"/> diff --git a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.hbm.xml index caf46130696f..9925130fa286 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/where/hbm/LazyOneToManyNonUniqueIdWhereTest.hbm.xml @@ -18,7 +18,7 @@ + where="NAME = 'high' or NAME = 'medium'"> diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/UnspecifiedEnumTypeTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/UnspecifiedEnumTypeTest.java index f2483ecfb4eb..86d01512827c 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/UnspecifiedEnumTypeTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/UnspecifiedEnumTypeTest.java @@ -104,7 +104,7 @@ public void testEnumRepresentation() { @SuppressWarnings("unchecked") List values = session - .createNativeQuery( "SELECT enum1 e1, enum2 e2 FROM ENUM_ENTITY_AUD ORDER BY rev ASC" ) + .createNativeQuery( "SELECT enum1 e1, enum2 e2 FROM ENUM_ENTITY_AUD ORDER BY REV ASC" ) .addScalar( "e1", IntegerType.INSTANCE ) .addScalar( "e2", IntegerType.INSTANCE ) .list(); diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/multiplerelations/GroupMemberTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/multiplerelations/GroupMemberTest.java index 70d431b87d2d..01bc64839afc 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/multiplerelations/GroupMemberTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/multiplerelations/GroupMemberTest.java @@ -89,7 +89,7 @@ private Integer getCurrentAuditUniqueGroupId() { return TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { final Session session = entityManager.unwrap( Session.class ); final Query query = session.createSQLQuery( - "SELECT uniqueGroup_id FROM GroupMember_AUD ORDER BY rev DESC" ).addScalar( + "SELECT uniqueGroup_id FROM GroupMember_AUD ORDER BY REV DESC" ).addScalar( "uniqueGroup_id", IntegerType.INSTANCE ).setMaxResults( 1 ); diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/cleaner/SQLServerDatabaseCleaner.java b/hibernate-testing/src/main/java/org/hibernate/testing/cleaner/SQLServerDatabaseCleaner.java index 98ef6ef2d6c7..d149073c6f79 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/cleaner/SQLServerDatabaseCleaner.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/cleaner/SQLServerDatabaseCleaner.java @@ -54,22 +54,22 @@ public void clearAllSchemas(Connection c) { LOG.log( Level.FINEST, "Collect schema objects: START" ); rs = s.executeQuery( "SELECT 'ALTER TABLE [' + TABLE_SCHEMA + '].[' + TABLE_NAME + '] DROP CONSTRAINT [' + CONSTRAINT_NAME + ']' FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE " + - "WHERE EXISTS (SELECT 1 FROM sys.Tables t JOIN sys.Schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME) " + - "AND EXISTS (SELECT 1 FROM sys.Foreign_keys WHERE name = CONSTRAINT_NAME)" ); + "WHERE EXISTS (SELECT 1 FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME) " + + "AND EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = CONSTRAINT_NAME)" ); while ( rs.next() ) { sqls.add( rs.getString( 1 ) ); } rs = s.executeQuery( "SELECT 'DROP VIEW [' + TABLE_SCHEMA + '].[' + TABLE_NAME + ']' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'VIEW' " + - "AND EXISTS (SELECT 1 FROM sys.Views t JOIN sys.Schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME)" ); + "AND EXISTS (SELECT 1 FROM sys.views t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME)" ); while ( rs.next() ) { sqls.add( rs.getString( 1 ) ); } rs = s.executeQuery( "SELECT 'DROP TABLE [' + TABLE_SCHEMA + '].[' + TABLE_NAME + ']' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' " + - "AND EXISTS (SELECT 1 FROM sys.Tables t JOIN sys.Schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME)" ); + "AND EXISTS (SELECT 1 FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME)" ); while ( rs.next() ) { sqls.add( rs.getString( 1 ) ); } From dad493d867aded008565e73a4f0704941fdba61e Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Thu, 2 Sep 2021 13:08:58 +0200 Subject: [PATCH 069/201] HHH-15178 Introduce internal init_sql config and by default enable ansinull for Sybase ASE --- .github/workflows/contributor-build.yml | 3 +- .../org/hibernate/userguide/hql/HQLTest.java | 4 + .../mapping/basic/ClobCharArrayTest.java | 3 + .../mapping/basic/ClobStringTest.java | 3 + .../userguide/mapping/basic/ClobTest.java | 3 + .../mapping/basic/NClobCharArrayTest.java | 2 + .../mapping/basic/NClobStringTest.java | 2 + .../userguide/mapping/basic/NClobTest.java | 2 + .../mapping/basic/NationalizedTest.java | 2 + .../mapping/basic/SubselectTest.java | 2 + .../DatabaseValueGenerationTest.java | 3 + .../userguide/pc/CascadeOnDeleteTest.java | 3 + .../schema/UniqueConstraintTest.java | 3 + .../src/test/resources/hibernate.properties | 1 + gradle/databases.gradle | 94 +++++++++++++------ .../src/test/resources/hibernate.properties | 1 + .../src/test/resources/hibernate.properties | 1 + .../org/hibernate/dialect/SybaseDialect.java | 6 ++ .../internal/BasicConnectionCreator.java | 15 ++- .../internal/ConnectionCreatorBuilder.java | 62 ------------ .../internal/ConnectionCreatorFactory.java | 32 +++++++ .../ConnectionCreatorFactoryImpl.java | 59 ++++++++++++ .../internal/DriverConnectionCreator.java | 5 +- .../DriverManagerConnectionCreator.java | 5 +- .../DriverManagerConnectionProviderImpl.java | 66 +++++++++++-- .../jpa/test/pack/cfgxmlpar/hibernate.cfg.xml | 1 + .../defaultpar/META-INF/persistence.xml | 1 + .../defaultpar_1_0/META-INF/persistence.xml | 1 + .../excludehbmpar/META-INF/persistence.xml | 1 + .../explicitpar/META-INF/persistence.xml | 1 + .../explicitpar2/META-INF/persistence.xml | 1 + .../explodedpar/META-INF/persistence.xml | 1 + .../overridenpar/overridenpar.properties | 3 +- .../space par/META-INF/persistence.xml | 1 + .../WEB-INF/classes/META-INF/persistence.xml | 1 + .../cache/spi/ReadWriteCacheTest.java | 4 + .../connection/ConnectionCreatorTest.java | 1 + .../CriteriaLiteralWithSingleQuoteTest.java | 4 +- .../criteria/selectcase/SelectCaseTest.java | 2 + ...tementIsClosedAfterALockExceptionTest.java | 2 +- .../test/procedure/DateTimeParameterTest.java | 64 ++++--------- .../jpa/test/procedure/JpaTckUsageTest.java | 79 +++++----------- .../hibernate/jpa/test/query/QueryTest.java | 11 --- .../jpa/PersistenceUnitOverridesTests.java | 1 + .../criteria/internal/NullPrecedenceTest.java | 3 + .../SearchedCaseExpressionTest.java | 3 + .../query/hhh14156/HHH14156Test.java | 2 + ...seCreationTimestampNullableColumnTest.java | 3 + .../annotations/embedded/EmbeddedTest.java | 1 - .../test/annotations/join/JoinTest.java | 3 + .../test/annotations/lob/LobTest.java | 4 + .../annotations/lob/VersionedLobTest.java | 4 + .../lob/locator/LobLocatorTest.java | 4 + .../annotations/onetomany/OneToManyTest.java | 3 + .../annotations/onetomany/OrderByTest.java | 5 + .../hhh9798/OneToOneJoinTableTest.java | 3 + .../annotations/query/QueryAndSQLTest.java | 6 +- ...hrowsConstraintViolationExceptionTest.java | 3 + .../DefaultCatalogAndSchemaTest.java | 2 + .../lazy/LazyLoadingByEnhancerSetterTest.java | 4 + .../converter/lob/ConverterAndLobTest.java | 3 + .../criterion/NationalizedIgnoreCaseTest.java | 2 + .../exception/SQLExceptionConversionTest.java | 3 + .../InheritanceManyToManyForeignKeyTest.java | 3 + .../MSSQLGeneratedPropertyEntity.hbm.xml | 2 +- .../org/hibernate/test/hql/IndicesTest.java | 1 + .../WhereAnnotatedOneToManySizeTest.java | 3 + .../test/limit/Oracle12LimitTest.java | 11 ++- .../hibernate/test/lob/BlobLocatorTest.java | 2 + .../hibernate/test/lob/ClobLocatorTest.java | 3 + .../hibernate/test/lob/LongByteArrayTest.java | 3 + .../test/lob/MaterializedClobTest.java | 4 + .../locking/paging/PagingAndLockingTest.java | 3 + ...ToManyAssociationClassGeneratedIdTest.java | 3 + .../usertypes/UserTypeMappingTest.java | 7 -- .../org/hibernate/test/ops/CreateTest.java | 2 + ...faultsSettingToFalseAndQuotedNameTest.java | 12 +++ ...dbcMetadataDefaultsSettingToFalseTest.java | 12 +++ .../sql/hand/custom/sybase/Mappings.hbm.xml | 12 +-- .../custom/sybase/SybaseCustomSQLTest.java | 2 + .../sql/hand/query/NativeSQLQueriesTest.java | 36 ++++++- .../org/hibernate/test/type/InstantTest.java | 9 +- .../test/type/LobUnfetchedPropertyTest.java | 4 + .../hibernate/test/type/LocalDateTest.java | 7 +- .../test/type/TimeAndTimestampTest.java | 4 +- .../test/typedescriptor/ByteTest.java | 4 + .../src/test/resources/hibernate.properties | 1 + .../ehcache/test/domain/HolidayCalendar.java | 10 ++ .../domain/HolidayCalendar.hbm.xml | 2 + .../envers/test/AbstractOneSessionTest.java | 2 + .../collection/StringMapLobTest.java | 2 + .../envers/test/integration/data/Lobs.java | 3 + .../src/test/resources/hibernate.properties | 1 + .../HikariCPConnectionProviderTest.java | 3 + .../hikaricp/HikariCPSkipAutoCommitTest.java | 3 + .../HikariTransactionIsolationConfigTest.java | 3 + .../src/test/resources/hibernate.properties | 1 + .../jcache/test/RefreshUpdatedDataTest.java | 4 + .../jcache/test/domain/HolidayCalendar.java | 10 ++ .../domain/HolidayCalendar.hbm.xml | 2 + .../src/test/resources/hibernate.properties | 1 + .../src/test/resources/hibernate.properties | 1 + .../src/test/resources/hibernate.properties | 1 + .../src/test/resources/hibernate.properties | 1 + .../env/ConnectionProviderBuilder.java | 2 + ...edDriverManagerConnectionProviderImpl.java | 23 ++++- .../ViburDBCPConnectionProviderTest.java | 14 +-- .../src/test/resources/hibernate.properties | 1 + 108 files changed, 599 insertions(+), 264 deletions(-) delete mode 100644 hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorBuilder.java create mode 100644 hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactory.java create mode 100644 hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactoryImpl.java diff --git a/.github/workflows/contributor-build.yml b/.github/workflows/contributor-build.yml index ee6ef4aec6bf..9c08ce859809 100644 --- a/.github/workflows/contributor-build.yml +++ b/.github/workflows/contributor-build.yml @@ -41,8 +41,7 @@ jobs: - rdbms: oracle - rdbms: db2 - rdbms: mssql -# Testing against Sybase requires many backports so let's skip it for now -# - rdbms: sybase + - rdbms: sybase # Running with HANA requires at least 8GB memory just for the database, which we don't have on GH Actions runners # - rdbms: hana steps: diff --git a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java index 98070dc5f67a..95027b62eb96 100644 --- a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java @@ -31,6 +31,7 @@ import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.type.StringType; import org.hibernate.userguide.model.AddressType; @@ -1314,6 +1315,7 @@ public void test_hql_sqrt_function_example() { @Test @SkipForDialect(SQLServerDialect.class) + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "current_date requires parenthesis which we don't render") @SkipForDialect(value = DerbyDialect.class, comment = "Comparisons between 'DATE' and 'TIMESTAMP' are not supported") public void test_hql_current_date_function_example() { doInJPA( this::entityManagerFactory, entityManager -> { @@ -1356,6 +1358,7 @@ public void test_hql_current_time_function_example() { } @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "current_timestamp requires parenthesis which we don't render") public void test_hql_current_timestamp_function_example() { doInJPA( this::entityManagerFactory, entityManager -> { //tag::hql-current-timestamp-function-example[] @@ -1428,6 +1431,7 @@ public void test_hql_year_function_example() { @Test @SkipForDialect(SQLServerDialect.class) + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "No proper implementation for the STR function available") public void test_hql_str_function_example() { doInJPA( this::entityManagerFactory, entityManager -> { //tag::hql-str-function-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobCharArrayTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobCharArrayTest.java index feb6a1591ceb..c56769569d2b 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobCharArrayTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobCharArrayTest.java @@ -10,8 +10,10 @@ import javax.persistence.Id; import javax.persistence.Lob; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.SkipForDialect; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; @@ -20,6 +22,7 @@ /** * @author Vlad Mihalcea */ +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement character stream handling") public class ClobCharArrayTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobStringTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobStringTest.java index 5df1221983db..0a216a292ce9 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobStringTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobStringTest.java @@ -10,8 +10,10 @@ import javax.persistence.Id; import javax.persistence.Lob; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.SkipForDialect; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; @@ -20,6 +22,7 @@ /** * @author Vlad Mihalcea */ +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement character stream handling") public class ClobStringTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobTest.java index 503413c63659..f7edd1f10998 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ClobTest.java @@ -16,9 +16,11 @@ import javax.persistence.Lob; import org.hibernate.Session; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.engine.jdbc.ClobProxy; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.SkipForDialect; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; @@ -28,6 +30,7 @@ /** * @author Vlad Mihalcea */ +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement character stream handling") public class ClobTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobCharArrayTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobCharArrayTest.java index 50fa6a64d2d1..f2caf437df51 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobCharArrayTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobCharArrayTest.java @@ -14,6 +14,7 @@ import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.SkipForDialect; @@ -33,6 +34,7 @@ }, comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and https://hibernate.atlassian.net/browse/HHH-10695 and https://hibernate.atlassian.net/browse/HHH-10473" ) +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement nationalized handling") public class NClobCharArrayTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobStringTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobStringTest.java index 03d20f9234bb..15c7b5e63120 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobStringTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobStringTest.java @@ -14,6 +14,7 @@ import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.SkipForDialect; @@ -33,6 +34,7 @@ }, comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and https://hibernate.atlassian.net/browse/HHH-10695 and https://hibernate.atlassian.net/browse/HHH-10473" ) +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement nationalized handling") public class NClobStringTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobTest.java index 8061589e4253..01eaae8b2035 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NClobTest.java @@ -22,6 +22,7 @@ import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.engine.jdbc.NClobProxy; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; @@ -45,6 +46,7 @@ }, comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and https://hibernate.atlassian.net/browse/HHH-10695 and https://hibernate.atlassian.net/browse/HHH-10473" ) +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement nationalized handling") public class NClobTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NationalizedTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NationalizedTest.java index 22ba662aa263..c3257a988c1a 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NationalizedTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/NationalizedTest.java @@ -12,6 +12,7 @@ import org.hibernate.annotations.Nationalized; import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.SkipForDialect; @@ -30,6 +31,7 @@ }, comment = "@see https://hibernate.atlassian.net/browse/HHH-10693 and Derby doesn't support nationalized type" ) +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement nationalized handling") public class NationalizedTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/SubselectTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/SubselectTest.java index d64f14813fe6..438f72f7b954 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/SubselectTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/SubselectTest.java @@ -16,6 +16,7 @@ import org.hibernate.annotations.Subselect; import org.hibernate.annotations.Synchronize; import org.hibernate.dialect.DerbyDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.SkipForDialect; @@ -28,6 +29,7 @@ * @author Vlad Mihalcea */ @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't support a CONCAT function") +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase doesn't support a CONCAT function") public class SubselectTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java index 870997a9b4d3..fec3260c73f6 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/generated/DatabaseValueGenerationTest.java @@ -15,11 +15,13 @@ import javax.persistence.Id; import org.hibernate.annotations.ValueGenerationType; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.tuple.AnnotationValueGeneration; import org.hibernate.tuple.GenerationTiming; import org.hibernate.tuple.ValueGenerator; +import org.hibernate.testing.SkipForDialect; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; @@ -27,6 +29,7 @@ /** * @author Vlad Mihalcea */ +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "current_timestamp requires parenthesis which we don't render") public class DatabaseValueGenerationTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java index be78241ad8ed..5afa0bd0b976 100644 --- a/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteTest.java @@ -10,6 +10,8 @@ import org.hibernate.annotations.OnDeleteAction; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; @@ -17,6 +19,7 @@ /** * @author Vlad Mihalcea */ +@RequiresDialectFeature(DialectChecks.SupportsCascadeDeleteCheck.class) public class CascadeOnDeleteTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/documentation/src/test/java/org/hibernate/userguide/schema/UniqueConstraintTest.java b/documentation/src/test/java/org/hibernate/userguide/schema/UniqueConstraintTest.java index ba6f042ff885..6651ccd70f30 100644 --- a/documentation/src/test/java/org/hibernate/userguide/schema/UniqueConstraintTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/schema/UniqueConstraintTest.java @@ -19,9 +19,11 @@ import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.DynamicInsert; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.util.ExceptionUtil; import org.junit.Test; @@ -44,6 +46,7 @@ protected Class[] getAnnotatedClasses() { } @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void test() { //tag::schema-generation-columns-unique-constraint-persist-example[] Author _author = doInJPA( this::entityManagerFactory, entityManager -> { diff --git a/documentation/src/test/resources/hibernate.properties b/documentation/src/test/resources/hibernate.properties index 8b93710f2628..968847a8f3f7 100644 --- a/documentation/src/test/resources/hibernate.properties +++ b/documentation/src/test/resources/hibernate.properties @@ -10,6 +10,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.connection.pool_size 5 diff --git a/gradle/databases.gradle b/gradle/databases.gradle index f7ca83bedc75..bd2b65b16043 100644 --- a/gradle/databases.gradle +++ b/gradle/databases.gradle @@ -16,20 +16,23 @@ ext { 'jdbc.user' : 'sa', 'jdbc.pass' : '', 'jdbc.url' : 'jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;LOCK_TIMEOUT=10000', + 'connection.init_sql' : '' ], hsqldb : [ 'db.dialect' : 'org.hibernate.dialect.HSQLDialect', 'jdbc.driver': 'org.hsqldb.jdbc.JDBCDriver', 'jdbc.user' : 'sa', 'jdbc.pass' : '', - 'jdbc.url' : 'jdbc:hsqldb:mem:test' + 'jdbc.url' : 'jdbc:hsqldb:mem:test', + 'connection.init_sql' : '' ], derby : [ 'db.dialect' : 'org.hibernate.dialect.DerbyTenSevenDialect', 'jdbc.driver': 'org.apache.derby.jdbc.EmbeddedDriver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:derby:target/tmp/derby/hibernate_orm_test;databaseName=hibernate_orm_test;create=true' + 'jdbc.url' : 'jdbc:derby:target/tmp/derby/hibernate_orm_test;databaseName=hibernate_orm_test;create=true', + 'connection.init_sql' : '' ], pgsql : [ 'db.dialect' : 'org.hibernate.dialect.PostgreSQL95Dialect', @@ -37,7 +40,8 @@ ext { 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', // Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com - 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0' + 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0', + 'connection.init_sql' : '' ], pgsql_docker : [ 'db.dialect' : 'org.hibernate.dialect.PostgreSQL10Dialect', @@ -45,7 +49,8 @@ ext { 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', // Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com - 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0' + 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0', + 'connection.init_sql' : '' ], pgsql_ci : [ 'db.dialect' : 'org.hibernate.dialect.PostgreSQL95Dialect', @@ -53,7 +58,8 @@ ext { 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', // Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com - 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0' + 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0', + 'connection.init_sql' : '' ], sybase_ci : [ 'db.dialect' : 'org.hibernate.dialect.SybaseASE157Dialect', @@ -69,14 +75,16 @@ ext { 'jdbc.driver': 'com.mysql.jdbc.Driver', 'jdbc.user' : 'hibernateormtest', 'jdbc.pass' : 'hibernateormtest', - 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test' + 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test', + 'connection.init_sql' : '' ], mysql_docker : [ 'db.dialect' : 'org.hibernate.dialect.MySQL57Dialect', 'jdbc.driver': 'com.mysql.jdbc.Driver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test?useSSL=false' + 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test?useSSL=false', + 'connection.init_sql' : '' ], mysql_ci : [ 'db.dialect' : 'org.hibernate.dialect.MySQL8Dialect', @@ -92,28 +100,32 @@ ext { 'jdbc.driver': 'com.mysql.cj.jdbc.Driver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test?allowPublicKeyRetrieval=true&useSSL=false' + 'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test?allowPublicKeyRetrieval=true&useSSL=false', + 'connection.init_sql' : '' ], mariadb : [ 'db.dialect' : 'org.hibernate.dialect.MariaDB103Dialect', 'jdbc.driver': 'org.mariadb.jdbc.Driver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test' + 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test', + 'connection.init_sql' : '' ], mariadb_ci : [ 'db.dialect' : 'org.hibernate.dialect.MariaDB103Dialect', 'jdbc.driver': 'org.mariadb.jdbc.Driver', 'jdbc.user' : 'root', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test' + 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test', + 'connection.init_sql' : '' ], mariadb_spatial_ci : [ 'db.dialect' : 'org.hibernate.spatial.dialect.mariadb.MariaDB103SpatialDialect', 'jdbc.driver': 'org.mariadb.jdbc.Driver', 'jdbc.user' : 'root', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test' + 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test', + 'connection.init_sql' : '' ], postgis : [ 'db.dialect' : 'org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect', @@ -121,14 +133,16 @@ ext { 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', // Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com - 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0' + 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0', + 'connection.init_sql' : '' ], oracle : [ 'db.dialect' : 'org.hibernate.dialect.Oracle10gDialect', 'jdbc.driver': 'oracle.jdbc.OracleDriver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521/xe' + 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521/xe', + 'connection.init_sql' : '' ], oracle_jenkins : [ 'db.dialect' : 'org.hibernate.dialect.Oracle12cDialect', @@ -144,70 +158,80 @@ ext { 'jdbc.driver': 'oracle.jdbc.OracleDriver', 'jdbc.user' : 'c##hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521/ORCLPDB1.localdomain' + 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521/ORCLPDB1.localdomain', + 'connection.init_sql' : '' ], oracle_ci : [ 'db.dialect' : 'org.hibernate.dialect.Oracle12cDialect', 'jdbc.driver': 'oracle.jdbc.OracleDriver', 'jdbc.user' : 'SYSTEM', 'jdbc.pass' : 'Oracle18', - 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521:XE' + 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521:XE', + 'connection.init_sql' : '' ], oracle_spatial_ci : [ 'db.dialect' : 'org.hibernate.spatial.dialect.oracle.OracleSpatial10gDialect', 'jdbc.driver': 'oracle.jdbc.OracleDriver', 'jdbc.user' : 'SYSTEM', 'jdbc.pass' : 'Oracle18', - 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521:XE' + 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521:XE', + 'connection.init_sql' : '' ], mssql : [ 'db.dialect' : 'org.hibernate.dialect.SQLServer2012Dialect', 'jdbc.driver': 'com.microsoft.sqlserver.jdbc.SQLServerDriver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', - 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';instance=SQLEXPRESS;databaseName=hibernate_orm_test' + 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';instance=SQLEXPRESS;databaseName=hibernate_orm_test', + 'connection.init_sql' : '' ], mssql_ci : [ 'db.dialect' : 'org.hibernate.dialect.SQLServer2012Dialect', 'jdbc.driver': 'com.microsoft.sqlserver.jdbc.SQLServerDriver', 'jdbc.user' : 'sa', 'jdbc.pass' : 'Hibernate_orm_test', - 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';databaseName=hibernate_orm_test;sendTimeAsDatetime=false' + 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';databaseName=hibernate_orm_test;sendTimeAsDatetime=false', + 'connection.init_sql' : '' ], mssql_spatial_ci : [ 'db.dialect' : 'org.hibernate.spatial.dialect.sqlserver.SqlServer2012SpatialDialect', 'jdbc.driver': 'com.microsoft.sqlserver.jdbc.SQLServerDriver', 'jdbc.user' : 'sa', 'jdbc.pass' : 'Hibernate_orm_test', - 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';databaseName=hibernate_orm_test' + 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';databaseName=hibernate_orm_test', + 'connection.init_sql' : '' ], informix : [ 'db.dialect' : 'org.hibernate.dialect.InformixDialect', 'jdbc.driver': 'com.informix.jdbc.IfxDriver', 'jdbc.user' : 'informix', 'jdbc.pass' : 'in4mix', - 'jdbc.url' : 'jdbc:informix-sqli://' + dbHost + ':9088/sysuser:INFORMIXSERVER=dev;user=informix;password=in4mix' + 'jdbc.url' : 'jdbc:informix-sqli://' + dbHost + ':9088/sysuser:INFORMIXSERVER=dev;user=informix;password=in4mix', + 'connection.init_sql' : '' ], db2 : [ 'db.dialect' : 'org.hibernate.dialect.DB2Dialect', 'jdbc.driver': 'com.ibm.db2.jcc.DB2Driver', 'jdbc.user' : 'db2inst1', 'jdbc.pass' : 'db2inst1-pwd', - 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/hibern8' + 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/hibern8', + 'connection.init_sql' : '' ], db2_ci : [ 'db.dialect' : 'org.hibernate.dialect.DB2Dialect', 'jdbc.driver': 'com.ibm.db2.jcc.DB2Driver', 'jdbc.user' : 'orm_test', 'jdbc.pass' : 'orm_test', - 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/orm_test' + 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/orm_test', + 'connection.init_sql' : '' ], db2_spatial_ci : [ 'db.dialect' : 'org.hibernate.spatial.dialect.db2.DB2SpatialDialect', 'jdbc.driver': 'com.ibm.db2.jcc.DB2Driver', 'jdbc.user' : 'orm_test', 'jdbc.pass' : 'orm_test', - 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/orm_test' + 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/orm_test', + 'connection.init_sql' : '' ], hana : [ 'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect', @@ -215,7 +239,8 @@ ext { 'jdbc.user' : 'HIBERNATE_TEST', 'jdbc.pass' : 'H1bernate_test', // Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html - 'jdbc.url' : 'jdbc:sap://' + dbHost + ':30015/?statementCacheSize=0' + 'jdbc.url' : 'jdbc:sap://' + dbHost + ':30015/?statementCacheSize=0', + 'connection.init_sql' : '' ], hana_cloud : [ 'db.dialect' : 'org.hibernate.dialect.HANACloudColumnStoreDialect', @@ -223,7 +248,8 @@ ext { 'jdbc.user' : 'HIBERNATE_TEST', 'jdbc.pass' : 'H1bernate_test', // Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html - 'jdbc.url' : 'jdbc:sap://' + dbHost + ':443/?encrypt=true&validateCertificate=false&statementCacheSize=0' + 'jdbc.url' : 'jdbc:sap://' + dbHost + ':443/?encrypt=true&validateCertificate=false&statementCacheSize=0', + 'connection.init_sql' : '' ], hana_jenkins : [ 'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect', @@ -240,7 +266,8 @@ ext { 'jdbc.user' : 'VLAD', 'jdbc.pass' : 'V1ad_test', // Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html - 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39015/?statementCacheSize=0' + 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39015/?statementCacheSize=0', + 'connection.init_sql' : '' ], hana_docker : [ 'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect', @@ -248,7 +275,8 @@ ext { 'jdbc.user' : 'SYSTEM', 'jdbc.pass' : 'H1bernate_test', // Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html - 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0' + 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0', + 'connection.init_sql' : '' ], hana_ci : [ 'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect', @@ -256,7 +284,8 @@ ext { 'jdbc.user' : 'SYSTEM', 'jdbc.pass' : 'H1bernate_test', // Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html - 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0' + 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0', + 'connection.init_sql' : '' ], hana_spatial_ci : [ 'db.dialect' : 'org.hibernate.spatial.dialect.hana.HANASpatialDialect', @@ -264,7 +293,8 @@ ext { 'jdbc.user' : 'SYSTEM', 'jdbc.pass' : 'H1bernate_test', // Disable prepared statement caching due to https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.04/en-US/78f2163887814223858e4369d18e2847.html - 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0' + 'jdbc.url' : 'jdbc:sap://' + dbHost + ':39017/?statementCacheSize=0', + 'connection.init_sql' : '' ], cockroachdb : [ 'db.dialect' : 'org.hibernate.dialect.CockroachDB192Dialect', @@ -273,7 +303,8 @@ ext { 'jdbc.user' : 'root', 'jdbc.pass' : '', // Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com - 'jdbc.url' : 'jdbc:postgresql://' + dbHost + ':26257/defaultdb?sslmode=disable&preparedStatementCacheQueries=0' + 'jdbc.url' : 'jdbc:postgresql://' + dbHost + ':26257/defaultdb?sslmode=disable&preparedStatementCacheQueries=0', + 'connection.init_sql' : '' ], cockroachdb_spatial : [ 'db.dialect' : 'org.hibernate.spatial.dialect.cockroachdb.CockroachDB202SpatialDialect', @@ -282,7 +313,8 @@ ext { 'jdbc.user' : 'root', 'jdbc.pass' : '', // Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com - 'jdbc.url' : 'jdbc:postgresql://' + dbHost + ':26257/defaultdb?sslmode=disable&preparedStatementCacheQueries=0' + 'jdbc.url' : 'jdbc:postgresql://' + dbHost + ':26257/defaultdb?sslmode=disable&preparedStatementCacheQueries=0', + 'connection.init_sql' : '' ] ] } diff --git a/hibernate-agroal/src/test/resources/hibernate.properties b/hibernate-agroal/src/test/resources/hibernate.properties index 6b80862911be..da8399b8675f 100644 --- a/hibernate-agroal/src/test/resources/hibernate.properties +++ b/hibernate-agroal/src/test/resources/hibernate.properties @@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.jdbc.batch_size 10 hibernate.connection.provider_class AgroalConnectionProvider diff --git a/hibernate-c3p0/src/test/resources/hibernate.properties b/hibernate-c3p0/src/test/resources/hibernate.properties index 0d39da782e64..715af2a80a52 100644 --- a/hibernate-c3p0/src/test/resources/hibernate.properties +++ b/hibernate-c3p0/src/test/resources/hibernate.properties @@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.connection.pool_size 5 hibernate.c3p0.min_size 50 diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java index d6e7186b5f65..e28b5371a035 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java @@ -50,6 +50,12 @@ public String getNullColumnString() { return " null"; } + @Override + public boolean canCreateSchema() { + // As far as I can tell, it does not + return false; + } + @Override public String getCurrentSchemaCommand() { return "select db_name()"; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/BasicConnectionCreator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/BasicConnectionCreator.java index 00bb69cb4fff..e03481257d09 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/BasicConnectionCreator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/BasicConnectionCreator.java @@ -8,6 +8,7 @@ import java.sql.Connection; import java.sql.SQLException; +import java.sql.Statement; import java.util.Properties; import org.hibernate.HibernateException; @@ -34,18 +35,21 @@ public abstract class BasicConnectionCreator implements ConnectionCreator { private final boolean autoCommit; private final Integer isolation; + private final String initSql; public BasicConnectionCreator( ServiceRegistryImplementor serviceRegistry, String url, Properties connectionProps, boolean autocommit, - Integer isolation) { + Integer isolation, + String initSql) { this.serviceRegistry = serviceRegistry; this.url = url; this.connectionProps = connectionProps; this.autoCommit = autocommit; this.isolation = isolation; + this.initSql = initSql; } @Override @@ -78,6 +82,15 @@ public Connection createConnection() { throw convertSqlException( "Unable to set auto-commit (" + autoCommit + ")", e ); } + if ( initSql != null && !initSql.trim().isEmpty() ) { + try (Statement s = conn.createStatement()) { + s.execute( initSql ); + } + catch (SQLException e) { + throw convertSqlException( "Unable to execute initSql (" + initSql + ")", e ); + } + } + return conn; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorBuilder.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorBuilder.java deleted file mode 100644 index 1cfaf3b43add..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorBuilder.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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 . - */ -package org.hibernate.engine.jdbc.connections.internal; - -import java.sql.Driver; -import java.util.Properties; - -import org.hibernate.service.spi.ServiceRegistryImplementor; - -/** - * A builder for ConnectionCreator instances - * - * @author Steve Ebersole - */ -public class ConnectionCreatorBuilder { - private final ServiceRegistryImplementor serviceRegistry; - - private Driver driver; - - private String url; - private Properties connectionProps; - - private boolean autoCommit; - private Integer isolation; - - public ConnectionCreatorBuilder(ServiceRegistryImplementor serviceRegistry) { - this.serviceRegistry = serviceRegistry; - } - - public void setDriver(Driver driver) { - this.driver = driver; - } - - public void setUrl(String url) { - this.url = url; - } - - public void setConnectionProps(Properties connectionProps) { - this.connectionProps = connectionProps; - } - - public void setAutoCommit(boolean autoCommit) { - this.autoCommit = autoCommit; - } - - public void setIsolation(Integer isolation) { - this.isolation = isolation; - } - - public ConnectionCreator build() { - if ( driver == null ) { - return new DriverManagerConnectionCreator( serviceRegistry, url, connectionProps, autoCommit, isolation ); - } - else { - return new DriverConnectionCreator( driver, serviceRegistry, url, connectionProps, autoCommit, isolation ); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactory.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactory.java new file mode 100644 index 000000000000..73b0a760b5c4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactory.java @@ -0,0 +1,32 @@ +/* + * 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 . + */ +package org.hibernate.engine.jdbc.connections.internal; + +import java.sql.Driver; +import java.util.Map; +import java.util.Properties; + +import org.hibernate.service.spi.ServiceRegistryImplementor; + +/** + * A factory for {@link ConnectionCreator}. + * + * @author Christian Beikov + */ +interface ConnectionCreatorFactory { + + public ConnectionCreator create( + Driver driver, + ServiceRegistryImplementor serviceRegistry, + String url, + Properties connectionProps, + Boolean autocommit, + Integer isolation, + String initSql, + Map configurationValues); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactoryImpl.java new file mode 100644 index 000000000000..f20bf449864a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactoryImpl.java @@ -0,0 +1,59 @@ +/* + * 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 . + */ +package org.hibernate.engine.jdbc.connections.internal; + +import java.sql.Driver; +import java.util.Map; +import java.util.Properties; + +import org.hibernate.service.spi.ServiceRegistryImplementor; + +/** + * The default factory for ConnectionCreator instances + * + * @author Christian Beikov + */ +public class ConnectionCreatorFactoryImpl implements ConnectionCreatorFactory { + + public static final ConnectionCreatorFactory INSTANCE = new ConnectionCreatorFactoryImpl(); + + private ConnectionCreatorFactoryImpl() { + } + + @Override + public ConnectionCreator create( + Driver driver, + ServiceRegistryImplementor serviceRegistry, + String url, + Properties connectionProps, + Boolean autoCommit, + Integer isolation, + String initSql, + Map configurationValues) { + if ( driver == null ) { + return new DriverManagerConnectionCreator( + serviceRegistry, + url, + connectionProps, + autoCommit, + isolation, + initSql + ); + } + else { + return new DriverConnectionCreator( + driver, + serviceRegistry, + url, + connectionProps, + autoCommit, + isolation, + initSql + ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverConnectionCreator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverConnectionCreator.java index 5ace7c3ab5bc..7d5c5ad24f66 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverConnectionCreator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverConnectionCreator.java @@ -27,8 +27,9 @@ public DriverConnectionCreator( String url, Properties connectionProps, Boolean autocommit, - Integer isolation) { - super( serviceRegistry, url, connectionProps, autocommit, isolation ); + Integer isolation, + String initSql) { + super( serviceRegistry, url, connectionProps, autocommit, isolation, initSql ); this.driver = driver; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionCreator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionCreator.java index 4b6b70bd19da..b7a22d463e89 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionCreator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionCreator.java @@ -24,8 +24,9 @@ public DriverManagerConnectionCreator( String url, Properties connectionProps, Boolean autocommit, - Integer isolation) { - super( serviceRegistry, url, connectionProps, autocommit, isolation ); + Integer isolation, + String initSql) { + super( serviceRegistry, url, connectionProps, autocommit, isolation, initSql ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java index 1a999ed00625..7201b7e68928 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java @@ -22,6 +22,7 @@ import org.hibernate.HibernateException; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.Database; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; @@ -55,6 +56,8 @@ public class DriverManagerConnectionProviderImpl public static final String INITIAL_SIZE = "hibernate.connection.initial_pool_size"; // in TimeUnit.SECONDS public static final String VALIDATION_INTERVAL = "hibernate.connection.pool_validation_interval"; + public static final String INIT_SQL ="hibernate.connection.init_sql"; + public static final String CONNECTION_CREATOR_FACTORY ="hibernate.connection.creator_factory_class"; private volatile PoolState state; @@ -99,18 +102,19 @@ private PooledConnections buildPool(Map configurationValues, ServiceRegistryImpl } private static ConnectionCreator buildCreator(Map configurationValues, ServiceRegistryImplementor serviceRegistry) { - final ConnectionCreatorBuilder connectionCreatorBuilder = new ConnectionCreatorBuilder( serviceRegistry ); + final String url = (String) configurationValues.get( AvailableSettings.URL ); - final String driverClassName = (String) configurationValues.get( AvailableSettings.DRIVER ); - connectionCreatorBuilder.setDriver( loadDriverIfPossible( driverClassName, serviceRegistry ) ); + String driverClassName = (String) configurationValues.get( AvailableSettings.DRIVER ); + Driver driver = null; + if ( driverClassName != null ) { + driver = loadDriverIfPossible( driverClassName, serviceRegistry ); + } - final String url = (String) configurationValues.get( AvailableSettings.URL ); if ( url == null ) { final String msg = log.jdbcUrlNotSpecified( AvailableSettings.URL ); log.error( msg ); throw new HibernateException( msg ); } - connectionCreatorBuilder.setUrl( url ); log.usingDriver( driverClassName, url ); @@ -123,19 +127,38 @@ private static ConnectionCreator buildCreator(Map configurationValues, ServiceRe else { log.connectionProperties( ConfigurationHelper.maskOut( connectionProps, "password" ) ); } - connectionCreatorBuilder.setConnectionProps( connectionProps ); final boolean autoCommit = ConfigurationHelper.getBoolean( AvailableSettings.AUTOCOMMIT, configurationValues, false ); log.autoCommitMode( autoCommit ); - connectionCreatorBuilder.setAutoCommit( autoCommit ); final Integer isolation = ConnectionProviderInitiator.extractIsolation( configurationValues ); if ( isolation != null ) { log.jdbcIsolationLevel( ConnectionProviderInitiator.toIsolationNiceName( isolation ) ); } - connectionCreatorBuilder.setIsolation( isolation ); - return connectionCreatorBuilder.build(); + final String initSql = (String) configurationValues.get( INIT_SQL ); + + final Object connectionCreatorFactory = configurationValues.get( CONNECTION_CREATOR_FACTORY ); + ConnectionCreatorFactory factory = null; + if ( connectionCreatorFactory instanceof ConnectionCreatorFactory ) { + factory = (ConnectionCreatorFactory) connectionCreatorFactory; + } + else if ( connectionCreatorFactory != null ) { + factory = loadConnectionCreatorFactory( connectionCreatorFactory.toString(), serviceRegistry ); + } + if ( factory == null ) { + factory = ConnectionCreatorFactoryImpl.INSTANCE; + } + return factory.create( + driver, + serviceRegistry, + url, + connectionProps, + autoCommit, + isolation, + initSql, + configurationValues + ); } private static Driver loadDriverIfPossible(String driverClassName, ServiceRegistryImplementor serviceRegistry) { @@ -163,6 +186,31 @@ private static Driver loadDriverIfPossible(String driverClassName, ServiceRegist } } + private static ConnectionCreatorFactory loadConnectionCreatorFactory(String connectionCreatorFactoryClassName, ServiceRegistryImplementor serviceRegistry) { + if ( connectionCreatorFactoryClassName == null ) { + log.debug( "No connection creator factory class specified" ); + return null; + } + + if ( serviceRegistry != null ) { + final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); + final Class factoryClass = classLoaderService.classForName( connectionCreatorFactoryClassName ); + try { + return factoryClass.newInstance(); + } + catch ( Exception e ) { + throw new ServiceException( "Specified ConnectionCreatorFactory " + connectionCreatorFactoryClassName + " could not be loaded", e ); + } + } + + try { + return (ConnectionCreatorFactory) Class.forName( connectionCreatorFactoryClassName ).newInstance(); + } + catch ( Exception e1 ) { + throw new ServiceException( "Specified ConnectionCreatorFactory " + connectionCreatorFactoryClassName + " could not be loaded", e1 ); + } + } + // use the pool ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/hibernate-core/src/test/bundles/templates/cfgxmlpar/org/hibernate/jpa/test/pack/cfgxmlpar/hibernate.cfg.xml b/hibernate-core/src/test/bundles/templates/cfgxmlpar/org/hibernate/jpa/test/pack/cfgxmlpar/hibernate.cfg.xml index 31857a6a0174..7ae30207f756 100644 --- a/hibernate-core/src/test/bundles/templates/cfgxmlpar/org/hibernate/jpa/test/pack/cfgxmlpar/hibernate.cfg.xml +++ b/hibernate-core/src/test/bundles/templates/cfgxmlpar/org/hibernate/jpa/test/pack/cfgxmlpar/hibernate.cfg.xml @@ -14,6 +14,7 @@ org.h2.Driver sa + jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1 true hibernate.test diff --git a/hibernate-core/src/test/bundles/templates/defaultpar/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/defaultpar/META-INF/persistence.xml index a9e31e39bfdb..72ae3cdd598b 100644 --- a/hibernate-core/src/test/bundles/templates/defaultpar/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/defaultpar/META-INF/persistence.xml @@ -20,6 +20,7 @@ + diff --git a/hibernate-core/src/test/bundles/templates/defaultpar_1_0/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/defaultpar_1_0/META-INF/persistence.xml index c0cdcc9abde0..c754da627e74 100644 --- a/hibernate-core/src/test/bundles/templates/defaultpar_1_0/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/defaultpar_1_0/META-INF/persistence.xml @@ -19,6 +19,7 @@ + diff --git a/hibernate-core/src/test/bundles/templates/excludehbmpar/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/excludehbmpar/META-INF/persistence.xml index a98d66020c34..a7773844b69f 100644 --- a/hibernate-core/src/test/bundles/templates/excludehbmpar/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/excludehbmpar/META-INF/persistence.xml @@ -19,6 +19,7 @@ + diff --git a/hibernate-core/src/test/bundles/templates/explicitpar/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/explicitpar/META-INF/persistence.xml index ba220a889d4e..8512f8ea15b1 100644 --- a/hibernate-core/src/test/bundles/templates/explicitpar/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/explicitpar/META-INF/persistence.xml @@ -25,6 +25,7 @@ + diff --git a/hibernate-core/src/test/bundles/templates/explicitpar2/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/explicitpar2/META-INF/persistence.xml index 6dbad8251482..6763c7d9d23c 100644 --- a/hibernate-core/src/test/bundles/templates/explicitpar2/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/explicitpar2/META-INF/persistence.xml @@ -28,6 +28,7 @@ + diff --git a/hibernate-core/src/test/bundles/templates/explodedpar/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/explodedpar/META-INF/persistence.xml index e73198f56502..8a2291d6a964 100644 --- a/hibernate-core/src/test/bundles/templates/explodedpar/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/explodedpar/META-INF/persistence.xml @@ -18,6 +18,7 @@ + diff --git a/hibernate-core/src/test/bundles/templates/overridenpar/overridenpar.properties b/hibernate-core/src/test/bundles/templates/overridenpar/overridenpar.properties index 415ae95c8a01..dfe6e40ca3d9 100644 --- a/hibernate-core/src/test/bundles/templates/overridenpar/overridenpar.properties +++ b/hibernate-core/src/test/bundles/templates/overridenpar/overridenpar.properties @@ -7,4 +7,5 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ -hibernate.connection.password @jdbc.pass@ \ No newline at end of file +hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ \ No newline at end of file diff --git a/hibernate-core/src/test/bundles/templates/space par/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/space par/META-INF/persistence.xml index d4055de74136..de12ae6befa9 100644 --- a/hibernate-core/src/test/bundles/templates/space par/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/space par/META-INF/persistence.xml @@ -18,6 +18,7 @@ + diff --git a/hibernate-core/src/test/bundles/templates/war/WEB-INF/classes/META-INF/persistence.xml b/hibernate-core/src/test/bundles/templates/war/WEB-INF/classes/META-INF/persistence.xml index 784cb33395d1..4f61dc82a138 100644 --- a/hibernate-core/src/test/bundles/templates/war/WEB-INF/classes/META-INF/persistence.xml +++ b/hibernate-core/src/test/bundles/templates/war/WEB-INF/classes/META-INF/persistence.xml @@ -19,6 +19,7 @@ + diff --git a/hibernate-core/src/test/java/org/hibernate/cache/spi/ReadWriteCacheTest.java b/hibernate-core/src/test/java/org/hibernate/cache/spi/ReadWriteCacheTest.java index 0eff288506fa..a8991139c42e 100644 --- a/hibernate-core/src/test/java/org/hibernate/cache/spi/ReadWriteCacheTest.java +++ b/hibernate-core/src/test/java/org/hibernate/cache/spi/ReadWriteCacheTest.java @@ -20,6 +20,8 @@ import org.hibernate.cfg.Configuration; import org.hibernate.dialect.CockroachDB192Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; + import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -63,6 +65,7 @@ public void rebuildSessionFactory() { @Test @SkipForDialect(value = CockroachDB192Dialect.class, comment = "CockroachDB uses SERIALIZABLE isolation, and does not support this") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase seems to block on acquiring a SHARE lock when a different TX upgraded a SHARE to EXCLUSIVE lock, maybe the upgrade caused a table lock?") public void testDelete() throws InterruptedException { bookId = 1L; @@ -140,6 +143,7 @@ public void testDeleteNativeQuery() throws InterruptedException { @Test @SkipForDialect(value = CockroachDB192Dialect.class, comment = "CockroachDB uses SERIALIZABLE isolation, and does not support this") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase seems to block on acquiring a SHARE lock when a different TX upgraded a SHARE to EXCLUSIVE lock, maybe the upgrade caused a table lock?") public void testUpdate() throws InterruptedException { bookId = 4L; diff --git a/hibernate-core/src/test/java/org/hibernate/connection/ConnectionCreatorTest.java b/hibernate-core/src/test/java/org/hibernate/connection/ConnectionCreatorTest.java index 953b1bc2582d..59af287e7552 100644 --- a/hibernate-core/src/test/java/org/hibernate/connection/ConnectionCreatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/connection/ConnectionCreatorTest.java @@ -56,6 +56,7 @@ public R getService(Class serviceRole) { "jdbc:h2:mem:test-bad-urls;nosuchparam=saywhat", new Properties(), false, + null, null ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralWithSingleQuoteTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralWithSingleQuoteTest.java index fdc1d4a1e974..77302e0e00a2 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralWithSingleQuoteTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralWithSingleQuoteTest.java @@ -17,6 +17,7 @@ import org.hibernate.dialect.CockroachDB192Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.SkipForDialect; @@ -67,7 +68,8 @@ public void literalProjectionTest() throws Exception { value = { @SkipForDialect(value = SQLServerDialect.class, comment = "SQLServer does not support literals in group by statement"), @SkipForDialect(value = PostgreSQL81Dialect.class, comment = "PostgreSQL does not support literals in group by statement"), - @SkipForDialect( value = CockroachDB192Dialect.class, comment = "CockroachDB does not support literals in group by statement") + @SkipForDialect( value = CockroachDB192Dialect.class, comment = "CockroachDB does not support literals in group by statement"), + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "Sybase does not support literals in group by statement") } ) public void testLiteralProjectionAndGroupBy() throws Exception { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/SelectCaseTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/SelectCaseTest.java index 91830e8c8682..a2872dd6aa66 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/SelectCaseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/selectcase/SelectCaseTest.java @@ -38,6 +38,7 @@ import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.RequiresDialect; @@ -48,6 +49,7 @@ @TestForIssue( jiraKey = "HHH-9731" ) @SkipForDialect(value = DB2Dialect.class, comment = "We would need casts in the case clauses. See HHH-12822.") @SkipForDialect(value = DerbyDialect.class, comment = "Derby requires either casted parameters or literals in the result arms of CASE expressions") +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase requires either casted parameters or literals in the result arms of CASE expressions") public class SelectCaseTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/StatementIsClosedAfterALockExceptionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/StatementIsClosedAfterALockExceptionTest.java index bd94f204dff3..9e23af765a04 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/StatementIsClosedAfterALockExceptionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/StatementIsClosedAfterALockExceptionTest.java @@ -34,7 +34,7 @@ /** * @author Andrea Boriero */ -@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) +@RequiresDialectFeature({DialectChecks.SupportsJdbcDriverProxying.class, DialectChecks.SupportsLockTimeouts.class}) public class StatementIsClosedAfterALockExceptionTest extends BaseEntityManagerFunctionalTestCase { private static final PreparedStatementSpyConnectionProvider CONNECTION_PROVIDER = new PreparedStatementSpyConnectionProvider( false, false ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/DateTimeParameterTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/DateTimeParameterTest.java index 343719430568..6229c9e32d9c 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/DateTimeParameterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/DateTimeParameterTest.java @@ -31,6 +31,7 @@ import javax.persistence.TemporalType; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.DerbyTenSevenDialect; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.spi.JdbcServices; @@ -40,6 +41,8 @@ import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.After; import org.junit.Before; @@ -50,15 +53,20 @@ /** * @author Steve Ebersole */ -public class DateTimeParameterTest extends BaseUnitTestCase { - HibernateEntityManagerFactory entityManagerFactory; +@RequiresDialect(DerbyDialect.class) +public class DateTimeParameterTest extends BaseCoreFunctionalTestCase { private static GregorianCalendar nowCal = new GregorianCalendar(); private static Date now = new Date( nowCal.getTime().getTime() ); + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{Message.class}; + } + @Test public void testBindingCalendarAsDate() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = sessionFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -76,7 +84,7 @@ public void testBindingCalendarAsDate() { @Test public void testBindingCalendarAsTime() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = sessionFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -94,50 +102,19 @@ public void testBindingCalendarAsTime() { @Before public void startUp() { - // create the EMF - entityManagerFactory = Bootstrap.getEntityManagerFactoryBuilder( - buildPersistenceUnitDescriptor(), - buildSettingsMap() - ).build().unwrap( HibernateEntityManagerFactory.class ); - // create the procedures - createTestData( entityManagerFactory ); - createProcedures( entityManagerFactory ); - } - - private PersistenceUnitDescriptor buildPersistenceUnitDescriptor() { - return new BaseEntityManagerFunctionalTestCase.TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ); - } - - @SuppressWarnings("unchecked") - private Map buildSettingsMap() { - Map settings = new HashMap(); - - settings.put( AvailableSettings.LOADED_CLASSES, Collections.singletonList( Message.class ) ); - - settings.put( org.hibernate.cfg.AvailableSettings.DIALECT, DerbyTenSevenDialect.class.getName() ); - settings.put( org.hibernate.cfg.AvailableSettings.DRIVER, org.apache.derby.jdbc.EmbeddedDriver.class.getName() ); - settings.put( org.hibernate.cfg.AvailableSettings.URL, "jdbc:derby:memory:hibernate-orm-testing;create=true" ); - settings.put( org.hibernate.cfg.AvailableSettings.USER, "" ); - - settings.put( org.hibernate.cfg.AvailableSettings.HBM2DDL_AUTO, "create-drop" ); - return settings; + createTestData( sessionFactory() ); + createProcedures( sessionFactory() ); } @After public void tearDown() { - if ( entityManagerFactory == null ) { - return; - } - - deleteTestData( entityManagerFactory ); - dropProcedures( entityManagerFactory ); - entityManagerFactory.close(); + deleteTestData( sessionFactory() ); + dropProcedures( sessionFactory() ); } - private void createProcedures(HibernateEntityManagerFactory emf) { - final SessionFactoryImplementor sf = emf.unwrap( SessionFactoryImplementor.class ); + private void createProcedures(SessionFactoryImplementor sf) { final JdbcConnectionAccess connectionAccess = sf.getServiceRegistry().getService( JdbcServices.class ).getBootstrapJdbcConnectionAccess(); final Connection conn; try { @@ -248,7 +225,7 @@ public static void retrieveTimestamp(Timestamp in, Timestamp[] out ) throws SQLE out[0] = in; } - private void createTestData(HibernateEntityManagerFactory entityManagerFactory) { + private void createTestData(SessionFactoryImplementor entityManagerFactory) { EntityManager em = entityManagerFactory.createEntityManager(); em.getTransaction().begin(); em.persist( new Message( 1, "test", now, now, now ) ); @@ -256,7 +233,7 @@ private void createTestData(HibernateEntityManagerFactory entityManagerFactory) em.close(); } - private void deleteTestData(HibernateEntityManagerFactory entityManagerFactory) { + private void deleteTestData(SessionFactoryImplementor entityManagerFactory) { EntityManager em = entityManagerFactory.createEntityManager(); em.getTransaction().begin(); em.createQuery( "delete from Message" ).executeUpdate(); @@ -264,8 +241,7 @@ private void deleteTestData(HibernateEntityManagerFactory entityManagerFactory) em.close(); } - private void dropProcedures(HibernateEntityManagerFactory emf) { - final SessionFactoryImplementor sf = emf.unwrap( SessionFactoryImplementor.class ); + private void dropProcedures(SessionFactoryImplementor sf) { final JdbcConnectionAccess connectionAccess = sf.getServiceRegistry().getService( JdbcServices.class ).getBootstrapJdbcConnectionAccess(); final Connection conn; try { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/JpaTckUsageTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/JpaTckUsageTest.java index 0aa68a3856a7..67f6db6e7a63 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/JpaTckUsageTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/procedure/JpaTckUsageTest.java @@ -20,6 +20,7 @@ import javax.persistence.StoredProcedureQuery; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.DerbyTenSevenDialect; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.spi.JdbcServices; @@ -30,6 +31,7 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.After; import org.junit.Before; @@ -46,11 +48,17 @@ * * @author Steve Ebersole */ -public class JpaTckUsageTest extends BaseUnitTestCase { +@RequiresDialect(DerbyDialect.class) +public class JpaTckUsageTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{User.class}; + } @Test public void testMultipleGetUpdateCountCalls() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -68,7 +76,7 @@ public void testMultipleGetUpdateCountCalls() { @Test public void testBasicScalarResults() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -95,7 +103,7 @@ public void testBasicScalarResults() { @Test @FailureExpected( jiraKey = "HHH-8416", message = "JPA TCK challenge" ) public void testHasMoreResultsHandlingTckChallenge() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -113,7 +121,7 @@ public void testHasMoreResultsHandlingTckChallenge() { @Test public void testHasMoreResultsHandling() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -130,7 +138,7 @@ public void testHasMoreResultsHandling() { @Test public void testResultClassHandling() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -157,7 +165,7 @@ public void testResultClassHandling() { @Test public void testSettingInParamDefinedOnNamedStoredProcedureQuery() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { StoredProcedureQuery query = em.createNamedStoredProcedureQuery( "positional-param" ); @@ -171,7 +179,7 @@ public void testSettingInParamDefinedOnNamedStoredProcedureQuery() { @Test public void testSettingNonExistingParams() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -204,7 +212,7 @@ public void testSettingNonExistingParams() { @Test @FailureExpected( jiraKey = "HHH-8395", message = "Out of the frying pan into the fire: https://issues.apache.org/jira/browse/DERBY-211" ) public void testExecuteUpdate() { - EntityManager em = entityManagerFactory.createEntityManager(); + EntityManager em = entityManagerFactory().createEntityManager(); em.getTransaction().begin(); try { @@ -248,57 +256,21 @@ public void testParameterRegistration() { // "$$"; // public static final String deleteAllUsers_DROP_CMD = "DROP ALIAS deleteAllUsers IF EXISTS"; - HibernateEntityManagerFactory entityManagerFactory; - @Before public void startUp() { - // create the EMF - entityManagerFactory = Bootstrap.getEntityManagerFactoryBuilder( - buildPersistenceUnitDescriptor(), - buildSettingsMap() - ).build().unwrap( HibernateEntityManagerFactory.class ); - // create the procedures - createTestUser( entityManagerFactory ); - createProcedures( entityManagerFactory ); - } - - private PersistenceUnitDescriptor buildPersistenceUnitDescriptor() { - return new BaseEntityManagerFunctionalTestCase.TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ); - } - - @SuppressWarnings("unchecked") - private Map buildSettingsMap() { - Map settings = new HashMap(); - - settings.put( AvailableSettings.LOADED_CLASSES, Collections.singletonList( User.class ) ); - - settings.put( org.hibernate.cfg.AvailableSettings.DIALECT, DerbyTenSevenDialect.class ); - settings.put( org.hibernate.cfg.AvailableSettings.DRIVER, org.apache.derby.jdbc.EmbeddedDriver.class.getName() ); -// settings.put( org.hibernate.cfg.AvailableSettings.URL, "jdbc:derby:/tmp/hibernate-orm-testing;create=true" ); - settings.put( org.hibernate.cfg.AvailableSettings.URL, "jdbc:derby:memory:hibernate-orm-testing;create=true" ); - settings.put( org.hibernate.cfg.AvailableSettings.USER, "" ); - - settings.put( org.hibernate.cfg.AvailableSettings.HBM2DDL_AUTO, "create-drop" ); - settings.put( org.hibernate.cfg.AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true" ); - settings.put( org.hibernate.cfg.AvailableSettings.DIALECT, DerbyTenSevenDialect.class.getName() ); - return settings; + createTestUser( entityManagerFactory() ); + createProcedures( entityManagerFactory() ); } @After public void tearDown() { - if ( entityManagerFactory == null ) { - return; - } - - deleteTestUser( entityManagerFactory ); - dropProcedures( entityManagerFactory ); - entityManagerFactory.close(); + deleteTestUser( entityManagerFactory() ); + dropProcedures( entityManagerFactory() ); } - private void createProcedures(HibernateEntityManagerFactory emf) { - final SessionFactoryImplementor sf = emf.unwrap( SessionFactoryImplementor.class ); + private void createProcedures(SessionFactoryImplementor sf) { final JdbcConnectionAccess connectionAccess = sf.getServiceRegistry().getService( JdbcServices.class ).getBootstrapJdbcConnectionAccess(); final Connection conn; try { @@ -395,7 +367,7 @@ public static void deleteAllUsers() throws SQLException { conn.close(); } - private void createTestUser(HibernateEntityManagerFactory entityManagerFactory) { + private void createTestUser(SessionFactoryImplementor entityManagerFactory) { EntityManager em = entityManagerFactory.createEntityManager(); em.getTransaction().begin(); @@ -404,7 +376,7 @@ private void createTestUser(HibernateEntityManagerFactory entityManagerFactory) em.close(); } - private void deleteTestUser(HibernateEntityManagerFactory entityManagerFactory) { + private void deleteTestUser(SessionFactoryImplementor entityManagerFactory) { EntityManager em = entityManagerFactory.createEntityManager(); em.getTransaction().begin(); em.createQuery( "delete from User" ).executeUpdate(); @@ -412,8 +384,7 @@ private void deleteTestUser(HibernateEntityManagerFactory entityManagerFactory) em.close(); } - private void dropProcedures(HibernateEntityManagerFactory emf) { - final SessionFactoryImplementor sf = emf.unwrap( SessionFactoryImplementor.class ); + private void dropProcedures(SessionFactoryImplementor sf) { final JdbcConnectionAccess connectionAccess = sf.getServiceRegistry().getService( JdbcServices.class ).getBootstrapJdbcConnectionAccess(); final Connection conn; try { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/QueryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/QueryTest.java index dc0090ac425f..77831491b4e5 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/QueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/QueryTest.java @@ -30,7 +30,6 @@ import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.PostgreSQL9Dialect; import org.hibernate.dialect.PostgresPlusDialect; -import org.hibernate.dialect.SybaseDialect; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.jpa.test.Distributor; @@ -136,7 +135,6 @@ public void testPagedQuery() throws Exception { } @Test - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to VARCHAR") public void testNullPositionalParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -167,7 +165,6 @@ public void testNullPositionalParameter() throws Exception { } @Test - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to VARCHAR") public void testNullPositionalParameterParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -215,7 +212,6 @@ public Class getParameterType() { } @Test - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to VARCHAR") public void testNullPositionalParameterParameterIncompatible() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -263,7 +259,6 @@ public Class getParameterType() { } @Test - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to VARCHAR") public void testNullNamedParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -294,7 +289,6 @@ public void testNullNamedParameter() throws Exception { } @Test - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to VARCHAR") public void testNullNamedParameterParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -341,7 +335,6 @@ public Class getParameterType() { } @Test - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to VARCHAR") public void testNullNamedParameterParameterIncompatible() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -392,7 +385,6 @@ public Class getParameterType() { @SkipForDialect(value = PostgreSQL9Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = CockroachDB192Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to INTEGER") public void testNativeQueryNullPositionalParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -430,7 +422,6 @@ public void testNativeQueryNullPositionalParameter() throws Exception { @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = CockroachDB192Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = Oracle8iDialect.class, comment = "ORA-00932: inconsistent datatypes: expected NUMBER got BINARY") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to INTEGER") public void testNativeQueryNullPositionalParameterParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -484,7 +475,6 @@ public Class getParameterType() { @SkipForDialect(value = PostgreSQL9Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = CockroachDB192Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to INTEGER") public void testNativeQueryNullNamedParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); @@ -522,7 +512,6 @@ public void testNativeQueryNullNamedParameter() throws Exception { @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = CockroachDB192Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = Oracle8iDialect.class, comment = "ORA-00932: inconsistent datatypes: expected NUMBER got BINARY") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to INTEGER") public void testNativeQueryNullNamedParameterParameter() throws Exception { EntityManager em = getOrCreateEntityManager(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitOverridesTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitOverridesTests.java index fa3da3d3caa8..eac0d966ba24 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitOverridesTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/jpa/PersistenceUnitOverridesTests.java @@ -373,6 +373,7 @@ public DataSource getNonJtaDataSource() { integrationSettings.put( AvailableSettings.JPA_JDBC_URL, ConnectionProviderBuilder.URL ); integrationSettings.put( AvailableSettings.JPA_JDBC_USER, ConnectionProviderBuilder.USER ); integrationSettings.put( AvailableSettings.JPA_JDBC_PASSWORD, ConnectionProviderBuilder.PASS ); + integrationSettings.put( "hibernate.connection.init_sql", "" ); final PersistenceProvider provider = new HibernatePersistenceProvider(); diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/NullPrecedenceTest.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/NullPrecedenceTest.java index 7e5178766687..5db04129b2f4 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/NullPrecedenceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/NullPrecedenceTest.java @@ -8,9 +8,11 @@ import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.query.criteria.HibernateCriteriaBuilder; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.Assert; import org.junit.Test; @@ -29,6 +31,7 @@ protected Class[] getAnnotatedClasses() { } @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "No support for null precedence on Sybase") public void testNullPrecedence() { doInJPA( this::entityManagerFactory, entityManager -> { entityManager.persist( new Foo( 1L, null ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpressionTest.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpressionTest.java index 0590ecdea877..6c94ccb85d9e 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpressionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpressionTest.java @@ -22,6 +22,7 @@ import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.SkipForDialect; @@ -67,6 +68,7 @@ public void testCaseClause() { @Test @SkipForDialect(value = DB2Dialect.class, comment = "We would need casts in the case clauses. See HHH-12822.") @SkipForDialect(value = DerbyDialect.class, comment = "Derby requires either casted parameters or literals in the result arms of CASE expressions") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase requires either casted parameters or literals in the result arms of CASE expressions") public void testEqualClause() { doInHibernate( this::sessionFactory, session -> { CriteriaBuilder cb = session.getCriteriaBuilder(); @@ -93,6 +95,7 @@ public void testEqualClause() { @TestForIssue(jiraKey = "HHH-13167") @SkipForDialect(value = DB2Dialect.class, comment = "We would need casts in the case clauses. See HHH-12822.") @SkipForDialect(value = DerbyDialect.class, comment = "Derby requires either casted parameters or literals in the result arms of CASE expressions") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase requires either casted parameters or literals in the result arms of CASE expressions") public void testMissingElseClause() { doInHibernate( this::sessionFactory, session -> { Event event = new Event(); diff --git a/hibernate-core/src/test/java/org/hibernate/query/hhh14156/HHH14156Test.java b/hibernate-core/src/test/java/org/hibernate/query/hhh14156/HHH14156Test.java index bb943030b221..61dced02c7bc 100644 --- a/hibernate-core/src/test/java/org/hibernate/query/hhh14156/HHH14156Test.java +++ b/hibernate-core/src/test/java/org/hibernate/query/hhh14156/HHH14156Test.java @@ -9,6 +9,7 @@ import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; @@ -30,6 +31,7 @@ protected Class[] getAnnotatedClasses() { @Test @SkipForDialect(value = SQLServerDialect.class, comment = "SQLServer doesn't support tuple comparisons") @SkipForDialect(value = DerbyDialect.class, comment = "Derby doesn't support tuple comparisons") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase doesn't support tuple comparisons") public void testNoExceptionThrown() { inTransaction( session -> session.createQuery( diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/DatabaseCreationTimestampNullableColumnTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/DatabaseCreationTimestampNullableColumnTest.java index 51520daf2c6e..dc13c41a5a3f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/DatabaseCreationTimestampNullableColumnTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/DatabaseCreationTimestampNullableColumnTest.java @@ -18,11 +18,13 @@ import org.hibernate.annotations.Generated; import org.hibernate.annotations.NaturalId; import org.hibernate.annotations.ValueGenerationType; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.tuple.AnnotationValueGeneration; import org.hibernate.tuple.GenerationTiming; import org.hibernate.tuple.ValueGenerator; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.Assert; import org.junit.Test; @@ -33,6 +35,7 @@ * @author Vlad Mihalcea */ @TestForIssue( jiraKey = "HHH-11096" ) +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "current_timestamp requires parenthesis which we don't render") public class DatabaseCreationTimestampNullableColumnTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java index 526a7e062d81..a12f4a137f3a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java @@ -158,7 +158,6 @@ public void testQueryWithEmbeddedParameterAllNull() throws Exception { @Test @TestForIssue(jiraKey = "HHH-8172") - @SkipForDialect( value = SybaseDialect.class, comment = "skip for Sybase because (null = null) evaluates to true") @FailureExpected(jiraKey = "HHH-8172") public void testQueryWithEmbeddedParameterOneNull() throws Exception { Person person = new Person(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/join/JoinTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/join/JoinTest.java index 6293249f3336..a6a7598355e2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/join/JoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/join/JoinTest.java @@ -19,9 +19,11 @@ import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl; import org.hibernate.criterion.Restrictions; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.mapping.Join; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.junit.Test; @@ -152,6 +154,7 @@ public void testReferenceColumnWithBacktics() throws Exception { } @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void testUniqueConstaintOnSecondaryTable() throws Exception { Cat cat = new Cat(); cat.setStoryPart2( "My long story" ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/LobTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/LobTest.java index 9225d3fe9b8f..7b4a8fc29ff3 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/LobTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/LobTest.java @@ -9,13 +9,17 @@ package org.hibernate.test.annotations.lob; +import org.hibernate.dialect.SybaseASE15Dialect; + import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; /** * @author Emmanuel Bernard */ @RequiresDialectFeature(DialectChecks.SupportsExpectedLobUsagePattern.class) +@SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement binary stream handling") public class LobTest extends AbstractLobTest { @Override protected Class getBookClass() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/VersionedLobTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/VersionedLobTest.java index 82cef752bf1d..596cfbf84409 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/VersionedLobTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/VersionedLobTest.java @@ -10,9 +10,12 @@ import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.dialect.SybaseASE15Dialect; + import org.hibernate.testing.DialectChecks; import org.hibernate.testing.FailureExpected; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import static org.junit.Assert.assertEquals; @@ -22,6 +25,7 @@ * @author Gail Badner */ @RequiresDialectFeature(DialectChecks.SupportsExpectedLobUsagePattern.class) +@SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement binary stream handling") public class VersionedLobTest extends AbstractLobTest { @Override protected Class getBookClass() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/locator/LobLocatorTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/locator/LobLocatorTest.java index c0549163aa3f..892b62d87b55 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/locator/LobLocatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/locator/LobLocatorTest.java @@ -14,8 +14,11 @@ import org.hibernate.Session; import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.type.descriptor.java.DataHelper; /** @@ -35,6 +38,7 @@ protected Class[] getAnnotatedClasses() { @Test @TestForIssue(jiraKey = "HHH-8193") @RequiresDialectFeature(DialectChecks.UsesInputStreamToInsertBlob.class) + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement binary stream handling") public void testStreamResetBeforeParameterBinding() throws SQLException { final Session session = openSession(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyTest.java index 0027981c70c4..0feead73c71b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyTest.java @@ -32,6 +32,7 @@ import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.mapping.Column; import org.hibernate.mapping.PersistentClass; @@ -39,6 +40,7 @@ import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.annotations.Customer; @@ -138,6 +140,7 @@ public void testListWithBagSemanticAndOrderBy() throws Exception { } @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void testUnidirectionalDefault() throws Exception { Session s; Transaction tx; diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java index 52870917a8ca..fca691cbfa32 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OrderByTest.java @@ -30,6 +30,7 @@ import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.SQLServer2008Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.graph.RootGraph; import org.hibernate.persister.collection.CollectionPersister; @@ -37,6 +38,7 @@ import org.hibernate.query.Query; import org.hibernate.sql.SimpleSelect; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Assert; @@ -49,6 +51,7 @@ */ public class OrderByTest extends BaseCoreFunctionalTestCase { @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Didn't check what's wrong, but probably null ordering") public void testOrderByOnIdClassProperties() throws Exception { Session s = openSession( ); s.getTransaction().begin(); @@ -419,6 +422,7 @@ public void testInverseIndexCascaded() { @Test @TestForIssue(jiraKey = "HHH-8794") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Didn't check what's wrong, but probably null ordering") public void testOrderByNoElement() { final Session s = openSession(); @@ -451,6 +455,7 @@ public void testOrderByNoElement() { @Test @TestForIssue( jiraKey = "HHH-9002" ) + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Didn't check what's wrong, but probably null ordering") public void testOrderByOneToManyWithJoinTable() { A a = new A(); a.setName( "a" ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/hhh9798/OneToOneJoinTableTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/hhh9798/OneToOneJoinTableTest.java index cdef9869ce66..3311df899279 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/hhh9798/OneToOneJoinTableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/hhh9798/OneToOneJoinTableTest.java @@ -10,11 +10,13 @@ import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.id.IdentifierGenerationException; import org.junit.Test; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -25,6 +27,7 @@ public class OneToOneJoinTableTest extends BaseCoreFunctionalTestCase { @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void storeNonUniqueRelationship() throws Throwable { Session session = null; try { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java index a11b3e8e8c21..7d8542874e18 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java @@ -30,10 +30,10 @@ import org.hibernate.dialect.PostgreSQL9Dialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.PostgresPlusDialect; -import org.hibernate.dialect.SybaseDialect; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.stat.Statistics; import org.hibernate.type.StandardBasicTypes; + import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -121,7 +121,6 @@ public void testNativeQueryWithFormulaAttributeWithoutAlias() { @SkipForDialect(value = PostgreSQL9Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = CockroachDB192Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to BIGINT") public void testQueryWithNullParameter(){ Chaos c0 = new Chaos(); @@ -163,7 +162,6 @@ public void testQueryWithNullParameter(){ @Test @TestForIssue( jiraKey = "HHH-10161") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") public void testQueryWithNullParameterTyped(){ Chaos c0 = new Chaos(); c0.setId( 0L ); @@ -208,7 +206,6 @@ public void testQueryWithNullParameterTyped(){ @SkipForDialect(value = PostgreSQL9Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") @SkipForDialect(value = CockroachDB192Dialect.class, jiraKey = "HHH-10312", comment = "Cannot convert untyped null (assumed to be bytea type) to bigint") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") @SkipForDialect(value = DerbyDialect.class, comment = "Cannot convert untyped null (assumed to be VARBINARY type) to BIGINT") public void testNativeQueryWithNullParameter(){ Chaos c0 = new Chaos(); @@ -250,7 +247,6 @@ public void testNativeQueryWithNullParameter(){ @Test @TestForIssue( jiraKey = "HHH-10161") - @SkipForDialect(value = SybaseDialect.class, comment = "Null == null on Sybase") public void testNativeQueryWithNullParameterTyped(){ Chaos c0 = new Chaos(); c0.setId( 0L ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintThrowsConstraintViolationExceptionTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintThrowsConstraintViolationExceptionTest.java index 7393215cdcba..2351b093e8be 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintThrowsConstraintViolationExceptionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/uniqueconstraint/UniqueConstraintThrowsConstraintViolationExceptionTest.java @@ -17,8 +17,10 @@ import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.exception.ConstraintViolationException; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -39,6 +41,7 @@ protected Class[] getAnnotatedClasses() { } @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void testUniqueConstraintWithEmptyColumnName() { doInHibernate( this::sessionFactory, session -> { Customer customer1 = new Customer(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java index b623df317629..b5ae33c2592e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java @@ -57,6 +57,7 @@ import org.hibernate.dialect.MariaDB53Dialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.SQLServer2012Dialect; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -86,6 +87,7 @@ @RunWith(CustomParameterized.class) @TestForIssue(jiraKey = { "HHH-14921", "HHH-14922" }) +@SkipForDialect(value = SybaseDialect.class, comment = "Sybase doesn't support sequences") @SkipForDialect(value = MySQLDialect.class, comment = "MySQL doesn't support sequences") @SkipForDialect(value = MariaDB53Dialect.class, strictMatching = true, comment = "MariaDB < 10.3 doesn't support sequences") diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingByEnhancerSetterTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingByEnhancerSetterTest.java index b4114a59060d..a569cb1c5677 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingByEnhancerSetterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/LazyLoadingByEnhancerSetterTest.java @@ -8,7 +8,10 @@ import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; +import org.hibernate.dialect.SybaseASE15Dialect; + import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -38,6 +41,7 @@ @TestForIssue( jiraKey = "HHH-10747" ) @RunWith( BytecodeEnhancerRunner.class ) +@SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement binary stream handling") public class LazyLoadingByEnhancerSetterTest extends BaseCoreFunctionalTestCase { private Item item, mergedItem; diff --git a/hibernate-core/src/test/java/org/hibernate/test/converter/lob/ConverterAndLobTest.java b/hibernate-core/src/test/java/org/hibernate/test/converter/lob/ConverterAndLobTest.java index 3382aaa8ef7b..b3c97e7471ed 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/converter/lob/ConverterAndLobTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/converter/lob/ConverterAndLobTest.java @@ -10,7 +10,9 @@ import org.hibernate.Session; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.SybaseASE15Dialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.junit.Test; @@ -25,6 +27,7 @@ public class ConverterAndLobTest extends BaseNonConfigCoreFunctionalTestCase { @Test @TestForIssue( jiraKey = "HHH-9615" ) + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement binary stream handling") @SuppressWarnings("unchecked") public void basicTest() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/criterion/NationalizedIgnoreCaseTest.java b/hibernate-core/src/test/java/org/hibernate/test/criterion/NationalizedIgnoreCaseTest.java index 7f10541705be..6451c043ea73 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/criterion/NationalizedIgnoreCaseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/criterion/NationalizedIgnoreCaseTest.java @@ -17,6 +17,7 @@ import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; @@ -33,6 +34,7 @@ @SkipForDialect(value = DB2Dialect.class, comment = "DB2 jdbc driver doesn't support setNString") @SkipForDialect(value = DerbyDialect.class, comment = "Derby jdbc driver doesn't support setNString") @SkipForDialect(value = PostgreSQL81Dialect.class, comment = "PostgreSQL jdbc driver doesn't support setNString") +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "jTDS jdbc driver doesn't support setNString") public class NationalizedIgnoreCaseTest extends BaseCoreFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/exception/SQLExceptionConversionTest.java b/hibernate-core/src/test/java/org/hibernate/test/exception/SQLExceptionConversionTest.java index 9aca51c95579..ee5ba59bb0e8 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/exception/SQLExceptionConversionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/exception/SQLExceptionConversionTest.java @@ -16,6 +16,7 @@ import org.hibernate.Session; import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.MySQLMyISAMDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.ResultSetReturn; import org.hibernate.engine.jdbc.spi.StatementPreparer; @@ -44,6 +45,7 @@ public String[] getMappings() { value = { MySQLMyISAMDialect.class, AbstractHANADialect.class }, comment = "MySQL (MyISAM) / Hana do not support FK violation checking" ) + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void testIntegrityViolation() throws Exception { final Session session = openSession(); session.beginTransaction(); @@ -110,6 +112,7 @@ public void execute(Connection connection) throws SQLException { @Test @TestForIssue(jiraKey = "HHH-7357") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void testNotNullConstraint() { final Session session = openSession(); session.beginTransaction(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/foreignkeys/disabled/InheritanceManyToManyForeignKeyTest.java b/hibernate-core/src/test/java/org/hibernate/test/foreignkeys/disabled/InheritanceManyToManyForeignKeyTest.java index 7da4197f584f..0a76fc0b93dd 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/foreignkeys/disabled/InheritanceManyToManyForeignKeyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/foreignkeys/disabled/InheritanceManyToManyForeignKeyTest.java @@ -21,7 +21,9 @@ import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.dialect.SybaseDialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.junit.Test; @@ -44,6 +46,7 @@ protected Class[] getAnnotatedClasses() { } @Test + @SkipForDialect(value = SybaseDialect.class, comment = "Only dates between January 1, 1753 and December 31, 9999 are accepted.") public void testForeignKeyNameUnicity() { Session session = openSession(); Transaction transaction = session.beginTransaction(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/generated/MSSQLGeneratedPropertyEntity.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/generated/MSSQLGeneratedPropertyEntity.hbm.xml index c1243813818f..3fa1797c5cc8 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/generated/MSSQLGeneratedPropertyEntity.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/generated/MSSQLGeneratedPropertyEntity.hbm.xml @@ -18,7 +18,7 @@ - + diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/IndicesTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/IndicesTest.java index ddeae05664ee..7720cd356889 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/IndicesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/IndicesTest.java @@ -96,6 +96,7 @@ public Project(Integer id) { } @Entity(name = "Role") + @Table(name = "proj_role") public static class Role { @Id diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/size/filter/WhereAnnotatedOneToManySizeTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/size/filter/WhereAnnotatedOneToManySizeTest.java index a3ec5d5a3587..b9a2377a1965 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/size/filter/WhereAnnotatedOneToManySizeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/size/filter/WhereAnnotatedOneToManySizeTest.java @@ -12,6 +12,7 @@ import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.testing.SkipForDialect; @@ -91,6 +92,7 @@ public void after() { @Test @SkipForDialect(value = DB2Dialect.class, comment = "DB2 does not support correlated subqueries in the ORDER BY clause") @SkipForDialect(value = AbstractHANADialect.class, comment = "HANA db does not support correlated subqueries in the ORDER BY clause") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase does not support correlated subqueries in the ORDER BY clause") public void orderBy_sizeOf() { inSession( session -> { QueryImplementor query = session.createQuery( @@ -104,6 +106,7 @@ public void orderBy_sizeOf() { @Test @SkipForDialect(value = DB2Dialect.class, comment = "DB2 does not support correlated subqueries in the ORDER BY clause") @SkipForDialect(value = AbstractHANADialect.class, comment = "HANA db does not support correlated subqueries in the ORDER BY clause") + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Sybase does not support correlated subqueries in the ORDER BY clause") public void orderBy_dotSize() { inSession( session -> { QueryImplementor query = session.createQuery( diff --git a/hibernate-core/src/test/java/org/hibernate/test/limit/Oracle12LimitTest.java b/hibernate-core/src/test/java/org/hibernate/test/limit/Oracle12LimitTest.java index 27f167380a9a..6d67b5619952 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/limit/Oracle12LimitTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/limit/Oracle12LimitTest.java @@ -9,6 +9,7 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; +import javax.persistence.Table; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -64,8 +65,9 @@ public void testLimit() { ); } - @Entity - public class Person { + @Entity(name = "Person") + @Table(name = "Person") + public static class Person { @Id private Long id; @@ -76,8 +78,9 @@ public class Person { private String name; } - @Entity - public class UserFunctionalArea { + @Entity(name = "UserFunctionalArea") + @Table(name = "UserFunctionalArea") + public static class UserFunctionalArea { @Id @Column(name = "USER_KEY") private Integer id; diff --git a/hibernate-core/src/test/java/org/hibernate/test/lob/BlobLocatorTest.java b/hibernate-core/src/test/java/org/hibernate/test/lob/BlobLocatorTest.java index 7242d1ead7a4..d5b98f76c456 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/lob/BlobLocatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/lob/BlobLocatorTest.java @@ -12,6 +12,7 @@ import org.hibernate.Hibernate; import org.hibernate.LockOptions; import org.hibernate.Session; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.dialect.TeradataDialect; import org.hibernate.testing.DialectChecks; @@ -45,6 +46,7 @@ public String[] getMappings() { jiraKey = "HHH-6637", comment = "Teradata requires locator to be used in same session where it was created/retrieved" ) + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "Sybase doesn't support empty blobs") public void testBoundedBlobLocatorAccess() throws Throwable { byte[] original = buildByteArray( BLOB_SIZE, true ); byte[] changed = buildByteArray( BLOB_SIZE, false ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/lob/ClobLocatorTest.java b/hibernate-core/src/test/java/org/hibernate/test/lob/ClobLocatorTest.java index 1aa1df12ec42..428161ffde6f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/lob/ClobLocatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/lob/ClobLocatorTest.java @@ -11,6 +11,7 @@ import org.hibernate.LockOptions; import org.hibernate.Session; import org.hibernate.dialect.SybaseASE157Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.dialect.TeradataDialect; import org.hibernate.type.descriptor.java.DataHelper; @@ -47,6 +48,7 @@ public String[] getMappings() { jiraKey = "HHH-6637", comment = "Teradata requires locator to be used in same session where it was created/retrieved" ) + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement character stream handling") public void testBoundedClobLocatorAccess() throws Throwable { String original = buildString( CLOB_SIZE, 'x' ); String changed = buildString( CLOB_SIZE, 'y' ); @@ -131,6 +133,7 @@ public void testBoundedClobLocatorAccess() throws Throwable { value = DialectChecks.SupportsUnboundedLobLocatorMaterializationCheck.class, comment = "database/driver does not support materializing a LOB locator outside the owning transaction" ) + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement character stream handling") public void testUnboundedClobLocatorAccess() throws Throwable { // Note: unbounded mutation of the underlying lob data is completely // unsupported; most databases would not allow such a construct anyway. diff --git a/hibernate-core/src/test/java/org/hibernate/test/lob/LongByteArrayTest.java b/hibernate-core/src/test/java/org/hibernate/test/lob/LongByteArrayTest.java index f35708f864e3..138c448f42fe 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/lob/LongByteArrayTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/lob/LongByteArrayTest.java @@ -9,7 +9,9 @@ import java.util.Arrays; import org.hibernate.Session; +import org.hibernate.dialect.SybaseASE15Dialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Assert; import org.junit.Test; @@ -26,6 +28,7 @@ public abstract class LongByteArrayTest extends BaseCoreFunctionalTestCase { private static final int ARRAY_SIZE = 10000; @Test + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "Sybase doesn't support empty blobs") public void testBoundedLongByteArrayAccess() { byte[] original = buildRecursively( ARRAY_SIZE, true ); byte[] changed = buildRecursively( ARRAY_SIZE, false ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/lob/MaterializedClobTest.java b/hibernate-core/src/test/java/org/hibernate/test/lob/MaterializedClobTest.java index a2670a2fbc3d..ab39d463abe0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/lob/MaterializedClobTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/lob/MaterializedClobTest.java @@ -6,8 +6,11 @@ */ package org.hibernate.test.lob; +import org.hibernate.dialect.SybaseASE15Dialect; + import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; /** * Tests eager materialization and mutation of data mapped by @@ -16,6 +19,7 @@ * @author Gail Badner */ @RequiresDialectFeature( DialectChecks.SupportsExpectedLobUsagePattern.class ) +@SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement character stream handling") public class MaterializedClobTest extends LongStringTest { public String[] getMappings() { return new String[] { "lob/MaterializedClobMappings.hbm.xml" }; diff --git a/hibernate-core/src/test/java/org/hibernate/test/locking/paging/PagingAndLockingTest.java b/hibernate-core/src/test/java/org/hibernate/test/locking/paging/PagingAndLockingTest.java index d29bf5a3f311..3c55805fe244 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/locking/paging/PagingAndLockingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/locking/paging/PagingAndLockingTest.java @@ -13,7 +13,9 @@ import org.hibernate.Query; import org.hibernate.SQLQuery; import org.hibernate.Session; +import org.hibernate.dialect.SybaseASE15Dialect; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.After; @@ -28,6 +30,7 @@ * @author Steve Ebersole */ @TestForIssue( jiraKey = "HHH-1168" ) +@SkipForDialect( value = SybaseASE15Dialect.class, comment = "Didn't dig into it too deeply, but I think it requires follow on locking") public class PagingAndLockingTest extends BaseCoreFunctionalTestCase { @Override protected Class[] getAnnotatedClasses() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/manytomanyassociationclass/surrogateid/generated/ManyToManyAssociationClassGeneratedIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/manytomanyassociationclass/surrogateid/generated/ManyToManyAssociationClassGeneratedIdTest.java index c3ab40350f69..96d1e3c7721d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/manytomanyassociationclass/surrogateid/generated/ManyToManyAssociationClassGeneratedIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/manytomanyassociationclass/surrogateid/generated/ManyToManyAssociationClassGeneratedIdTest.java @@ -10,8 +10,10 @@ import java.util.HashSet; import org.hibernate.Session; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.exception.ConstraintViolationException; +import org.hibernate.testing.SkipForDialect; import org.hibernate.test.manytomanyassociationclass.AbstractManyToManyAssociationClassTest; import org.hibernate.test.manytomanyassociationclass.Membership; import org.junit.Test; @@ -24,6 +26,7 @@ * * @author Gail Badner */ +@SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public class ManyToManyAssociationClassGeneratedIdTest extends AbstractManyToManyAssociationClassTest { @Override public String[] getMappings() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/mapping/usertypes/UserTypeMappingTest.java b/hibernate-core/src/test/java/org/hibernate/test/mapping/usertypes/UserTypeMappingTest.java index bae30b67ddf9..3a871fbfd139 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/mapping/usertypes/UserTypeMappingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/mapping/usertypes/UserTypeMappingTest.java @@ -34,13 +34,6 @@ public class UserTypeMappingTest extends BaseUnitTestCase{ @Before public void setup(){ cfg=new Configuration(); - Properties p = new Properties(); - p.put( Environment.DIALECT, "org.hibernate.dialect.HSQLDialect" ); - p.put( "hibernate.connection.driver_class", "org.h2.Driver" ); - p.put( "hibernate.connection.url", "jdbc:h2:mem:" ); - p.put( "hibernate.connection.username", "sa" ); - p.put( "hibernate.connection.password", "" ); - cfg.setProperties(p); serviceRegistry = ServiceRegistryBuilder.buildServiceRegistry( cfg.getProperties() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/ops/CreateTest.java b/hibernate-core/src/test/java/org/hibernate/test/ops/CreateTest.java index b992eaae5122..99076be00dcc 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/ops/CreateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/ops/CreateTest.java @@ -14,6 +14,7 @@ import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.dialect.AbstractHANADialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.testing.DialectChecks; @@ -120,6 +121,7 @@ public void testCreateTreeWithGeneratedId() { } @Test + @SkipForDialect(value = SybaseASE15Dialect.class, comment = "Missing constraint violation extractor") public void testCreateException() { Session s = openSession(); Transaction tx = s.beginTransaction(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseAndQuotedNameTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseAndQuotedNameTest.java index a41cf2003609..d03593f57b40 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseAndQuotedNameTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseAndQuotedNameTest.java @@ -22,14 +22,17 @@ import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.hibernate.tool.hbm2ddl.SchemaUpdate; import org.hibernate.tool.hbm2ddl.SchemaValidator; import org.hibernate.tool.schema.JdbcMetadaAccessStrategy; import org.hibernate.tool.schema.TargetType; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.After; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -81,6 +84,10 @@ public void setUp() throws IOException { @After public void tearDown() { + // The jTDS driver doesn't support schemas + if ( metadata.getDatabase().getDialect() instanceof SybaseASE15Dialect ) { + return; + } new SchemaExport().setHaltOnError( true ) .setFormat( false ) .drop( EnumSet.of( TargetType.DATABASE ), metadata ); @@ -88,8 +95,13 @@ public void tearDown() { } @Test +// @SkipForDialect(value = SybaseASE15Dialect.class, comment = "The jTDS driver doesn't support schemas") public void testSchemaUpdateDoesNotTryToRecreateExistingTables() throws Exception { + // The jTDS driver doesn't support schemas + if ( metadata.getDatabase().getDialect() instanceof SybaseASE15Dialect ) { + return; + } createSchema(); new SchemaUpdate().setHaltOnError( true ) diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseTest.java index 3d3eedf77710..9b30637fa54f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseTest.java @@ -21,14 +21,17 @@ import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.hibernate.tool.hbm2ddl.SchemaUpdate; import org.hibernate.tool.hbm2ddl.SchemaValidator; import org.hibernate.tool.schema.JdbcMetadaAccessStrategy; import org.hibernate.tool.schema.TargetType; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.After; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -84,6 +87,10 @@ public void setUp() throws IOException { @After public void tearDown() { + // The jTDS driver doesn't support schemas + if ( metadata.getDatabase().getDialect() instanceof SybaseASE15Dialect ) { + return; + } new SchemaExport().setHaltOnError( true ) .setFormat( false ) .drop( EnumSet.of( TargetType.DATABASE ), metadata ); @@ -91,8 +98,13 @@ public void tearDown() { } @Test +// @SkipForDialect(value = SybaseASE15Dialect.class, comment = "The jTDS driver doesn't support schemas") public void testSchemaUpdateDoesNotTryToRecreateExistingTables() throws Exception { + // The jTDS driver doesn't support schemas + if ( metadata.getDatabase().getDialect() instanceof SybaseASE15Dialect ) { + return; + } createSchema(); new SchemaUpdate().setHaltOnError( true ) diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/Mappings.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/Mappings.hbm.xml index fd0aca780f26..a2b798274a33 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/Mappings.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/Mappings.hbm.xml @@ -70,11 +70,11 @@ DELETE FROM EMPLOYMENT WHERE EMPID=? - - + + - + INSERT INTO TEXTHOLDER @@ -85,11 +85,11 @@ DELETE FROM TEXTHOLDER WHERE ID=? - - + + - + INSERT INTO IMAGEHOLDER diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/SybaseCustomSQLTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/SybaseCustomSQLTest.java index 1e11fc84e53e..5949a9ae6cab 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/SybaseCustomSQLTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/custom/sybase/SybaseCustomSQLTest.java @@ -12,6 +12,7 @@ import org.hibernate.dialect.SybaseDialect; import org.hibernate.test.sql.hand.custom.CustomStoredProcTestSupport; import org.hibernate.testing.RequiresDialect; +import org.junit.Ignore; /** * Custom SQL tests for Sybase dialects @@ -19,6 +20,7 @@ * @author Gavin King */ @RequiresDialect( { SybaseDialect.class, SybaseASE15Dialect.class, Sybase11Dialect.class, SybaseAnywhereDialect.class }) +@Ignore("The jTDS driver doesn't detect callable prepared statements and can't unwrap from a PreparedStatement to a CallableStatement") public class SybaseCustomSQLTest extends CustomStoredProcTestSupport { public String[] getMappings() { return new String[] { "sql/hand/custom/sybase/Mappings.hbm.xml" }; diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java index fdf460e33278..1cb6100f96bf 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java @@ -10,6 +10,9 @@ import java.io.Serializable; import java.math.BigDecimal; import java.math.BigInteger; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.SQLException; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; @@ -846,8 +849,21 @@ public void testTextTypeInSQLQuery() { s = openSession(); t = s.beginTransaction(); - String descriptionRead = ( String ) s.createSQLQuery( getDescriptionsSQL() ) + Object result = s.createSQLQuery( getDescriptionsSQL() ) .uniqueResult(); + String descriptionRead; + if ( result instanceof String ) { + descriptionRead = (String) result; + } + else { + Clob clob = (Clob) result; + try { + descriptionRead = clob.getSubString( 1L, (int) clob.length() ); + } + catch (SQLException e) { + throw new RuntimeException( e ); + } + } assertEquals( description, descriptionRead ); s.delete( holder ); t.commit(); @@ -859,7 +875,8 @@ public void testTextTypeInSQLQuery() { public void testImageTypeInSQLQuery() { Session s = openSession(); Transaction t = s.beginTransaction(); - byte[] photo = buildLongByteArray( 15000, true ); + // Make sure the last byte is non-zero as Sybase cuts that off + byte[] photo = buildLongByteArray( 14999, true ); ImageHolder holder = new ImageHolder( photo ); s.persist( holder ); t.commit(); @@ -867,8 +884,21 @@ public void testImageTypeInSQLQuery() { s = openSession(); t = s.beginTransaction(); - byte[] photoRead = ( byte[] ) s.createSQLQuery( getPhotosSQL() ) + Object result = s.createSQLQuery( getPhotosSQL() ) .uniqueResult(); + byte[] photoRead; + if ( result instanceof byte[] ) { + photoRead = (byte[]) result; + } + else { + Blob blob = (Blob) result; + try { + photoRead = blob.getBytes( 1L, (int) blob.length() ); + } + catch (SQLException e) { + throw new RuntimeException( e ); + } + } assertTrue( Arrays.equals( photo, photoRead ) ); s.delete( holder ); t.commit(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java index 1b91e48cc336..56412b9ce320 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java @@ -25,6 +25,7 @@ import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.dialect.SybaseDialect; import org.junit.runners.Parameterized; @@ -89,8 +90,12 @@ public static List data() { ) // => Also test DST start, just in case .add( 2018, 3, 25, 1, 0, 0, 0, ZONE_PARIS ) - .add( 2018, 3, 25, 2, 0, 0, 0, ZONE_PARIS ) - .add( 2018, 9, 30, 2, 0, 0, 0, ZONE_AUCKLAND ) + .skippedForDialects( + // No idea what Sybase is doing here exactly + dialect -> dialect instanceof SybaseASE15Dialect, + b -> b.add( 2018, 3, 25, 2, 0, 0, 0, ZONE_PARIS ) + .add( 2018, 9, 30, 2, 0, 0, 0, ZONE_AUCKLAND ) + ) .add( 2018, 9, 30, 3, 0, 0, 0, ZONE_AUCKLAND ) // => Also test dates around 1905-01-01, because the code behaves differently before and after 1905 .add( 1904, 12, 31, 22, 59, 59, 999_999_999, ZONE_PARIS ) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java index 092e268d471b..13841c2e6778 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LobUnfetchedPropertyTest.java @@ -29,8 +29,11 @@ import org.hibernate.Hibernate; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.dialect.SybaseASE15Dialect; + import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -40,6 +43,7 @@ @TestForIssue(jiraKey = "HHH-12555") @RequiresDialectFeature(DialectChecks.SupportsExpectedLobUsagePattern.class) @RunWith(BytecodeEnhancerRunner.class) +@SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement binary stream handling") public class LobUnfetchedPropertyTest extends BaseCoreFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java index 19dbb894577f..3e33ac46fac1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java @@ -26,6 +26,7 @@ import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; import org.hibernate.testing.SkipForDialect; @@ -72,7 +73,11 @@ public static List data() { .add( 1892, 1, 1, ZONE_OSLO ) .add( 1900, 1, 1, ZONE_PARIS ) .add( 1900, 1, 1, ZONE_AMSTERDAM ) - .add( 1600, 1, 1, ZONE_AMSTERDAM ) + ) + .skippedForDialects( + // No idea what Sybase is doing here exactly + dialect -> dialect instanceof SybaseASE15Dialect, + b -> b.add( 1600, 1, 1, ZONE_AMSTERDAM ) ) // HHH-13379: DST end (where Timestamp becomes ambiguous, see JDK-4312621) // It doesn't seem that any date at midnight can be affected by HHH-13379, but we add some tests just in case diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java index 6497d3165108..ede7d8aa2b22 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java @@ -48,14 +48,14 @@ public void test() { Event event = new Event(); event.id = 1L; event.timeValue = new Time( 1000 ); - event.timestampValue = new Timestamp( 45678 ); + event.timestampValue = new Timestamp( 45677 ); session.persist( event ); } ); doInHibernate( this::sessionFactory, session -> { Event event = session.find( Event.class, 1L ); assertEquals(1000, event.timeValue.getTime() % TimeUnit.DAYS.toMillis( 1 )); - assertEquals(45678, event.timestampValue.getTime() % TimeUnit.DAYS.toMillis( 1 )); + assertEquals(45677, event.timestampValue.getTime() % TimeUnit.DAYS.toMillis( 1 )); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/typedescriptor/ByteTest.java b/hibernate-core/src/test/java/org/hibernate/test/typedescriptor/ByteTest.java index a7351de8dfd7..ca5aec6b7f0c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/typedescriptor/ByteTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/typedescriptor/ByteTest.java @@ -11,6 +11,9 @@ import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.dialect.SybaseASE15Dialect; + +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -29,6 +32,7 @@ protected Class[] getAnnotatedClasses() { @Test @TestForIssue( jiraKey = "HHH-6533" ) + @SkipForDialect( value = SybaseASE15Dialect.class, comment = "Didn't check what's the problem, but I think the DDL type chosen for byte is unsigned") public void testByteDataPersistenceAndRetrieval() { Session session = openSession(); Transaction transaction = session.beginTransaction(); diff --git a/hibernate-core/src/test/resources/hibernate.properties b/hibernate-core/src/test/resources/hibernate.properties index de12583ef4c8..cb2fcfccb984 100644 --- a/hibernate-core/src/test/resources/hibernate.properties +++ b/hibernate-core/src/test/resources/hibernate.properties @@ -10,6 +10,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.connection.pool_size 5 diff --git a/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/domain/HolidayCalendar.java b/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/domain/HolidayCalendar.java index 7d5ecea3e641..5a9749400615 100644 --- a/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/domain/HolidayCalendar.java +++ b/hibernate-ehcache/src/test/java/org/hibernate/cache/ehcache/test/domain/HolidayCalendar.java @@ -19,10 +19,12 @@ public class HolidayCalendar { private Long id; + private String name; // Date -> String private Map holidays = new HashMap(); public HolidayCalendar init() { + name = "default"; DateFormat df = new SimpleDateFormat("yyyy.MM.dd"); try { holidays.clear(); @@ -35,6 +37,14 @@ public HolidayCalendar init() { return this; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public Map getHolidays() { return holidays; } diff --git a/hibernate-ehcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml b/hibernate-ehcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml index 9ba308b2196a..686621ddf2b5 100644 --- a/hibernate-ehcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml +++ b/hibernate-ehcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml @@ -16,6 +16,8 @@ + + diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/AbstractOneSessionTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/AbstractOneSessionTest.java index c3783600de42..f8c13215ebd4 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/AbstractOneSessionTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/AbstractOneSessionTest.java @@ -50,6 +50,8 @@ public void init() throws URISyntaxException { } config.setProperty( Environment.USE_NEW_ID_GENERATOR_MAPPINGS, "true" ); config.setProperty( EnversSettings.USE_REVISION_ENTITY_WITH_NATIVE_ID, "false" ); + // These tests always use H2, so we reset the init_sql config here + config.setProperty( "hibernate.connection.init_sql", "" ); addProperties( config ); this.initMappings(); diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/StringMapLobTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/StringMapLobTest.java index 347b9dfc9289..2e4f2f5c8c70 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/StringMapLobTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/collection/StringMapLobTest.java @@ -20,6 +20,7 @@ import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.envers.Audited; import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; import org.hibernate.envers.test.Priority; @@ -39,6 +40,7 @@ @SkipForDialect(Oracle8iDialect.class) @SkipForDialect(value = PostgreSQL81Dialect.class, jiraKey = "HHH-11477", comment = "@Lob field in HQL predicate fails with error about text = bigint") @SkipForDialect(value = AbstractHANADialect.class, comment = "HANA doesn't support comparing LOBs with the = operator") +@SkipForDialect(value = SybaseDialect.class, comment = "Sybase doesn't support comparing LOBs with the = operator") @SkipForDialect(value = DB2Dialect.class, comment = "DB2 jdbc driver doesn't support setNString") @SkipForDialect(value = DerbyDialect.class, comment = "Derby jdbc driver doesn't support setNString") public class StringMapLobTest extends BaseEnversJPAFunctionalTestCase { diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/data/Lobs.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/data/Lobs.java index afddaecdcb1f..76c41b96a46f 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/data/Lobs.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/data/Lobs.java @@ -12,10 +12,12 @@ import javax.persistence.EntityManager; import org.hibernate.dialect.PostgreSQL82Dialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; import org.hibernate.envers.test.Priority; import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.junit.Test; @@ -26,6 +28,7 @@ * @author Chris Cranford */ @RequiresDialectFeature(DialectChecks.SupportsExpectedLobUsagePattern.class) +@SkipForDialect( value = SybaseASE15Dialect.class, comment = "jTDS driver doesn't implement binary stream handling") public class Lobs extends BaseEnversJPAFunctionalTestCase { private Integer id1; private Integer id2; diff --git a/hibernate-envers/src/test/resources/hibernate.properties b/hibernate-envers/src/test/resources/hibernate.properties index f983fab1f46f..17fa883cd061 100644 --- a/hibernate-envers/src/test/resources/hibernate.properties +++ b/hibernate-envers/src/test/resources/hibernate.properties @@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.connection.pool_size 5 diff --git a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPConnectionProviderTest.java b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPConnectionProviderTest.java index de38961c2062..37e52d32f2e7 100644 --- a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPConnectionProviderTest.java +++ b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPConnectionProviderTest.java @@ -11,10 +11,12 @@ import java.util.ArrayList; import java.util.List; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.ConnectionProviderJdbcConnectionAccess; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.hikaricp.internal.HikariCPConnectionProvider; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -27,6 +29,7 @@ /** * @author Brett Meyer */ +@SkipForDialect(value = SybaseDialect.class, comment = "The jTDS driver doesn't implement Connection#isValid so this fails") public class HikariCPConnectionProviderTest extends BaseCoreFunctionalTestCase { @Test diff --git a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java index 491ceb6088f8..0fb2f0c16417 100644 --- a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java +++ b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java @@ -14,9 +14,11 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.testing.DialectChecks; import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.test.util.PreparedStatementSpyConnectionProvider; import org.junit.Test; @@ -32,6 +34,7 @@ * @author Vlad Mihalcea */ @RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) +@SkipForDialect(value = SybaseDialect.class, comment = "The jTDS driver doesn't implement Connection#isValid so this fails") public class HikariCPSkipAutoCommitTest extends BaseCoreFunctionalTestCase { private PreparedStatementSpyConnectionProvider connectionProvider = diff --git a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariTransactionIsolationConfigTest.java b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariTransactionIsolationConfigTest.java index 6893f9f90730..61df95790f8f 100644 --- a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariTransactionIsolationConfigTest.java +++ b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariTransactionIsolationConfigTest.java @@ -6,14 +6,17 @@ */ package org.hibernate.test.hikaricp; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.hikaricp.internal.HikariCPConnectionProvider; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.common.connections.BaseTransactionIsolationConfigTest; /** * @author Steve Ebersole */ +@SkipForDialect(value = SybaseDialect.class, comment = "The jTDS driver doesn't implement Connection#isValid so this fails") public class HikariTransactionIsolationConfigTest extends BaseTransactionIsolationConfigTest { @Override protected ConnectionProvider getConnectionProviderUnderTest() { diff --git a/hibernate-hikaricp/src/test/resources/hibernate.properties b/hibernate-hikaricp/src/test/resources/hibernate.properties index 506b085d54b5..a27b5a46fb9e 100644 --- a/hibernate-hikaricp/src/test/resources/hibernate.properties +++ b/hibernate-hikaricp/src/test/resources/hibernate.properties @@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.jdbc.batch_size 10 hibernate.connection.provider_class HikariCPConnectionProvider diff --git a/hibernate-jcache/src/test/java/org/hibernate/jcache/test/RefreshUpdatedDataTest.java b/hibernate-jcache/src/test/java/org/hibernate/jcache/test/RefreshUpdatedDataTest.java index e62902d51992..8940cfa3906e 100644 --- a/hibernate-jcache/src/test/java/org/hibernate/jcache/test/RefreshUpdatedDataTest.java +++ b/hibernate-jcache/src/test/java/org/hibernate/jcache/test/RefreshUpdatedDataTest.java @@ -24,6 +24,8 @@ import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.HSQLDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.service.ServiceRegistry; import org.hibernate.testing.SkipForDialect; @@ -89,6 +91,8 @@ public void releaseResources() { @Test @SkipForDialect(value = CockroachDB192Dialect.class, comment = "does not support nested transactions") @SkipForDialect(value = DerbyDialect.class, comment = "Derby does not support nested transactions") + @SkipForDialect(SybaseASE15Dialect.class) + @SkipForDialect(HSQLDialect.class) public void testUpdateAndFlushThenRefresh() { final String BEFORE = "before"; diff --git a/hibernate-jcache/src/test/java/org/hibernate/jcache/test/domain/HolidayCalendar.java b/hibernate-jcache/src/test/java/org/hibernate/jcache/test/domain/HolidayCalendar.java index c0e19f33fe4b..dcc7cfb1a7c0 100644 --- a/hibernate-jcache/src/test/java/org/hibernate/jcache/test/domain/HolidayCalendar.java +++ b/hibernate-jcache/src/test/java/org/hibernate/jcache/test/domain/HolidayCalendar.java @@ -19,10 +19,12 @@ public class HolidayCalendar { private Long id; + private String name; // Date -> String private Map holidays = new HashMap(); public HolidayCalendar init() { + name = "default"; DateFormat df = new SimpleDateFormat("yyyy.MM.dd"); try { holidays.clear(); @@ -35,6 +37,14 @@ public HolidayCalendar init() { return this; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public Map getHolidays() { return holidays; } diff --git a/hibernate-jcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml b/hibernate-jcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml index 47822c93222f..583c1317a9e2 100644 --- a/hibernate-jcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml +++ b/hibernate-jcache/src/test/resources/hibernate-config/domain/HolidayCalendar.hbm.xml @@ -16,6 +16,8 @@ + + diff --git a/hibernate-jcache/src/test/resources/hibernate.properties b/hibernate-jcache/src/test/resources/hibernate.properties index a643fcdfae23..c6fe2b13e598 100644 --- a/hibernate-jcache/src/test/resources/hibernate.properties +++ b/hibernate-jcache/src/test/resources/hibernate.properties @@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.connection.pool_size 5 diff --git a/hibernate-micrometer/src/test/resources/hibernate.properties b/hibernate-micrometer/src/test/resources/hibernate.properties index de12583ef4c8..cb2fcfccb984 100644 --- a/hibernate-micrometer/src/test/resources/hibernate.properties +++ b/hibernate-micrometer/src/test/resources/hibernate.properties @@ -10,6 +10,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.connection.pool_size 5 diff --git a/hibernate-proxool/src/test/resources/hibernate.properties b/hibernate-proxool/src/test/resources/hibernate.properties index 8343ae53a458..a216660df551 100644 --- a/hibernate-proxool/src/test/resources/hibernate.properties +++ b/hibernate-proxool/src/test/resources/hibernate.properties @@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.connection.pool_size 5 hibernate.jdbc.batch_size 10 diff --git a/hibernate-spatial/src/test/resources/hibernate.properties b/hibernate-spatial/src/test/resources/hibernate.properties index 6496030da17f..10a4341dc55e 100644 --- a/hibernate-spatial/src/test/resources/hibernate.properties +++ b/hibernate-spatial/src/test/resources/hibernate.properties @@ -12,6 +12,7 @@ hibernate.connection.driver_class=@jdbc.driver@ hibernate.connection.url=@jdbc.url@ hibernate.connection.username=@jdbc.user@ hibernate.connection.password=@jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ #hibernate.cache.region_prefix hibernate.test #hibernate.cache.region.factory_class org.hibernate.testing.cache.CachingRegionFactory # diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/env/ConnectionProviderBuilder.java b/hibernate-testing/src/main/java/org/hibernate/testing/env/ConnectionProviderBuilder.java index 8a793590168f..1dad95c83d10 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/env/ConnectionProviderBuilder.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/env/ConnectionProviderBuilder.java @@ -44,6 +44,7 @@ public static Properties getConnectionProviderProperties(String dbName) { props.put( Environment.URL, String.format( URL_FORMAT, dbName ) ); props.put( Environment.USER, USER ); props.put( Environment.PASS, PASS ); + props.put( "hibernate.connection.init_sql", "" ); return props; } @@ -53,6 +54,7 @@ public static Properties getJpaConnectionProviderProperties(String dbName) { props.put( Environment.JPA_JDBC_URL, String.format( URL_FORMAT, dbName ) ); props.put( Environment.JPA_JDBC_USER, USER ); props.put( Environment.JPA_JDBC_PASSWORD, PASS ); + props.put( "hibernate.connection.init_sql", "" ); return props; } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SharedDriverManagerConnectionProviderImpl.java b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SharedDriverManagerConnectionProviderImpl.java index 28ba052668db..143e6756a164 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SharedDriverManagerConnectionProviderImpl.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/SharedDriverManagerConnectionProviderImpl.java @@ -31,6 +31,7 @@ public static SharedDriverManagerConnectionProviderImpl getInstance() { } private Config config; + private Boolean supportsIsValid; @Override public void configure(Map configurationValues) { @@ -46,8 +47,23 @@ public void configure(Map configurationValues) { @Override public boolean isValid(Connection connection) throws SQLException { - // Wait at most 5 seconds to validate a connection is still valid - return connection.isValid( 5 ); + if ( supportsIsValid == Boolean.FALSE ) { + // Assume is valid if the driver doesn't support the check + return true; + } + Boolean supportsIsValid = Boolean.FALSE; + try { + // Wait at most 5 seconds to validate a connection is still valid + boolean valid = connection.isValid( 5 ); + supportsIsValid = Boolean.TRUE; + return valid; + } + catch (AbstractMethodError e) { + return true; + } + finally { + this.supportsIsValid = supportsIsValid; + } } @Override @@ -58,7 +74,6 @@ public void stop() { public void reset() { super.stop(); - config = null; } private static class Config { @@ -71,7 +86,7 @@ private static class Config { private final Properties connectionProps; private final Integer isolation; - public Config(Map configurationValues) { + public Config(Map configurationValues) { this.autoCommit = ConfigurationHelper.getBoolean( AvailableSettings.AUTOCOMMIT, configurationValues, false ); this.minSize = ConfigurationHelper.getInt( MIN_SIZE, configurationValues, 2 ); this.maxSize = ConfigurationHelper.getInt( AvailableSettings.POOL_SIZE, configurationValues, 20 ); diff --git a/hibernate-vibur/src/test/java/org/hibernate/test/vibur/ViburDBCPConnectionProviderTest.java b/hibernate-vibur/src/test/java/org/hibernate/test/vibur/ViburDBCPConnectionProviderTest.java index 8cb6b7354a26..00970ca0338c 100644 --- a/hibernate-vibur/src/test/java/org/hibernate/test/vibur/ViburDBCPConnectionProviderTest.java +++ b/hibernate-vibur/src/test/java/org/hibernate/test/vibur/ViburDBCPConnectionProviderTest.java @@ -73,16 +73,17 @@ public void setUpPoolAndDatabase(int poolMaxSize, int statementCacheMaxSize) { buildSessionFactory(); doInHibernate(this::sessionFactory, session -> { - addDbRecord(session, "CHRISTIAN", "GABLE"); - addDbRecord(session, "CHRISTIAN", "AKROYD"); - addDbRecord(session, "CHRISTIAN", "NEESON"); - addDbRecord(session, "CAMERON", "NEESON"); - addDbRecord(session, "RAY", "JOHANSSON"); + addDbRecord(session, 1L, "CHRISTIAN", "GABLE"); + addDbRecord(session, 2L, "CHRISTIAN", "AKROYD"); + addDbRecord(session, 3L, "CHRISTIAN", "NEESON"); + addDbRecord(session, 4L, "CAMERON", "NEESON"); + addDbRecord(session, 5L, "RAY", "JOHANSSON"); }); } - private static void addDbRecord(Session session, String firstName, String lastName) { + private static void addDbRecord(Session session, Long id, String firstName, String lastName) { Actor actor = new Actor(); + actor.setId( id ); actor.setFirstName(firstName); actor.setLastName(lastName); session.persist(actor); @@ -145,7 +146,6 @@ private static void executeAndVerifySelect(Session session) { @Entity(name="Actor") public static class Actor { @Id - @GeneratedValue private Long id; private String firstName; diff --git a/hibernate-vibur/src/test/resources/hibernate.properties b/hibernate-vibur/src/test/resources/hibernate.properties index 22936d7e924a..8953491b9c19 100644 --- a/hibernate-vibur/src/test/resources/hibernate.properties +++ b/hibernate-vibur/src/test/resources/hibernate.properties @@ -9,6 +9,7 @@ hibernate.connection.driver_class @jdbc.driver@ hibernate.connection.url @jdbc.url@ hibernate.connection.username @jdbc.user@ hibernate.connection.password @jdbc.pass@ +hibernate.connection.init_sql @connection.init_sql@ hibernate.jdbc.batch_size 10 hibernate.connection.provider_class ViburDBCPConnectionProvider From 3094185ded22d775a4be547835a62de7a361e9ad Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Mon, 25 Apr 2022 12:10:43 +0200 Subject: [PATCH 070/201] Ignore jakarta projects for javadoc aggregation --- documentation/documentation.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/documentation/documentation.gradle b/documentation/documentation.gradle index 15f00ab5230a..38a0dbbb8e42 100644 --- a/documentation/documentation.gradle +++ b/documentation/documentation.gradle @@ -16,6 +16,10 @@ ext { 'hibernate-ehcache', 'hibernate-java8', 'hibernate-integrationtest-java-modules', + 'hibernate-core-jakarta', + 'hibernate-envers-jakarta', + 'hibernate-testing-jakarta', + 'hibernate-jpamodelgen-jakarta', 'release' ] } From f1c46277e55c62b56839af88e88daf862e79bafd Mon Sep 17 00:00:00 2001 From: Ptits de Barbe Date: Mon, 28 Mar 2022 17:41:27 +0300 Subject: [PATCH 071/201] HHH-15142 Add test for issue --- .../CriteriaUpdateWithParametersTest.java | 41 ++-- .../ReuseCriteriaWithMixedParametersTest.java | 222 ++++++++++++++++++ 2 files changed, 248 insertions(+), 15 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/query/ReuseCriteriaWithMixedParametersTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/CriteriaUpdateWithParametersTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/CriteriaUpdateWithParametersTest.java index c1cda6c51ad6..10313f7d6439 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/CriteriaUpdateWithParametersTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/CriteriaUpdateWithParametersTest.java @@ -1,10 +1,5 @@ package org.hibernate.jpa.test.query; -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; -import org.hibernate.testing.orm.junit.Jpa; -import org.junit.jupiter.api.Test; - import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Query; @@ -14,15 +9,27 @@ import javax.persistence.criteria.Root; import javax.persistence.metamodel.EntityType; -@Jpa( - annotatedClasses = CriteriaUpdateWithParametersTest.Person.class -) -@TestForIssue( jiraKey = "HHH-15113") -public class CriteriaUpdateWithParametersTest { +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +@TestForIssue(jiraKey = "HHH-15113") +public class CriteriaUpdateWithParametersTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class + }; + } @Test - public void testCriteriaUpdate(EntityManagerFactoryScope scope) { - scope.inTransaction( + public void testCriteriaUpdate() { + doInJPA( + this::entityManagerFactory, entityManager -> { final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); final CriteriaUpdate criteriaUpdate = criteriaBuilder.createCriteriaUpdate( Person.class ); @@ -33,7 +40,10 @@ public void testCriteriaUpdate(EntityManagerFactoryScope scope) { final EntityType personEntityType = entityManager.getMetamodel().entity( Person.class ); - criteriaUpdate.set( root.get( personEntityType.getSingularAttribute( "age", Integer.class ) ), intValueParameter ); + criteriaUpdate.set( + root.get( personEntityType.getSingularAttribute( "age", Integer.class ) ), + intValueParameter + ); criteriaUpdate.where( criteriaBuilder.equal( root.get( personEntityType.getSingularAttribute( "name", String.class ) ), @@ -50,8 +60,9 @@ public void testCriteriaUpdate(EntityManagerFactoryScope scope) { } @Test - public void testCriteriaUpdate2(EntityManagerFactoryScope scope) { - scope.inTransaction( + public void testCriteriaUpdate2() { + doInJPA( + this::entityManagerFactory, entityManager -> { final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); final CriteriaUpdate criteriaUpdate = criteriaBuilder.createCriteriaUpdate( Person.class ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ReuseCriteriaWithMixedParametersTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ReuseCriteriaWithMixedParametersTest.java new file mode 100644 index 000000000000..43c95fc0fac5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/query/ReuseCriteriaWithMixedParametersTest.java @@ -0,0 +1,222 @@ +/* + * 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 . + */ +package org.hibernate.jpa.test.query; + +import java.time.Instant; +import java.util.Date; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Query; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.ParameterExpression; +import javax.persistence.criteria.Root; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.jpa.test.Wallet; +import org.hibernate.jpa.test.Wallet_; + +import org.hibernate.testing.TestForIssue; +import org.junit.After; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +@TestForIssue(jiraKey = "HHH-15142") +public class ReuseCriteriaWithMixedParametersTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Wallet.class, + Person.class + }; + } + + @After + public void tearDown() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "delete from Person" ).executeUpdate(); + } + ); + } + + @Test + public void cqReuse() { + doInJPA( this::entityManagerFactory, entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + final CriteriaQuery criteriaQuery = criteriaBuilder.createQuery( Wallet.class ); + final Root root = criteriaQuery.from( Wallet.class ); + + final ParameterExpression stringValueParameter = criteriaBuilder.parameter( String.class ); + + criteriaQuery.where( + criteriaBuilder.like( + root.get( Wallet_.model ), + stringValueParameter + ), + criteriaBuilder.lessThan( + root.get( Wallet_.marketEntrance ), + criteriaBuilder.literal( Date.from( Instant.EPOCH ) ) + ) + ); + + Query query = entityManager.createQuery( criteriaQuery ); + query.setParameter( stringValueParameter, "Z%" ); + + query.getResultList(); + + query = entityManager.createQuery( criteriaQuery ); + query.setParameter( stringValueParameter, "A%" ); + + query.getResultList(); + + } ); + } + + @Test + public void likeCqReuse() { + doInJPA( this::entityManagerFactory, entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + final CriteriaQuery criteriaQuery = criteriaBuilder.createQuery( Wallet.class ); + final Root root = criteriaQuery.from( Wallet.class ); + + final ParameterExpression stringValueParameter = criteriaBuilder.parameter( String.class ); + + criteriaQuery.where( + criteriaBuilder.like( + root.get( Wallet_.model ), + stringValueParameter, + '/' + ) + ); + + Query query = entityManager.createQuery( criteriaQuery ); + query.setParameter( stringValueParameter, "Z%" ); + + query.getResultList(); + + query = entityManager.createQuery( criteriaQuery ); + query.setParameter( stringValueParameter, "A%" ); + + query.getResultList(); + + } ); + } + + @Test + public void predicateReuse() { + doInJPA( this::entityManagerFactory, entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + final CriteriaQuery criteriaQuery = criteriaBuilder.createQuery( Wallet.class ); + final Root root = criteriaQuery.from( Wallet.class ); + + final ParameterExpression stringValueParameter = criteriaBuilder.parameter( String.class ); + final ParameterExpression dateValueParameter = criteriaBuilder.parameter( Date.class ); + + criteriaQuery.where( + criteriaBuilder.like( + root.get( Wallet_.model ), + stringValueParameter + ) + ); + + Query query = entityManager.createQuery( criteriaQuery ); + query.setParameter( stringValueParameter, "Z%" ); + + query.getResultList(); + + criteriaQuery.where( + criteriaBuilder.like( + root.get( Wallet_.model ), + stringValueParameter + ), + criteriaBuilder.lessThan( + root.get( Wallet_.marketEntrance ), + dateValueParameter + ) + ); + + query = entityManager.createQuery( criteriaQuery ); + query.setParameter( stringValueParameter, "A%" ); + query.setParameter( dateValueParameter, Date.from( Instant.EPOCH ) ); + + query.getResultList(); + } ); + } + + @Test + public void testLikePredicate() { + doInJPA( this::entityManagerFactory, entityManager -> { + + entityManager.persist( new Person( "Person 1" ) ); + entityManager.persist( new Person( "Person 2" ) ); + } + ); + + doInJPA( this::entityManagerFactory, entityManager -> { + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + final CriteriaQuery personQuery = cb.createQuery( Person.class ); + final Root root = personQuery.from( Person.class ); + final ParameterExpression pattern = cb.parameter( String.class ); + CriteriaQuery criteriaQuery = personQuery + .where( cb.like( + root.get( "name" ), + pattern, + cb.literal( '\\' ) + ) ); + for ( int i = 0; i < 2; i++ ) { + + final TypedQuery query = entityManager.createQuery( criteriaQuery ); + query.setParameter( pattern, "%_1" ); + final List result = query.getResultList(); + + assertEquals( 1, result.size() ); + } + } + ); + + } + + @Entity(name = "Person") + public static class Person { + @Id + @GeneratedValue + private Long id; + + private String name; + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + 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; + } + } + + +} From ee3129d45b2de8ce54ac4ef72d086e7c574b1f08 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 13 Apr 2022 11:12:54 +0200 Subject: [PATCH 072/201] HHH-15142 CriteriaQuery with Like predicate fails when repeated with java.lang.IllegalArgumentException: Parameter value [] did not match expected type [java.lang.String (n/a)] --- .../query/criteria/internal/compile/CriteriaCompiler.java | 5 ++--- .../internal/expression/ParameterExpressionImpl.java | 6 ++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java index b41adfa6377d..d4c685c9d543 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java @@ -94,7 +94,7 @@ public Stack getFunctionStack() { public ExplicitParameterInfo registerExplicitParameter(ParameterExpression criteriaQueryParameter) { ExplicitParameterInfo parameterInfo = explicitParameterInfoMap.get( criteriaQueryParameter ); if ( parameterInfo == null ) { - if ( StringHelper.isNotEmpty( criteriaQueryParameter.getName() ) ) { + if ( StringHelper.isNotEmpty( criteriaQueryParameter.getName() ) && !( (ParameterExpressionImpl) criteriaQueryParameter ).isNameGenerated() ) { parameterInfo = new ExplicitParameterInfo( criteriaQueryParameter.getName(), null, @@ -109,9 +109,8 @@ else if ( criteriaQueryParameter.getPosition() != null ) { ); } else { - final String name = generateParameterName(); parameterInfo = new ExplicitParameterInfo( - name, + generateParameterName(), null, criteriaQueryParameter.getJavaType() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java index 7875341724e7..4c565350cf07 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/ParameterExpressionImpl.java @@ -25,6 +25,7 @@ public class ParameterExpressionImpl implements ParameterExpression, Serializable { private String name; private final Integer position; + private boolean isNameGenerated; public ParameterExpressionImpl( CriteriaBuilderImpl criteriaBuilder, @@ -57,6 +58,10 @@ public String getName() { return name; } + public boolean isNameGenerated() { + return isNameGenerated; + } + @Override public Integer getPosition() { return position; @@ -76,6 +81,7 @@ public void registerParameters(ParameterRegistry registry) { public String render(RenderingContext renderingContext) { final ExplicitParameterInfo parameterInfo = renderingContext.registerExplicitParameter( this ); if ( name == null && position == null ) { + isNameGenerated = true; name = parameterInfo.getName(); } return parameterInfo.render(); From 73d07272fd239a448b65834cd5088cb71182c2bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 27 Apr 2022 11:28:00 +0200 Subject: [PATCH 073/201] HHH-15212 Fix SimpleAuxiliaryDatabaseObject ignoring the default catalog/schema set through configuration properties --- .../relational/AuxiliaryDatabaseObject.java | 8 +++-- .../SimpleAuxiliaryDatabaseObject.java | 31 ++++++++++++++++--- .../SqlStringGenerationContext.java | 16 ++++++++++ .../SqlStringGenerationContextImpl.java | 5 +++ 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/AuxiliaryDatabaseObject.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/AuxiliaryDatabaseObject.java index 60c54fcb334b..fe91498b11c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/AuxiliaryDatabaseObject.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/AuxiliaryDatabaseObject.java @@ -56,7 +56,9 @@ default String[] sqlCreateStrings(SqlStringGenerationContext context) { * @param dialect The dialect for which to generate the SQL creation strings * * @return the SQL strings for creating the database object. - * @deprecated Implement {@link #sqlCreateStrings(SqlStringGenerationContext)} instead. + * @deprecated Hibernate ORM may never call this method, + * and implementations cannot properly handle default catalogs/schemas. + * Call/implement {@link #sqlCreateStrings(SqlStringGenerationContext)} instead. */ @Deprecated default String[] sqlCreateStrings(Dialect dialect) { @@ -80,7 +82,9 @@ default String[] sqlDropStrings(SqlStringGenerationContext context) { * @param dialect The dialect for which to generate the SQL drop strings * * @return the SQL strings for dropping the database object. - * @deprecated Implement {@link #sqlDropStrings(SqlStringGenerationContext)} instead. + * @deprecated Hibernate ORM may never call this method, + * and implementations cannot properly handle default catalogs/schemas. + * Call/implement {@link #sqlDropStrings(SqlStringGenerationContext)} instead. */ @Deprecated default String[] sqlDropStrings(Dialect dialect) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java index df59ef7466f7..618931f1d04b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/SimpleAuxiliaryDatabaseObject.java @@ -9,6 +9,7 @@ import java.util.Set; import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; import org.hibernate.dialect.Dialect; import org.hibernate.internal.util.StringHelper; @@ -76,19 +77,37 @@ public SimpleAuxiliaryDatabaseObject( } @Override + @Deprecated public String[] sqlCreateStrings(Dialect dialect) { + // Implemented exclusively for backwards compatibility for callers other than Hibernate ORM. + // This is not called by Hibernate ORM and will not take into account + // default catalog/schema set through configuration properties. + return sqlCreateStrings( SqlStringGenerationContextImpl.forBackwardsCompatibility( dialect, null, null ) ); + } + + @Override + public String[] sqlCreateStrings(SqlStringGenerationContext context) { final String[] copy = new String[createStrings.length]; for ( int i = 0, max =createStrings.length; i * Note that the Identifiers returned from this helper already account for auto-quoting. + * + * @deprecated Use {@link #toIdentifier(String)} instead. */ + @Deprecated IdentifierHelper getIdentifierHelper(); + /** + * Generate an Identifier instance from its simple name as obtained from mapping + * information. + *

    + * Note that Identifiers returned from here may be implicitly quoted based on + * 'globally quoted identifiers' or based on reserved words. + * + * @param text The text form of a name as obtained from mapping information. + * + * @return The identifier form of the name. + */ + Identifier toIdentifier(String text); + /** * @return The default catalog, used for table/sequence names that do not explicitly mention a catalog. * May be {@code null}. diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java index 3cfec4057aec..e61331500201 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java @@ -149,6 +149,11 @@ public IdentifierHelper getIdentifierHelper() { return identifierHelper; } + @Override + public Identifier toIdentifier(String text) { + return identifierHelper != null ? identifierHelper.toIdentifier( text ) : Identifier.toIdentifier( text ); + } + @Override public Identifier getDefaultCatalog() { return defaultCatalog; From 863369c08e5ea2ac4847e852ab50fbcb273deae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 27 Apr 2022 10:52:46 +0200 Subject: [PATCH 074/201] HHH-15212 Test placeholders in auxiliary object SQL strings --- .../DefaultCatalogAndSchemaTest.java | 14 +++++++-- ...e-object-using-catalog-placeholder.hbm.xml | 30 +++++++++++++++++++ ...se-object-using-schema-placeholder.hbm.xml | 30 +++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/database-object-using-catalog-placeholder.hbm.xml create mode 100644 hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/database-object-using-schema-placeholder.hbm.xml diff --git a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java index b5ae33c2592e..9c7c2113a8eb 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java @@ -65,7 +65,6 @@ import org.hibernate.hql.spi.id.local.LocalTemporaryTableBulkIdStrategy; import org.hibernate.id.IdentifierGenerator; import org.hibernate.persister.entity.AbstractEntityPersister; -import org.hibernate.persister.entity.EntityPersister; import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.hibernate.tool.schema.TargetType; @@ -86,7 +85,7 @@ import org.junit.runners.Parameterized; @RunWith(CustomParameterized.class) -@TestForIssue(jiraKey = { "HHH-14921", "HHH-14922" }) +@TestForIssue(jiraKey = { "HHH-14921", "HHH-14922", "HHH-15212" }) @SkipForDialect(value = SybaseDialect.class, comment = "Sybase doesn't support sequences") @SkipForDialect(value = MySQLDialect.class, comment = "MySQL doesn't support sequences") @SkipForDialect(value = MariaDB53Dialect.class, strictMatching = true, @@ -217,6 +216,8 @@ public void initSessionFactory() { final MetadataSources metadataSources = new MetadataSources( serviceRegistry ); metadataSources.addInputStream( getClass().getResourceAsStream( "implicit-file-level-catalog-and-schema.orm.xml" ) ); metadataSources.addInputStream( getClass().getResourceAsStream( "implicit-file-level-catalog-and-schema.hbm.xml" ) ); + metadataSources.addInputStream( getClass().getResourceAsStream( "database-object-using-catalog-placeholder.hbm.xml" ) ); + metadataSources.addInputStream( getClass().getResourceAsStream( "database-object-using-schema-placeholder.hbm.xml" ) ); if ( configuredXmlMappingPath != null ) { metadataSources.addInputStream( getClass().getResourceAsStream( configuredXmlMappingPath ) ); } @@ -585,6 +586,15 @@ private void verifyDDLQualifiers(String sql) { verifyOnlyQualifier( sql, SqlType.DDL, EntityWithExplicitQualifiersWithEnhancedSequenceGenerator.NAME, expectedExplicitQualifier() ); verifyOnlyQualifier( sql, SqlType.DDL, EntityWithDefaultQualifiersWithLegacySequenceGenerator.NAME, expectedDefaultQualifier() ); verifyOnlyQualifier( sql, SqlType.DDL, EntityWithExplicitQualifiersWithLegacySequenceGenerator.NAME, expectedExplicitQualifier() ); + + if ( dbSupportsCatalogs && expectedDefaultCatalog != null ) { + verifyOnlyQualifier( sql, SqlType.DDL, "catalogPrefixedAuxObject", + expectedQualifier( expectedDefaultCatalog, null ) ); + } + if ( dbSupportsSchemas && expectedDefaultSchema != null ) { + verifyOnlyQualifier( sql, SqlType.DDL, "schemaPrefixedAuxObject", + expectedQualifier( null, expectedDefaultSchema ) ); + } } private enum SqlType { diff --git a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/database-object-using-catalog-placeholder.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/database-object-using-catalog-placeholder.hbm.xml new file mode 100644 index 000000000000..ee1e9f593dcf --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/database-object-using-catalog-placeholder.hbm.xml @@ -0,0 +1,30 @@ + + + + + + + CREATE OR REPLACE FUNCTION ${catalog}.catalogPrefixedAuxObject() + RETURNS varchar AS + $BODY$ + BEGIN + SELECT 'test'; + END; + $BODY$ + LANGUAGE plpgsql + + DROP FUNCTION ${catalog}.catalogPrefixedAuxObject() + + diff --git a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/database-object-using-schema-placeholder.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/database-object-using-schema-placeholder.hbm.xml new file mode 100644 index 000000000000..b9e36a015ed1 --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/database-object-using-schema-placeholder.hbm.xml @@ -0,0 +1,30 @@ + + + + + + + CREATE OR REPLACE FUNCTION ${schema}.schemaPrefixedAuxObject() + RETURNS varchar AS + $BODY$ + BEGIN + SELECT 'test'; + END; + $BODY$ + LANGUAGE plpgsql + + DROP FUNCTION ${schema}.schemaPrefixedAuxObject() + + From 404020f8b049112fd9099fa1380f77e96c631999 Mon Sep 17 00:00:00 2001 From: Ptits de Barbe Date: Fri, 4 Oct 2019 13:01:14 +0300 Subject: [PATCH 075/201] HHH-4384 Allow join column override if @JoinColumn is absent on @OneToOne(mappedBy = ""). Tests: this case and disallow if mappedBy > "". --- .../org/hibernate/cfg/Ejb3JoinColumn.java | 2 +- .../OverrideOneToOneJoinColumnTest.java | 204 ++++++++++++++++++ 2 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OverrideOneToOneJoinColumnTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java index 77be05530038..f9d7107fffea 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java @@ -253,7 +253,7 @@ private static Ejb3JoinColumn buildJoinColumn( String suffixForDefaultColumnName, MetadataBuildingContext buildingContext) { if ( ann != null ) { - if ( BinderHelper.isEmptyAnnotationValue( mappedBy ) ) { + if ( !BinderHelper.isEmptyOrNullAnnotationValue( mappedBy ) ) { throw new AnnotationException( "Illegal attempt to define a @JoinColumn with a mappedBy association: " + BinderHelper.getRelativePath( propertyHolder, propertyName ) diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OverrideOneToOneJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OverrideOneToOneJoinColumnTest.java new file mode 100644 index 000000000000..087e02fb8bf7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OverrideOneToOneJoinColumnTest.java @@ -0,0 +1,204 @@ +package org.hibernate.test.annotations.onetoone; + +import javax.persistence.AssociationOverride; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToOne; + +import org.hibernate.AnnotationException; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.mapping.ForeignKey; +import org.hibernate.mapping.Table; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Aresnii Skvortsov + */ +@TestForIssue(jiraKey = "HHH-4384") +public class OverrideOneToOneJoinColumnTest extends BaseUnitTestCase { + + @Test + public void allowIfJoinColumnIsAbsent() { + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build(); + try { + Metadata metadata = new MetadataSources( ssr ) + .addAnnotatedClass( Person.class ) + .addAnnotatedClass( State.class ) + .buildMetadata(); + + Table personTable = metadata.getDatabase().getDefaultNamespace().locateTable( Identifier.toIdentifier( + "PERSON_TABLE" ) ); + ForeignKey foreignKey = personTable.getForeignKeyIterator().next(); + + assertEquals( + "Overridden join column name should be applied", + "PERSON_ADDRESS_STATE", + foreignKey.getColumn( 0 ).getName() + ); + + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + @Test + public void disallowOnSideWithMappedBy() { + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build(); + try { + new MetadataSources( ssr ) + .addAnnotatedClass( Employee.class ) + .addAnnotatedClass( PartTimeEmployee.class ) + .addAnnotatedClass( Desk.class ) + .buildMetadata(); + fail( "Should disallow @JoinColumn override on side with mappedBy" ); + } + catch (AnnotationException ex) { + assertTrue( + "Should disallow exactly because of @JoinColumn override on side with mappedBy", + ex + .getMessage() + .startsWith( "Illegal attempt to define a @JoinColumn with a mappedBy association:" ) + ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + @Entity(name = "Person") + @javax.persistence.Table(name = "PERSON_TABLE") + public static class Person { + + private String id; + + private Address address; + + @Id + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Embedded + @AssociationOverride(name = "state", joinColumns = { @JoinColumn(name = "PERSON_ADDRESS_STATE") }) + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + } + + @Embeddable + public static class Address { + + private String street; + + private String city; + + private State state; + + @OneToOne + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + } + + @Entity(name = "State") + @javax.persistence.Table(name = "STATE_TABLE") + public static class State { + + private String id; + + private String name; + + @Id + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @MappedSuperclass + public static class Employee { + + @Id + private Long id; + + private String name; + + @OneToOne(mappedBy = "employee") + protected Desk desk; + } + + @Entity + @AssociationOverride(name = "desk", + joinColumns = @JoinColumn(name = "PARTTIMEEMPLOYEE_DESK")) + public static class PartTimeEmployee extends Employee { + + } + + @Entity(name = "Desk") + public static class Desk { + @Id + private Long id; + + @OneToOne + private PartTimeEmployee employee; + + private String location; + } + + +} From 2506a8435efc25e2465bc7d77852c06566b600d2 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 16 Mar 2022 12:24:17 +0100 Subject: [PATCH 076/201] HHH-15091 Add test for issue --- .../OneToManyJoinColumnsUniquenessTest.java | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyJoinColumnsUniquenessTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyJoinColumnsUniquenessTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyJoinColumnsUniquenessTest.java new file mode 100644 index 000000000000..d2d05152101a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyJoinColumnsUniquenessTest.java @@ -0,0 +1,115 @@ +package org.hibernate.test.annotations.onetomany; + +import java.io.Serializable; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.PersistenceException; + +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.junit.Assert.fail; + +@TestForIssue(jiraKey = "HHH-15091") +public class OneToManyJoinColumnsUniquenessTest extends BaseCoreFunctionalTestCase { + private static final SQLStatementInspector statementInspector = new SQLStatementInspector(); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + EntityA.class, + EntityB.class + }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.getProperties().put( Environment.STATEMENT_INSPECTOR, statementInspector ); + } + + @Test + public void testInsertWithNullAssociationThrowPersistenceException() { + statementInspector.clear(); + + inTransaction( + session -> { + try { + EntityB entityB = new EntityB( 1l ); + session.persist( entityB ); + fail("PersistenceException expected"); + } + catch (PersistenceException e) { + //expected + } + // check that no insert statement has bees executed + statementInspector.assertExecutedCount( 0 ); + } + ); + } + + @Entity(name = "EntityA") + public static class EntityA { + + @EmbeddedId + private PK id; + + @OneToMany(mappedBy = "entityA", fetch = FetchType.LAZY) + private Set entityBs; + + public EntityA() { + } + } + + @Embeddable + public static class PK implements Serializable { + @Column(name = "id_1") + private String id1; + @Column(name = "id_2") + private String id2; + + public PK() { + } + + public PK(String id1, String id2) { + this.id1 = id1; + this.id2 = id2; + } + } + + @Entity(name = "EntityB") + public static class EntityB { + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns(value = { + @JoinColumn(name = "b_to_a_1", referencedColumnName = "id_1", nullable = false) + , + @JoinColumn(name = "b_to_a_2", referencedColumnName = "id_2", nullable = false) + } + ) + private EntityA entityA; + + public EntityB() { + } + + public EntityB(Long id) { + this.id = id; + } + } +} From 929af9c9f66d263bba33365e9cfbec5863f29c27 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 16 Mar 2022 14:51:34 +0100 Subject: [PATCH 077/201] HHH-15091 EntityManager.persist does not verify the existence of the one side of a many-to-one relationship --- .../org/hibernate/cfg/AnnotationBinder.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index 4ea840ebb2f2..49a059e61628 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -3159,11 +3159,24 @@ else if (hasSpecjManyToOne) { propertyBinder.setXToMany( true ); final Property boundProperty = propertyBinder.makePropertyAndBind(); + boundProperty.setOptional( optional && isNullable( joinColumns, joinColumn ) ); + } + + private static boolean isNullable(JoinColumns joinColumns, JoinColumn joinColumn) { if ( joinColumn != null ) { - boundProperty.setOptional( joinColumn.nullable() && optional ); + return joinColumn.nullable(); + } + else if ( joinColumns != null ) { + final JoinColumn[] col = joinColumns.value(); + for ( int i = 0; i < col.length; i++ ) { + if ( joinColumns.value()[i].nullable() ) { + return true; + } + } + return false; } else { - boundProperty.setOptional( optional ); + return true; } } From 74d73a77b466294abf896fe61740449d20d530d6 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 14 May 2022 17:05:51 +0100 Subject: [PATCH 078/201] HHH-15274 Optimise LazyAttributeLoadingInterceptor's routines to identify lazy fields --- .../spi/interceptor/LazyAttributeLoadingInterceptor.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java index 1b084cccd223..e7ee52a0dc21 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java @@ -30,6 +30,8 @@ */ public class LazyAttributeLoadingInterceptor extends AbstractLazyLoadInterceptor { private final Object identifier; + + //N.B. this Set needs to be treated as immutable private final Set lazyFields; private Set initializedLazyFields; @@ -40,7 +42,8 @@ public LazyAttributeLoadingInterceptor( SharedSessionContractImplementor session) { super( entityName, session ); this.identifier = identifier; - this.lazyFields = lazyFields; + //Important optimisation to not actually do a Map lookup for entities which don't have any lazy fields at all: + this.lazyFields = org.hibernate.internal.util.collections.CollectionHelper.toSmallSet( lazyFields ); } @Override From 88b5f49ac984972cfafc224438809dec17138187 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 14 May 2022 17:07:21 +0100 Subject: [PATCH 079/201] HHH-15274 Field LazyAttributeLoadingInterceptor#lazyFields can never be null --- .../spi/interceptor/LazyAttributeLoadingInterceptor.java | 4 ++-- .../hibernate/internal/util/collections/CollectionHelper.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java index e7ee52a0dc21..ea2feab1f78e 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java @@ -124,7 +124,7 @@ public boolean isAttributeLoaded(String fieldName) { } private boolean isLazyAttribute(String fieldName) { - return lazyFields == null || lazyFields.contains( fieldName ); + return lazyFields.contains( fieldName ); } private boolean isInitializedLazyField(String fieldName) { @@ -132,7 +132,7 @@ private boolean isInitializedLazyField(String fieldName) { } public boolean hasAnyUninitializedAttributes() { - if ( lazyFields == null || lazyFields.isEmpty() ) { + if ( lazyFields.isEmpty() ) { return false; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java index af4936bffb7d..c58052d287d9 100755 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java @@ -220,7 +220,7 @@ public static boolean isEmpty(Object[] objects) { * The goal is to save memory. * @param set * @param - * @return + * @return will never return null, but might return an immutable collection. */ public static Set toSmallSet(Set set) { switch ( set.size() ) { From dabdc562cb560650f7ad76d11b2ac05298464f8e Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Thu, 21 Apr 2022 16:37:42 +0100 Subject: [PATCH 080/201] HHH-15222 Introduce a SessionLazyDelegator SPI --- .../engine/spi/SessionLazyDelegator.java | 787 ++++++++++++++++++ 1 file changed, 787 insertions(+) create mode 100644 hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java new file mode 100644 index 000000000000..c79b7edd8939 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java @@ -0,0 +1,787 @@ +/* + * 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 . + */ +package org.hibernate.engine.spi; + +import java.io.Serializable; +import java.sql.Connection; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import javax.persistence.EntityGraph; +import javax.persistence.EntityManagerFactory; +import javax.persistence.FlushModeType; +import javax.persistence.LockModeType; +import javax.persistence.StoredProcedureQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaDelete; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.CriteriaUpdate; +import javax.persistence.metamodel.Metamodel; + +import org.hibernate.CacheMode; +import org.hibernate.Criteria; +import org.hibernate.Filter; +import org.hibernate.FlushMode; +import org.hibernate.HibernateException; +import org.hibernate.IdentifierLoadAccess; +import org.hibernate.LobHelper; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.MultiIdentifierLoadAccess; +import org.hibernate.NaturalIdLoadAccess; +import org.hibernate.Query; +import org.hibernate.ReplicationMode; +import org.hibernate.Session; +import org.hibernate.SessionEventListener; +import org.hibernate.SessionFactory; +import org.hibernate.SharedSessionBuilder; +import org.hibernate.SimpleNaturalIdLoadAccess; +import org.hibernate.Transaction; +import org.hibernate.TypeHelper; +import org.hibernate.UnknownProfileException; +import org.hibernate.graph.RootGraph; +import org.hibernate.jdbc.ReturningWork; +import org.hibernate.jdbc.Work; +import org.hibernate.procedure.ProcedureCall; +import org.hibernate.query.NativeQuery; +import org.hibernate.stat.SessionStatistics; + +/** + * This helper class allows decorating a Session instance, while the + * instance itself is lazily provided via a {@code Supplier}. + * When the decorated instance is readily available, one + * should prefer using {@code SessionDelegatorBaseImpl}. + * + * Another difference with SessionDelegatorBaseImpl is that + * this type only implements Session. + * + * @author Sanne Grinovero (C) 2022 Red Hat Inc. + */ +public class SessionLazyDelegator implements Session { + + private final Supplier lazySession; + + public SessionLazyDelegator(Supplier lazySessionLookup){ + this.lazySession = lazySessionLookup; + } + + @Override + public SharedSessionBuilder sessionWithOptions() { + return lazySession.get().sessionWithOptions(); + } + + @Override + public void flush() throws HibernateException { + lazySession.get().flush(); + } + + @Override + @Deprecated + public void setFlushMode(FlushMode flushMode) { + lazySession.get().setFlushMode( flushMode ); + } + + @Override + public FlushModeType getFlushMode() { + return lazySession.get().getFlushMode(); + } + + @Override + public void setHibernateFlushMode(FlushMode flushMode) { + lazySession.get().setHibernateFlushMode( flushMode ); + } + + @Override + public FlushMode getHibernateFlushMode() { + return lazySession.get().getHibernateFlushMode(); + } + + @Override + public void setCacheMode(CacheMode cacheMode) { + lazySession.get().setCacheMode( cacheMode ); + } + + @Override + public CacheMode getCacheMode() { + return lazySession.get().getCacheMode(); + } + + @Override + public SessionFactory getSessionFactory() { + return lazySession.get().getSessionFactory(); + } + + @Override + public void cancelQuery() throws HibernateException { + lazySession.get().cancelQuery(); + } + + @Override + public boolean isDirty() throws HibernateException { + return lazySession.get().isDirty(); + } + + @Override + public boolean isDefaultReadOnly() { + return lazySession.get().isDefaultReadOnly(); + } + + @Override + public void setDefaultReadOnly(boolean readOnly) { + lazySession.get().setDefaultReadOnly( readOnly ); + } + + @Override + public Serializable getIdentifier(Object object) { + return lazySession.get().getIdentifier( object ); + } + + @Override + public boolean contains(String entityName, Object object) { + return lazySession.get().contains( entityName, object ); + } + + @Override + public void evict(Object object) { + lazySession.get().evict( object ); + } + + @Override + public T load(Class theClass, Serializable id, LockMode lockMode) { + return lazySession.get().load( theClass, id, lockMode ); + } + + @Override + public T load(Class theClass, Serializable id, LockOptions lockOptions) { + return lazySession.get().load( theClass, id, lockOptions ); + } + + @Override + public Object load(String entityName, Serializable id, LockMode lockMode) { + return lazySession.get().load( entityName, id, lockMode ); + } + + @Override + public Object load(String entityName, Serializable id, LockOptions lockOptions) { + return lazySession.get().load( entityName, id, lockOptions ); + } + + @Override + public T load(Class theClass, Serializable id) { + return lazySession.get().load( theClass, id ); + } + + @Override + public Object load(String entityName, Serializable id) { + return lazySession.get().load( entityName, id ); + } + + @Override + public void load(Object object, Serializable id) { + lazySession.get().load( object, id ); + } + + @Override + public void replicate(Object object, ReplicationMode replicationMode) { + lazySession.get().replicate( object, replicationMode ); + } + + @Override + public void replicate(String entityName, Object object, ReplicationMode replicationMode) { + lazySession.get().replicate( entityName, object, replicationMode ); + } + + @Override + public Serializable save(Object object) { + return lazySession.get().save( object ); + } + + @Override + public Serializable save(String entityName, Object object) { + return lazySession.get().save( entityName, object ); + } + + @Override + public void saveOrUpdate(Object object) { + lazySession.get().saveOrUpdate( object ); + } + + @Override + public void saveOrUpdate(String entityName, Object object) { + lazySession.get().saveOrUpdate( entityName, object ); + } + + @Override + public void update(Object object) { + lazySession.get().update( object ); + } + + @Override + public void update(String entityName, Object object) { + lazySession.get().update( entityName, object ); + } + + @Override + public Object merge(Object object) { + return lazySession.get().merge( object ); + } + + @Override + public Object merge(String entityName, Object object) { + return lazySession.get().merge( entityName, object ); + } + + @Override + public void persist(Object object) { + lazySession.get().persist( object ); + } + + @Override + public void persist(String entityName, Object object) { + lazySession.get().persist( entityName, object ); + } + + @Override + public void delete(Object object) { + lazySession.get().delete( object ); + } + + @Override + public void delete(String entityName, Object object) { + lazySession.get().delete( entityName, object ); + } + + @Override + public void lock(Object object, LockMode lockMode) { + lazySession.get().lock( object, lockMode ); + } + + @Override + public void lock(String entityName, Object object, LockMode lockMode) { + lazySession.get().lock( entityName, object, lockMode ); + } + + @Override + public LockRequest buildLockRequest(LockOptions lockOptions) { + return lazySession.get().buildLockRequest( lockOptions ); + } + + @Override + public void refresh(Object object) { + lazySession.get().refresh( object ); + } + + @Override + public void refresh(String entityName, Object object) { + lazySession.get().refresh( entityName, object ); + } + + @Override + public void refresh(Object object, LockMode lockMode) { + lazySession.get().refresh( object, lockMode ); + } + + @Override + public void refresh(Object object, LockOptions lockOptions) { + lazySession.get().refresh( object, lockOptions ); + } + + @Override + public void refresh(String entityName, Object object, LockOptions lockOptions) { + lazySession.get().refresh( entityName, object, lockOptions ); + } + + @Override + public LockMode getCurrentLockMode(Object object) { + return lazySession.get().getCurrentLockMode( object ); + } + + @Override + @Deprecated + public Query createFilter(Object collection, String queryString) { + return lazySession.get().createFilter( collection, queryString ); + } + + @Override + public void clear() { + lazySession.get().clear(); + } + + @Override + public T get(Class entityType, Serializable id) { + return lazySession.get().get( entityType, id ); + } + + @Override + public T get(Class entityType, Serializable id, LockMode lockMode) { + return lazySession.get().get( entityType, id, lockMode ); + } + + @Override + public T get(Class entityType, Serializable id, LockOptions lockOptions) { + return lazySession.get().get( entityType, id, lockOptions ); + } + + @Override + public Object get(String entityName, Serializable id) { + return lazySession.get().get( entityName, id ); + } + + @Override + public Object get(String entityName, Serializable id, LockMode lockMode) { + return lazySession.get().get( entityName, id, lockMode ); + } + + @Override + public Object get(String entityName, Serializable id, LockOptions lockOptions) { + return lazySession.get().get( entityName, id, lockOptions ); + } + + @Override + public String getEntityName(Object object) { + return lazySession.get().getEntityName( object ); + } + + @Override + public IdentifierLoadAccess byId(String entityName) { + return lazySession.get().byId( entityName ); + } + + @Override + public MultiIdentifierLoadAccess byMultipleIds(Class entityClass) { + return lazySession.get().byMultipleIds( entityClass ); + } + + @Override + public MultiIdentifierLoadAccess byMultipleIds(String entityName) { + return lazySession.get().byMultipleIds( entityName ); + } + + @Override + public IdentifierLoadAccess byId(Class entityClass) { + return lazySession.get().byId( entityClass ); + } + + @Override + public NaturalIdLoadAccess byNaturalId(String entityName) { + return lazySession.get().byNaturalId( entityName ); + } + + @Override + public NaturalIdLoadAccess byNaturalId(Class entityClass) { + return lazySession.get().byNaturalId( entityClass ); + } + + @Override + public SimpleNaturalIdLoadAccess bySimpleNaturalId(String entityName) { + return lazySession.get().bySimpleNaturalId( entityName ); + } + + @Override + public SimpleNaturalIdLoadAccess bySimpleNaturalId(Class entityClass) { + return lazySession.get().bySimpleNaturalId( entityClass ); + } + + @Override + public Filter enableFilter(String filterName) { + return lazySession.get().enableFilter( filterName ); + } + + @Override + public Filter getEnabledFilter(String filterName) { + return lazySession.get().getEnabledFilter( filterName ); + } + + @Override + public void disableFilter(String filterName) { + lazySession.get().disableFilter( filterName ); + } + + @Override + public SessionStatistics getStatistics() { + return lazySession.get().getStatistics(); + } + + @Override + public boolean isReadOnly(Object entityOrProxy) { + return lazySession.get().isReadOnly( entityOrProxy ); + } + + @Override + public void setReadOnly(Object entityOrProxy, boolean readOnly) { + lazySession.get().setReadOnly( entityOrProxy, readOnly ); + } + + @Override + public RootGraph createEntityGraph(Class rootType) { + return lazySession.get().createEntityGraph( rootType ); + } + + @Override + public RootGraph createEntityGraph(String graphName) { + return lazySession.get().createEntityGraph( graphName ); + } + + @Override + public RootGraph getEntityGraph(String graphName) { + return lazySession.get().getEntityGraph( graphName ); + } + + @Override + public List> getEntityGraphs(Class entityClass) { + return lazySession.get().getEntityGraphs( entityClass ); + } + + @Override + public Connection disconnect() { + return lazySession.get().disconnect(); + } + + @Override + public void reconnect(Connection connection) { + lazySession.get().reconnect( connection ); + } + + @Override + public boolean isFetchProfileEnabled(String name) throws UnknownProfileException { + return lazySession.get().isFetchProfileEnabled( name ); + } + + @Override + public void enableFetchProfile(String name) throws UnknownProfileException { + lazySession.get().enableFetchProfile( name ); + } + + @Override + public void disableFetchProfile(String name) throws UnknownProfileException { + lazySession.get().disableFetchProfile( name ); + } + + @Override + public TypeHelper getTypeHelper() { + return lazySession.get().getTypeHelper(); + } + + @Override + public LobHelper getLobHelper() { + return lazySession.get().getLobHelper(); + } + + @Override + public void addEventListeners(SessionEventListener... listeners) { + lazySession.get().addEventListeners( listeners ); + } + + @Override + public org.hibernate.query.Query createQuery(String queryString, Class resultType) { + return lazySession.get().createQuery( queryString, resultType ); + } + + @Override + public org.hibernate.query.Query createQuery(CriteriaQuery criteriaQuery) { + return lazySession.get().createQuery( criteriaQuery ); + } + + @Override + public org.hibernate.query.Query createQuery(CriteriaUpdate updateQuery) { + return lazySession.get().createQuery( updateQuery ); + } + + @Override + public org.hibernate.query.Query createQuery(CriteriaDelete deleteQuery) { + return lazySession.get().createQuery( deleteQuery ); + } + + @Override + public org.hibernate.query.Query createNamedQuery(String name, Class resultType) { + return lazySession.get().createNamedQuery( name, resultType ); + } + + @Override + public NativeQuery createSQLQuery(String queryString) { + return lazySession.get().createSQLQuery( queryString ); + } + + @Override + public String getTenantIdentifier() { + return lazySession.get().getTenantIdentifier(); + } + + @Override + public void close() throws HibernateException { + lazySession.get().close(); + } + + @Override + public boolean isOpen() { + return lazySession.get().isOpen(); + } + + @Override + public boolean isConnected() { + return lazySession.get().isConnected(); + } + + @Override + public Transaction beginTransaction() { + return lazySession.get().beginTransaction(); + } + + @Override + public Transaction getTransaction() { + return lazySession.get().getTransaction(); + } + + @Override + public org.hibernate.query.Query createQuery(String queryString) { + return lazySession.get().createQuery( queryString ); + } + + @Override + public org.hibernate.query.Query getNamedQuery(String queryName) { + return lazySession.get().getNamedQuery( queryName ); + } + + @Override + public ProcedureCall getNamedProcedureCall(String name) { + return lazySession.get().getNamedProcedureCall( name ); + } + + @Override + public ProcedureCall createStoredProcedureCall(String procedureName) { + return lazySession.get().createStoredProcedureCall( procedureName ); + } + + @Override + public ProcedureCall createStoredProcedureCall(String procedureName, Class... resultClasses) { + return lazySession.get().createStoredProcedureCall( procedureName, resultClasses ); + } + + @Override + public ProcedureCall createStoredProcedureCall(String procedureName, String... resultSetMappings) { + return lazySession.get().createStoredProcedureCall( procedureName, resultSetMappings ); + } + + @Override + @Deprecated + public Criteria createCriteria(Class persistentClass) { + return lazySession.get().createCriteria( persistentClass ); + } + + @Override + @Deprecated + public Criteria createCriteria(Class persistentClass, String alias) { + return lazySession.get().createCriteria( persistentClass, alias ); + } + + @Override + @Deprecated + public Criteria createCriteria(String entityName) { + return lazySession.get().createCriteria( entityName ); + } + + @Override + @Deprecated + public Criteria createCriteria(String entityName, String alias) { + return lazySession.get().createCriteria( entityName, alias ); + } + + @Override + public Integer getJdbcBatchSize() { + return lazySession.get().getJdbcBatchSize(); + } + + @Override + public void setJdbcBatchSize(Integer jdbcBatchSize) { + lazySession.get().setJdbcBatchSize( jdbcBatchSize ); + } + + @Override + public void doWork(Work work) throws HibernateException { + lazySession.get().doWork( work ); + } + + @Override + public T doReturningWork(ReturningWork work) throws HibernateException { + return lazySession.get().doReturningWork( work ); + } + + @Override + public org.hibernate.query.Query createNamedQuery(String name) { + return lazySession.get().createNamedQuery( name ); + } + + @Override + public NativeQuery createNativeQuery(String sqlString) { + return lazySession.get().createNativeQuery( sqlString ); + } + + @Override + public NativeQuery createNativeQuery(String sqlString, String resultSetMapping) { + return lazySession.get().createNativeQuery( sqlString, resultSetMapping ); + } + + @Override + @Deprecated + public Query getNamedSQLQuery(String name) { + return lazySession.get().getNamedSQLQuery( name ); + } + + @Override + public NativeQuery getNamedNativeQuery(String name) { + return lazySession.get().getNamedNativeQuery( name ); + } + @Override + public void remove(Object entity) { + lazySession.get().remove( entity ); + } + @Override + public T find(Class entityClass, Object primaryKey) { + return lazySession.get().find( entityClass, primaryKey ); + } + + @Override + public T find(Class entityClass, Object primaryKey, Map properties) { + return lazySession.get().find( entityClass, primaryKey, properties ); + } + + @Override + public T find(Class entityClass, Object primaryKey, LockModeType lockMode) { + return lazySession.get().find( entityClass, primaryKey, lockMode ); + } + + @Override + public T find(Class entityClass, Object primaryKey, LockModeType lockMode, Map properties) { + return lazySession.get().find( entityClass, primaryKey, lockMode, properties ); + } + + @Override + public T getReference(Class entityClass, Object primaryKey) { + return lazySession.get().getReference( entityClass, primaryKey ); + } + + @Override + public void setFlushMode(FlushModeType flushMode) { + lazySession.get().setFlushMode( flushMode ); + } + + @Override + public void lock(Object entity, LockModeType lockMode) { + lazySession.get().lock( entity, lockMode ); + } + + @Override + public void lock(Object entity, LockModeType lockMode, Map properties) { + lazySession.get().lock( entity, lockMode, properties ); + } + + @Override + public void refresh(Object entity, Map properties) { + lazySession.get().refresh( entity, properties ); + } + + @Override + public void refresh(Object entity, LockModeType lockMode) { + lazySession.get().refresh( entity, lockMode ); + } + + @Override + public void refresh(Object entity, LockModeType lockMode, Map properties) { + lazySession.get().refresh( entity, lockMode, properties ); + } + + @Override + public void detach(Object entity) { + lazySession.get().detach( entity ); + } + + @Override + public boolean contains(Object entity) { + return lazySession.get().contains( entity ); + } + + @Override + public LockModeType getLockMode(Object entity) { + return lazySession.get().getLockMode( entity ); + } + + @Override + public void setProperty(String propertyName, Object value) { + lazySession.get().setProperty( propertyName, value ); + } + + @Override + public Map getProperties() { + return lazySession.get().getProperties(); + } + + @Override + public NativeQuery createNativeQuery(String sqlString, Class resultClass) { + return lazySession.get().createNativeQuery( sqlString, resultClass ); + } + + @Override + public StoredProcedureQuery createNamedStoredProcedureQuery(String name) { + return lazySession.get().createNamedStoredProcedureQuery( name ); + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName) { + return lazySession.get().createStoredProcedureQuery( procedureName ); + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName, Class... resultClasses) { + return lazySession.get().createStoredProcedureQuery( procedureName, resultClasses ); + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName, String... resultSetMappings) { + return lazySession.get().createStoredProcedureQuery( procedureName, resultSetMappings ); + } + + @Override + public void joinTransaction() { + lazySession.get().joinTransaction(); + } + + @Override + public boolean isJoinedToTransaction() { + return lazySession.get().isJoinedToTransaction(); + } + + @Override + public T unwrap(Class cls) { + return lazySession.get().unwrap( cls ); + } + + @Override + public Object getDelegate() { + return lazySession.get().getDelegate(); + } + + @Override + public EntityManagerFactory getEntityManagerFactory() { + return lazySession.get().getEntityManagerFactory(); + } + + @Override + public CriteriaBuilder getCriteriaBuilder() { + return lazySession.get().getCriteriaBuilder(); + } + + @Override + public Metamodel getMetamodel() { + return lazySession.get().getMetamodel(); + } + + @Override + public Session getSession() { + return lazySession.get().getSession(); + } +} From 791fa98394d65c35b1152987edc5a104d49ee01e Mon Sep 17 00:00:00 2001 From: Francesco Marino Date: Thu, 7 Apr 2022 01:28:57 +0200 Subject: [PATCH 081/201] HHH-15134 Update a bytecode enanchhed Entity with a Version attribute causes OptimisticLockException --- ...EnhancementAsProxyLazinessInterceptor.java | 5 +- .../version/VersionedEntityTest.java | 241 ++++++++++++++++++ 2 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/version/VersionedEntityTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java index e5a65e356579..005889668d2c 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java @@ -72,8 +72,9 @@ public EnhancementAsProxyLazinessInterceptor( this.inLineDirtyChecking = entityPersister.getEntityMode() == EntityMode.POJO && SelfDirtinessTracker.class.isAssignableFrom( entityPersister.getMappedClass() ); // if self-dirty tracking is enabled but DynamicUpdate is not enabled then we need to initialise the entity - // because the pre-computed update statement contains even not dirty properties and so we need all the values - initializeBeforeWrite = !( inLineDirtyChecking && entityPersister.getEntityMetamodel().isDynamicUpdate() ); + // because the pre-computed update statement contains even not dirty properties and so we need all the values + // we have to initialise it even if it's versioned to fetch the current version + initializeBeforeWrite = !( inLineDirtyChecking && entityPersister.getEntityMetamodel().isDynamicUpdate() ) || entityPersister.isVersioned(); status = Status.UNINITIALIZED; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/version/VersionedEntityTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/version/VersionedEntityTest.java new file mode 100644 index 000000000000..bf07a9e2e387 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/version/VersionedEntityTest.java @@ -0,0 +1,241 @@ +package org.hibernate.test.bytecode.enhancement.version; + +import org.hibernate.Hibernate; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.Version; + +import static org.hibernate.Hibernate.isInitialized; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@TestForIssue(jiraKey = "HHH-15134") +@RunWith(BytecodeEnhancerRunner.class) +public class VersionedEntityTest extends BaseCoreFunctionalTestCase { + private final Long parentID = 1L; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{ FooEntity.class, BarEntity.class, BazEntity.class }; + } + + @Before + public void prepare() { + doInJPA(this::sessionFactory, em -> { + final FooEntity entity = FooEntity.of( parentID, "foo" ); + em.persist( entity ); + }); + } + + @Test + public void testUpdateVersionedEntity() { + doInJPA(this::sessionFactory, em -> { + final FooEntity entity = em.getReference( FooEntity.class, parentID ); + + assertFalse( isInitialized( entity ) ); + assertTrue( Hibernate.isPropertyInitialized( entity, "id" ) ); + assertFalse( Hibernate.isPropertyInitialized( entity, "name" ) ); + assertFalse( Hibernate.isPropertyInitialized( entity, "version" ) ); + assertFalse( Hibernate.isPropertyInitialized( entity, "bars" ) ); + assertFalse( Hibernate.isPropertyInitialized( entity, "bazzes" ) ); + + entity.setName( "bar" ); + }); + } + + @MappedSuperclass + public static abstract class AbstractEntity { + + public abstract T getId(); + + public abstract void setId(T id); + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != getClass()) return false; + + final AbstractEntity other = (AbstractEntity) obj; + return getId() != null && getId().equals(other.getId()); + } + } + + @Entity(name = "FooEntity") + public static class FooEntity extends AbstractEntity { + + @Id + private long id; + @Version + private int version; + + private String name; + + @OneToMany(mappedBy = "foo", cascade = CascadeType.ALL, targetEntity = BarEntity.class, orphanRemoval = true) + public Set bars = new HashSet<>(); + + @OneToMany(mappedBy = "foo", cascade = CascadeType.ALL, targetEntity = BazEntity.class, orphanRemoval = true) + public Set bazzes = new HashSet<>(); + + public static FooEntity of(long id, String name) { + final FooEntity f = new FooEntity(); + f.id = id; + f.name = name; + return f; + } + + @Override + public Long getId() { + return id; + } + + @Override + public void setId(Long id) { + this.id = id; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getBars() { + return bars; + } + + public void addBar(BarEntity bar) { + bars.add(bar); + bar.setFoo(this); + } + + public void removeBar(BarEntity bar) { + bars.remove(bar); + bar.setFoo(null); + } + + public Set getBazzes() { + return bazzes; + } + + public void addBaz(BazEntity baz) { + bazzes.add(baz); + baz.setFoo(this); + } + + public void removeBaz(BazEntity baz) { + bazzes.remove(baz); + baz.setFoo(null); + } + + @Override + public String toString() { + return String.format("FooEntity: id=%d, version=%d, name=%s", id, version, name); + } + } + + @Entity(name = "BazEntity") + public static class BazEntity extends AbstractEntity { + + @Id + @GeneratedValue + private long id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(foreignKey = @ForeignKey(name = "fk_baz_foo"), nullable = false) + private FooEntity foo; + + @Override + public Long getId() { + return id; + } + + @Override + public void setId(Long id) { + this.id = id; + } + + public FooEntity getFoo() { + return foo; + } + + public void setFoo(FooEntity foo) { + this.foo = foo; + } + + @Override + public String toString() { + return String.format("BazEntity: id=%d", id); + } + } + + @Entity(name = "BarEntity") + public static class BarEntity extends AbstractEntity { + + @Id + @GeneratedValue + private long id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(foreignKey = @ForeignKey(name = "fk_bar_foo"), nullable = false) + private FooEntity foo; + + @Override + public Long getId() { + return id; + } + + @Override + public void setId(Long id) { + this.id = id; + } + + public FooEntity getFoo() { + return foo; + } + + public void setFoo(FooEntity foo) { + this.foo = foo; + } + + @Override + public String toString() { + return String.format("BarEntity: id=%d", id); + } + } +} + From 66bd42651c4fd8eb5abbf6897d84dc4923f21e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 13 May 2022 09:52:56 +0200 Subject: [PATCH 082/201] HHH-15270 Test default catalog/schema with hbm.xml/orm.xml mapping when catalog/schema not specified at file level --- .../DefaultCatalogAndSchemaTest.java | 21 +++++++++ .../no-file-level-catalog-and-schema.hbm.xml | 17 +++++++ .../no-file-level-catalog-and-schema.orm.xml | 44 +++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/no-file-level-catalog-and-schema.hbm.xml create mode 100644 hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/no-file-level-catalog-and-schema.orm.xml diff --git a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java index 9c7c2113a8eb..deb5e7ac9b2c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java @@ -216,6 +216,8 @@ public void initSessionFactory() { final MetadataSources metadataSources = new MetadataSources( serviceRegistry ); metadataSources.addInputStream( getClass().getResourceAsStream( "implicit-file-level-catalog-and-schema.orm.xml" ) ); metadataSources.addInputStream( getClass().getResourceAsStream( "implicit-file-level-catalog-and-schema.hbm.xml" ) ); + metadataSources.addInputStream( getClass().getResourceAsStream( "no-file-level-catalog-and-schema.orm.xml" ) ); + metadataSources.addInputStream( getClass().getResourceAsStream( "no-file-level-catalog-and-schema.hbm.xml" ) ); metadataSources.addInputStream( getClass().getResourceAsStream( "database-object-using-catalog-placeholder.hbm.xml" ) ); metadataSources.addInputStream( getClass().getResourceAsStream( "database-object-using-schema-placeholder.hbm.xml" ) ); if ( configuredXmlMappingPath != null ) { @@ -336,6 +338,8 @@ public void entityPersister() { verifyEntityPersisterQualifiers( EntityWithExplicitQualifiers.class, expectedExplicitQualifier() ); verifyEntityPersisterQualifiers( EntityWithOrmXmlImplicitFileLevelQualifiers.class, expectedImplicitFileLevelQualifier() ); verifyEntityPersisterQualifiers( EntityWithHbmXmlImplicitFileLevelQualifiers.class, expectedImplicitFileLevelQualifier() ); + verifyEntityPersisterQualifiers( EntityWithOrmXmlNoFileLevelQualifiers.class, expectedDefaultQualifier() ); + verifyEntityPersisterQualifiers( EntityWithHbmXmlNoFileLevelQualifiers.class, expectedDefaultQualifier() ); verifyEntityPersisterQualifiers( EntityWithJoinedInheritanceWithDefaultQualifiers.class, expectedDefaultQualifier() ); verifyEntityPersisterQualifiers( EntityWithJoinedInheritanceWithDefaultQualifiersSubclass.class, expectedDefaultQualifier() ); @@ -557,6 +561,8 @@ private void verifyDDLQualifiers(String sql) { verifyOnlyQualifier( sql, SqlType.DDL, EntityWithDefaultQualifiers.NAME, expectedDefaultQualifier() ); verifyOnlyQualifier( sql, SqlType.DDL, EntityWithOrmXmlImplicitFileLevelQualifiers.NAME, expectedImplicitFileLevelQualifier() ); verifyOnlyQualifier( sql, SqlType.DDL, EntityWithHbmXmlImplicitFileLevelQualifiers.NAME, expectedImplicitFileLevelQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithOrmXmlNoFileLevelQualifiers.NAME, expectedDefaultQualifier() ); + verifyOnlyQualifier( sql, SqlType.DDL, EntityWithHbmXmlNoFileLevelQualifiers.NAME, expectedDefaultQualifier() ); verifyOnlyQualifier( sql, SqlType.DDL, EntityWithJoinedInheritanceWithDefaultQualifiers.NAME, expectedDefaultQualifier() ); verifyOnlyQualifier( sql, SqlType.DDL, EntityWithJoinedInheritanceWithDefaultQualifiersSubclass.NAME, expectedDefaultQualifier() ); @@ -827,6 +833,21 @@ public static class EntityWithHbmXmlImplicitFileLevelQualifiers { private String basic; } + public static class EntityWithOrmXmlNoFileLevelQualifiers { + public static final String NAME = "EntityWithOrmXmlNoFileLevelQualifiers"; + private Long id; + private String basic; + private List oneToMany; + private List manyToMany; + private List elementCollection; + } + + public static class EntityWithHbmXmlNoFileLevelQualifiers { + public static final String NAME = "EntityWithHbmXmlNoFileLevelQualifiers"; + private Long id; + private String basic; + } + @Entity(name = EntityWithJoinedInheritanceWithDefaultQualifiers.NAME) @Inheritance(strategy = InheritanceType.JOINED) public static class EntityWithJoinedInheritanceWithDefaultQualifiers { diff --git a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/no-file-level-catalog-and-schema.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/no-file-level-catalog-and-schema.hbm.xml new file mode 100644 index 000000000000..7475d7d5218e --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/no-file-level-catalog-and-schema.hbm.xml @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/no-file-level-catalog-and-schema.orm.xml b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/no-file-level-catalog-and-schema.orm.xml new file mode 100644 index 000000000000..8fbd54d5c330 --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/no-file-level-catalog-and-schema.orm.xml @@ -0,0 +1,44 @@ + + + + org.hibernate.orm.test.boot.database.qualfiedTableNaming + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 0b25a0e478f3e28e00a4ed3d2112d97a1fe05bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 13 May 2022 10:19:59 +0200 Subject: [PATCH 083/201] HHH-15270 Fix inconsistent precedence of orm.xml implicit catalog over "default_catalog" in XML-mapped entities --- .../hibernate/boot/model/source/internal/hbm/ModelBinder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java index e0f7310847d1..3052ee32c0c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java @@ -2948,7 +2948,7 @@ private Identifier determineCatalogName(TableSpecificationSource tableSpecSource return database.toIdentifier( tableSpecSource.getExplicitCatalogName() ); } else { - return database.toIdentifier( metadataBuildingContext.getMappingDefaults().getImplicitCatalogName() ); + return null; } } From 24d23c40678fa26351994eaae9cd805455924a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 13 May 2022 10:21:05 +0200 Subject: [PATCH 084/201] HHH-15265 Test that default catalog/schema are taken into account in generated DDL comments --- .../implicit-file-level-catalog-and-schema.hbm.xml | 13 +++++++++++-- .../no-file-level-catalog-and-schema.hbm.xml | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/implicit-file-level-catalog-and-schema.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/implicit-file-level-catalog-and-schema.hbm.xml index fc5317d20da2..b76222a9e602 100644 --- a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/implicit-file-level-catalog-and-schema.hbm.xml +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/implicit-file-level-catalog-and-schema.hbm.xml @@ -11,7 +11,16 @@ package="org.hibernate.test.boot.database.qualfiedTableNaming" catalog="someImplicitFileLevelCatalog" schema="someImplicitFileLevelSchema" default-access="field"> - - + Some entity-level comment + + + Some column-level comment for "id" + + + + + Some column-level comment for "property" + + diff --git a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/no-file-level-catalog-and-schema.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/no-file-level-catalog-and-schema.hbm.xml index 7475d7d5218e..6f3e00b76ca3 100644 --- a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/no-file-level-catalog-and-schema.hbm.xml +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/no-file-level-catalog-and-schema.hbm.xml @@ -11,7 +11,16 @@ package="org.hibernate.orm.test.boot.database.qualfiedTableNaming" default-access="field"> - - + Some entity-level comment + + + Some column-level comment for "id" + + + + + Some column-level comment for "property" + + From 9a2e625f6cc81ab464f8b4c87fcc227d8c332525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 13 May 2022 11:26:17 +0200 Subject: [PATCH 085/201] HHH-15265 Take default catalog/schema into account when generating DDL comments --- .../internal/StandardTableExporter.java | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableExporter.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableExporter.java index 8d0edf29617e..e547c3757de6 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/StandardTableExporter.java @@ -16,6 +16,7 @@ import org.hibernate.boot.model.relational.InitCommand; import org.hibernate.boot.model.relational.QualifiedName; import org.hibernate.boot.model.relational.QualifiedNameParser; +import org.hibernate.boot.model.relational.QualifiedTableName; import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.mapping.Column; @@ -43,10 +44,11 @@ public String[] getSqlCreateStrings(Table table, Metadata metadata, table.getNameIdentifier() ); + String formattedTableName = context.format( tableName ); StringBuilder buf = new StringBuilder( tableCreateString( table.hasPrimaryKey() ) ) .append( ' ' ) - .append( context.format( tableName ) ) + .append( formattedTableName ) .append( " (" ); @@ -145,24 +147,41 @@ public String[] getSqlCreateStrings(Table table, Metadata metadata, List sqlStrings = new ArrayList(); sqlStrings.add( buf.toString() ); - applyComments( table, tableName, sqlStrings ); + applyComments( table, formattedTableName, sqlStrings ); applyInitCommands( table, sqlStrings, context ); return sqlStrings.toArray( new String[ sqlStrings.size() ] ); } - protected void applyComments(Table table, QualifiedName tableName, List sqlStrings) { + /** + * @param table The table. + * @param tableName The qualified table name. + * @param sqlStrings The list of SQL strings to add comments to. + * @deprecated Use {@link #applyComments(Table, String, List)} instead. + */ + // For backwards compatibility with subclasses that happen to call this method... + @Deprecated + protected void applyComments(Table table, QualifiedTableName tableName, List sqlStrings) { + applyComments( table, tableName.toString(), sqlStrings ); + } + + /** + * @param table The table. + * @param formattedTableName The formatted table name. + * @param sqlStrings The list of SQL strings to add comments to. + */ + protected void applyComments(Table table, String formattedTableName, List sqlStrings) { if ( dialect.supportsCommentOn() ) { if ( table.getComment() != null ) { - sqlStrings.add( "comment on table " + tableName + " is '" + table.getComment() + "'" ); + sqlStrings.add( "comment on table " + formattedTableName + " is '" + table.getComment() + "'" ); } final Iterator iter = table.getColumnIterator(); while ( iter.hasNext() ) { Column column = (Column) iter.next(); String columnComment = column.getComment(); if ( columnComment != null ) { - sqlStrings.add( "comment on column " + tableName + '.' + column.getQuotedName( dialect ) + " is '" + columnComment + "'" ); + sqlStrings.add( "comment on column " + formattedTableName + '.' + column.getQuotedName( dialect ) + " is '" + columnComment + "'" ); } } } From eb4a7a660885839f4dc09bcb6d9753589e6b25dd Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 14 May 2022 22:58:38 +0100 Subject: [PATCH 086/201] HHH-15265 Adjust inconsistent package names in tests --- .../DefaultCatalogAndSchemaTest.java | 2 +- .../NamespaceTest.java | 2 +- .../QualifiedTableNamingTest.java | 2 +- .../database-object-using-catalog-placeholder.hbm.xml | 2 +- .../database-object-using-schema-placeholder.hbm.xml | 2 +- .../implicit-file-level-catalog-and-schema.hbm.xml | 2 +- .../implicit-file-level-catalog-and-schema.orm.xml | 2 +- .../implicit-global-catalog-and-schema.orm.xml | 0 .../no-file-level-catalog-and-schema.hbm.xml | 2 +- .../no-file-level-catalog-and-schema.orm.xml | 2 +- 10 files changed, 9 insertions(+), 9 deletions(-) rename hibernate-core/src/test/java/org/hibernate/test/boot/database/{qualfiedTableNaming => qualifiedTableNaming}/DefaultCatalogAndSchemaTest.java (99%) rename hibernate-core/src/test/java/org/hibernate/test/boot/database/{qualfiedTableNaming => qualifiedTableNaming}/NamespaceTest.java (97%) rename hibernate-core/src/test/java/org/hibernate/test/boot/database/{qualfiedTableNaming => qualifiedTableNaming}/QualifiedTableNamingTest.java (98%) rename hibernate-core/src/test/resources/org/hibernate/test/boot/database/{qualfiedTableNaming => qualifiedTableNaming}/database-object-using-catalog-placeholder.hbm.xml (94%) rename hibernate-core/src/test/resources/org/hibernate/test/boot/database/{qualfiedTableNaming => qualifiedTableNaming}/database-object-using-schema-placeholder.hbm.xml (94%) rename hibernate-core/src/test/resources/org/hibernate/test/boot/database/{qualfiedTableNaming => qualifiedTableNaming}/implicit-file-level-catalog-and-schema.hbm.xml (94%) rename hibernate-core/src/test/resources/org/hibernate/test/boot/database/{qualfiedTableNaming => qualifiedTableNaming}/implicit-file-level-catalog-and-schema.orm.xml (96%) rename hibernate-core/src/test/resources/org/hibernate/test/boot/database/{qualfiedTableNaming => qualifiedTableNaming}/implicit-global-catalog-and-schema.orm.xml (100%) rename hibernate-core/src/test/resources/org/hibernate/test/boot/database/{qualfiedTableNaming => qualifiedTableNaming}/no-file-level-catalog-and-schema.hbm.xml (93%) rename hibernate-core/src/test/resources/org/hibernate/test/boot/database/{qualfiedTableNaming => qualifiedTableNaming}/no-file-level-catalog-and-schema.orm.xml (95%) diff --git a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/DefaultCatalogAndSchemaTest.java similarity index 99% rename from hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java rename to hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/DefaultCatalogAndSchemaTest.java index deb5e7ac9b2c..d1fe7f74ce53 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/DefaultCatalogAndSchemaTest.java @@ -4,7 +4,7 @@ * 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 */ -package org.hibernate.test.boot.database.qualfiedTableNaming; +package org.hibernate.test.boot.database.qualifiedTableNaming; import static org.assertj.core.api.Assertions.assertThat; diff --git a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/NamespaceTest.java b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/NamespaceTest.java similarity index 97% rename from hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/NamespaceTest.java rename to hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/NamespaceTest.java index 7ecfd501bb0c..bbef2a3a8c8b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/NamespaceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/NamespaceTest.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.boot.database.qualfiedTableNaming; +package org.hibernate.test.boot.database.qualifiedTableNaming; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategy; diff --git a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/QualifiedTableNamingTest.java b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/QualifiedTableNamingTest.java similarity index 98% rename from hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/QualifiedTableNamingTest.java rename to hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/QualifiedTableNamingTest.java index 237a947599dc..db306277a149 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualfiedTableNaming/QualifiedTableNamingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/boot/database/qualifiedTableNaming/QualifiedTableNamingTest.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.boot.database.qualfiedTableNaming; +package org.hibernate.test.boot.database.qualifiedTableNaming; import java.sql.Connection; import java.sql.SQLException; diff --git a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/database-object-using-catalog-placeholder.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/database-object-using-catalog-placeholder.hbm.xml similarity index 94% rename from hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/database-object-using-catalog-placeholder.hbm.xml rename to hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/database-object-using-catalog-placeholder.hbm.xml index ee1e9f593dcf..124c2ebcaea3 100644 --- a/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualfiedTableNaming/database-object-using-catalog-placeholder.hbm.xml +++ b/hibernate-core/src/test/resources/org/hibernate/test/boot/database/qualifiedTableNaming/database-object-using-catalog-placeholder.hbm.xml @@ -8,7 +8,7 @@