Merge remote-tracking branch 'upstream5/master' into wip/6.0_merge_15

This commit is contained in:
Andrea Boriero 2019-12-02 09:14:27 +00:00
commit d2865a54df
14 changed files with 525 additions and 11 deletions

View File

@ -3133,4 +3133,9 @@ public abstract class Dialect implements ConversionContext {
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
return null;
}
public boolean supportsSelectAliasInGroupByClause() {
return false;
}
}

View File

@ -444,4 +444,10 @@ public class H2Dialect extends Dialect {
public String getQueryHintString(String query, String hints) {
return IndexQueryHintHandler.INSTANCE.addQueryHints( query, hints );
}
@Override
public boolean supportsSelectAliasInGroupByClause() {
return true;
}
}

View File

@ -612,4 +612,10 @@ public class MySQLDialect extends Dialect {
protected String escapeLiteral(String literal) {
return ESCAPE_PATTERN.matcher( super.escapeLiteral( literal ) ).replaceAll( ESCAPE_PATTERN_REPLACEMENT );
}
@Override
public boolean supportsSelectAliasInGroupByClause() {
return true;
}
}

View File

@ -68,7 +68,7 @@ public class Oracle8iDialect extends Dialect {
private static final Pattern UNION_KEYWORD_PATTERN = Pattern.compile( "\\bunion\\b" );
private static final Pattern SQL_STATEMENT_TYPE_PATTERN = Pattern.compile("^(?:\\/\\*.*?\\*\\/)?\\s*(select|insert|update|delete)\\s+.*?");
private static final Pattern SQL_STATEMENT_TYPE_PATTERN = Pattern.compile("^(?:\\/\\*.*?\\*\\/)?\\s*(select|insert|update|delete)\\s+.*?", Pattern.CASE_INSENSITIVE);
private static final AbstractLimitHandler LIMIT_HANDLER = new AbstractLimitHandler() {
@Override

View File

@ -647,4 +647,10 @@ public class PostgreSQL81Dialect extends Dialect {
public boolean supportsJdbcConnectionLobCreation(DatabaseMetaData databaseMetaData) {
return false;
}
@Override
public boolean supportsSelectAliasInGroupByClause() {
return true;
}
}

View File

@ -398,6 +398,24 @@ public abstract class AbstractEntityPersister
protected abstract boolean isClassOrSuperclassTable(int j);
protected boolean isClassOrSuperclassJoin(int j) {
/*
* TODO:
* SingleTableEntityPersister incorrectly used isClassOrSuperclassJoin == isClassOrSuperclassTable,
* this caused HHH-12895, as this resulted in the subclass tables always being joined, even if no
* property on these tables was accessed.
*
* JoinedTableEntityPersister does not use isClassOrSuperclassJoin at all, probably incorrectly so.
* I however haven't been able to reproduce any quirks regarding <join>s, secondary tables or
* @JoinTable's.
*
* Probably this method needs to be properly implemented for the various entity persisters,
* but this at least fixes the SingleTableEntityPersister, while maintaining the the
* previous behaviour for other persisters.
*/
return isClassOrSuperclassTable( j );
}
public abstract int getSubclassTableSpan();
protected abstract int getTableSpan();
@ -4281,7 +4299,7 @@ public abstract class AbstractEntityPersister
Set<String> treatAsDeclarations,
Set<String> referencedTables) {
if ( isClassOrSuperclassTable( subclassTableNumber ) ) {
if ( isClassOrSuperclassJoin( subclassTableNumber ) ) {
String superclassTableName = getSubclassTableName( subclassTableNumber );
if ( referencedTables != null && canOmitSuperclassTableJoin() && !referencedTables.contains(
superclassTableName ) ) {

View File

@ -91,6 +91,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
private final boolean[] subclassTableSequentialSelect;
private final String[][] subclassTableKeyColumnClosure;
private final boolean[] isClassOrSuperclassTable;
private final boolean[] isClassOrSuperclassJoin;
// properties of this class, including inherited properties
private final int[] propertyTableNumbers;
@ -243,6 +244,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
ArrayList<String> subclassTables = new ArrayList<String>();
ArrayList<String[]> joinKeyColumns = new ArrayList<String[]>();
ArrayList<Boolean> isConcretes = new ArrayList<Boolean>();
ArrayList<Boolean> isClassOrSuperclassJoins = new ArrayList<Boolean>();
ArrayList<Boolean> isDeferreds = new ArrayList<Boolean>();
ArrayList<Boolean> isInverses = new ArrayList<Boolean>();
ArrayList<Boolean> isNullables = new ArrayList<Boolean>();
@ -250,6 +252,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
subclassTables.add( qualifiedTableNames[0] );
joinKeyColumns.add( getIdentifierColumnNames() );
isConcretes.add( Boolean.TRUE );
isClassOrSuperclassJoins.add( Boolean.TRUE );
isDeferreds.add( Boolean.FALSE );
isInverses.add( Boolean.FALSE );
isNullables.add( Boolean.FALSE );
@ -257,14 +260,15 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
joinIter = persistentClass.getSubclassJoinClosureIterator();
while ( joinIter.hasNext() ) {
Join join = (Join) joinIter.next();
isConcretes.add( persistentClass.isClassOrSuperclassJoin( join ) );
isDeferreds.add( join.isSequentialSelect() );
isConcretes.add( persistentClass.isClassOrSuperclassTable( join.getTable() ) );
isClassOrSuperclassJoins.add( persistentClass.isClassOrSuperclassJoin( join ) );
isInverses.add( join.isInverse() );
isNullables.add( join.isOptional() );
isLazies.add( lazyAvailable && join.isLazy() );
if ( join.isSequentialSelect() && !persistentClass.isClassOrSuperclassJoin( join ) ) {
hasDeferred = true;
}
boolean isDeferred = join.isSequentialSelect() && ! persistentClass.isClassOrSuperclassJoin( join ) ;
isDeferreds.add( isDeferred );
hasDeferred |= isDeferred;
String joinTableName = determineTableName( join.getTable(), jdbcEnvironment );
subclassTables.add( joinTableName );
@ -284,6 +288,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
subclassTableIsLazyClosure = ArrayHelper.toBooleanArray( isLazies );
subclassTableKeyColumnClosure = ArrayHelper.to2DStringArray( joinKeyColumns );
isClassOrSuperclassTable = ArrayHelper.toBooleanArray( isConcretes );
isClassOrSuperclassJoin = ArrayHelper.toBooleanArray( isClassOrSuperclassJoins );
isInverseSubclassTable = ArrayHelper.toBooleanArray( isInverses );
isNullableSubclassTable = ArrayHelper.toBooleanArray( isNullables );
hasSequentialSelects = hasDeferred;
@ -813,6 +818,10 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
return isClassOrSuperclassTable[j];
}
protected boolean isClassOrSuperclassJoin(int j) {
return isClassOrSuperclassJoin[j];
}
protected boolean isSubclassTableLazy(int j) {
return subclassTableIsLazyClosure[j];
}
@ -847,6 +856,11 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
}
}
@Override
public boolean canOmitSuperclassTableJoin() {
return true;
}
public boolean isMultiTable() {
return getTableSpan() > 1;
}

View File

@ -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.jpa.test.jointable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* @author Christian Beikov
*/
@Entity(name="House")
public class Address {
private Long id;
private String street;
@Id
@GeneratedValue
public Long getId() {
return id;
}
public String getStreet() {
return street;
}
public void setId(Long id) {
this.id = id;
}
public void setStreet(String street) {
this.street = street;
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.jointable;
import org.hibernate.engine.query.spi.HQLQueryPlan;
import org.hibernate.hql.spi.QueryTranslator;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import java.util.Collections;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
/**
* @author Christian Beikov
*/
public class ManyToOneJoinTableTest extends BaseCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] {
Person.class,
Address.class
};
}
@Test
public void testAvoidJoin() {
final HQLQueryPlan plan = sessionFactory().getQueryPlanCache().getHQLQueryPlan(
"SELECT e.id FROM Person e",
false,
Collections.EMPTY_MAP
);
assertEquals( 1, plan.getTranslators().length );
final QueryTranslator translator = plan.getTranslators()[0];
final String generatedSql = translator.getSQLString();
// Ideally, we could detect that *ToOne join tables aren't used, but that requires tracking the uses of properties
// Since *ToOne join tables are treated like secondary or subclass/superclass tables, the proper fix will allow many more optimizations
assertFalse( generatedSql.contains( "join" ) );
}
}

View File

@ -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.jpa.test.jointable;
import javax.persistence.*;
import java.util.Set;
/**
*
* @author Christian Beikov
*/
@Entity
public class Person {
private Long id;
private Address address;
private Set<Address> addresses;
@Id
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@ManyToOne
@JoinTable( name = "SOME_OTHER_TABLE" )
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@OneToMany
@JoinTable( name = "SOME_OTHER_TABLE2" )
public Set<Address> getAddresses() {
return addresses;
}
public void setAddresses(Set<Address> addresses) {
this.addresses = addresses;
}
}

View File

@ -0,0 +1,228 @@
/*
* 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.query;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.jdbc.SQLStatementInterceptor;
import org.junit.Test;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.ManyToOne;
import javax.persistence.Tuple;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertNotNull;
/**
* @author Jan-Willem Gmelig Meyling
* @author Sayra Ranjha
*/
@RequiresDialectFeature(value = DialectChecks.SupportsSelectAliasInGroupByClause.class, jiraKey = "HHH-9301")
public class GroupByAliasTest extends BaseEntityManagerFunctionalTestCase {
public static final int MAX_COUNT = 15;
private SQLStatementInterceptor sqlStatementInterceptor;
@Override
protected void addConfigOptions(Map options) {
sqlStatementInterceptor = new SQLStatementInterceptor( options );
}
@Override
public Class[] getAnnotatedClasses() {
return new Class[] {
Person.class,
Association.class
};
}
@Override
protected void afterEntityManagerFactoryBuilt() {
doInJPA( this::entityManagerFactory, entityManager -> {
for ( int i = 0; i < MAX_COUNT; i++ ) {
Association association = new Association();
association.setId( i );
association.setName(String.format( "Association nr %d", i ) );
Person person = new Person();
person.setId( i );
person.setName( String.format( "Person nr %d", i ) );
person.setAssociation(association);
person.setAge(5);
entityManager.persist( person );
}
} );
}
@Test
public void testSingleIdAlias() {
sqlStatementInterceptor.clear();
List<Tuple> list = doInJPA(this::entityManagerFactory, entityManager -> {
return entityManager.createQuery(
"select p.id as id_alias, sum(p.age) " +
"from Person p group by id_alias order by id_alias", Tuple.class)
.getResultList();
});
String s = sqlStatementInterceptor.getSqlQueries().get(0);
assertNotNull(s);
}
@Test
public void testCompoundIdAlias() {
sqlStatementInterceptor.clear();
List<Tuple> list = doInJPA(this::entityManagerFactory, entityManager -> {
return entityManager.createQuery(
"select p.association as id_alias, sum(p.age) " +
"from Person p group by id_alias order by id_alias", Tuple.class)
.getResultList();
});
String s = sqlStatementInterceptor.getSqlQueries().get(0);
assertNotNull(s);
}
@Test
public void testMultiIdAlias() {
sqlStatementInterceptor.clear();
List<Tuple> list = doInJPA(this::entityManagerFactory, entityManager -> {
return entityManager.createQuery(
"select p.id as id_alias_1, p.association as id_alias_2, sum(p.age) " +
"from Person p group by id_alias_1, id_alias_2 order by id_alias_1, id_alias_2 ", Tuple.class)
.getResultList();
});
String s = sqlStatementInterceptor.getSqlQueries().get(0);
assertNotNull(s);
}
@Entity(name = "Person")
public static class Person {
@Id
private Integer id;
private String name;
private Integer age;
@ManyToOne(cascade = CascadeType.PERSIST)
private Association association;
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 Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Association getAssociation() {
return association;
}
public void setAssociation(Association association) {
this.association = association;
}
}
@IdClass(Association.IdClass.class)
@Entity(name = "Association")
public static class Association {
public static class IdClass implements Serializable {
private Integer id;
private String 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;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IdClass id1 = (IdClass) o;
return Objects.equals(id, id1.id) &&
Objects.equals(name, id1.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
@Id
private Integer id;
@Id
private String 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;
}
}
}

View File

@ -6,7 +6,6 @@
*/
package org.hibernate.query.hhh13670;
import org.hibernate.cfg.Configuration;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Before;
@ -101,6 +100,74 @@ public class HHH13670Test extends BaseCoreFunctionalTestCase {
});
}
@Test
public void testSubTypePropertyReferencedFromEntityJoinInSyntheticSubquery() {
doInJPA(this::sessionFactory, em -> {
List<Tuple> resultList = em.createQuery(
"SELECT subB_0.id, subA_0.id, subB_0.id, subA_0.id FROM SubB subB_0 INNER JOIN SubA subA_0 ON 1=1 WHERE (EXISTS (SELECT 1 FROM subB_0.parent _synth_subquery_0 WHERE subA_0.id = _synth_subquery_0.id)) ORDER BY subB_0.id ASC, subA_0.id ASC", Tuple.class)
.getResultList();
assertEquals(1, resultList.size());
});
}
@Test
public void testSubTypePropertyReferencedFromEntityJoinInSyntheticSubquery2() {
doInJPA(this::sessionFactory, em -> {
List<Tuple> resultList = em.createQuery(
"SELECT subB_0.id, subA_0.id, subB_0.id, subA_0.id FROM SubB subB_0 INNER JOIN SubA subA_0 ON 1=1 WHERE (EXISTS (SELECT 1 FROM Super s WHERE subA_0.id = s.parent.id)) ORDER BY subB_0.id ASC, subA_0.id ASC", Tuple.class)
.getResultList();
assertEquals(4, resultList.size());
});
}
@Test
public void testSubTypePropertyReferencedFromEntityJoinInSyntheticSubquery3() {
doInJPA(this::sessionFactory, em -> {
List<Tuple> resultList = em.createQuery(
"SELECT subB_0.id, subA_0.id, subB_0.id, subA_0.id FROM SubB subB_0 INNER JOIN SubA subA_0 ON 1=1 WHERE (EXISTS (SELECT 1 FROM Super s WHERE s.id = subB_0.parent.id)) ORDER BY subB_0.id ASC, subA_0.id ASC", Tuple.class)
.getResultList();
assertEquals(6, resultList.size());
});
}
@Test
public void testSubTypePropertyReferencedFromEntityJoinInSyntheticSubquery4() {
doInJPA(this::sessionFactory, em -> {
List<Tuple> resultList = em.createQuery(
"SELECT subB_0.id, subA_0.id, subB_0.id, subA_0.id FROM SubB subB_0 INNER JOIN SubA subA_0 ON 1=1 WHERE (EXISTS (SELECT 1 FROM Super s WHERE s.id = subA_0.parent.id)) ORDER BY subB_0.id ASC, subA_0.id ASC", Tuple.class)
.getResultList();
assertEquals(0, resultList.size());
});
}
@Test
public void testSubTypePropertyReferencedFromWhereClause() {
doInJPA(this::sessionFactory, em -> {
List<Tuple> resultList = em.createQuery("SELECT subB_0.id FROM SubB subB_0 WHERE subB_0.parent.id IS NOT NULL", Tuple.class)
.getResultList();
});
}
@Test
public void testSubTypePropertyReferencedFromGroupByClause() {
doInJPA(this::sessionFactory, em -> {
List<Tuple> resultList = em.createQuery("SELECT subB_0.id FROM SubB subB_0 GROUP BY subB_0.id , subB_0.parent.id", Tuple.class)
.getResultList();
});
}
@Test
public void testSubTypePropertyReferencedFromOrderByClause() {
doInJPA(this::sessionFactory, em -> {
List<Tuple> resultList = em.createQuery("SELECT subB_0.id FROM SubB subB_0 ORDER BY subB_0.id , subB_0.parent.id", Tuple.class)
.getResultList();
});
}
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { Super.class, SubA.class, SubB.class };

View File

@ -161,8 +161,33 @@ public class QueryHintTest extends BaseNonConfigCoreFunctionalTestCase {
assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).contains( "/* My_Query */ select /*+ ALL_ROWS */" ) );
sqlStatementInterceptor.clear();
}
@Test
@TestForIssue( jiraKey = "HHH-13608")
public void testQueryHintCaseInsensitive() {
sqlStatementInterceptor.clear();
doInHibernate( this::sessionFactory, s -> {
List results = s.createNativeQuery(
"SELECT e.id as id " +
"FROM Employee e " +
"JOIN Department d ON e.department_id = d.id " +
"WHERE d.name = :departmentName" )
.addQueryHint( "ALL_ROWS" )
.setComment( "My_Query" )
.setParameter( "departmentName", "Sales" )
.getResultList();
assertEquals(results.size(), 2);
} );
sqlStatementInterceptor.assertExecutedCount( 1 );
assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).contains( "/* My_Query */ SELECT /*+ ALL_ROWS */" ) );
sqlStatementInterceptor.clear();
}
@Entity
@Entity(name = "Employee")
public static class Employee {
@Id
@GeneratedValue
@ -171,8 +196,8 @@ public class QueryHintTest extends BaseNonConfigCoreFunctionalTestCase {
@ManyToOne(fetch = FetchType.LAZY)
public Department department;
}
@Entity
@Entity(name = "Department")
public static class Department {
@Id
@GeneratedValue

View File

@ -266,6 +266,12 @@ abstract public class DialectChecks {
}
}
public static class SupportsSelectAliasInGroupByClause implements DialectCheck {
public boolean isMatch(Dialect dialect) {
return dialect.supportsSelectAliasInGroupByClause();
}
}
public static class SupportsNClob implements DialectCheck {
@Override
public boolean isMatch(Dialect dialect) {