HHH-11157 : Add property for disabling subquery join rewrites and handle mysql quoted identifiers

(cherry picked from commit 556aa265c0)
This commit is contained in:
Christian Beikov 2016-10-21 18:14:42 +02:00 committed by Gail Badner
parent 4a697148e6
commit f631cb4da5
9 changed files with 89 additions and 53 deletions

View File

@ -57,50 +57,7 @@ import org.hibernate.tuple.entity.EntityTuplizer;
import org.hibernate.tuple.entity.EntityTuplizerFactory;
import org.jboss.logging.Logger;
import static org.hibernate.cfg.AvailableSettings.ACQUIRE_CONNECTIONS;
import static org.hibernate.cfg.AvailableSettings.AUTO_CLOSE_SESSION;
import static org.hibernate.cfg.AvailableSettings.AUTO_EVICT_COLLECTION_CACHE;
import static org.hibernate.cfg.AvailableSettings.AUTO_SESSION_EVENTS_LISTENER;
import static org.hibernate.cfg.AvailableSettings.BATCH_FETCH_STYLE;
import static org.hibernate.cfg.AvailableSettings.BATCH_VERSIONED_DATA;
import static org.hibernate.cfg.AvailableSettings.CACHE_REGION_PREFIX;
import static org.hibernate.cfg.AvailableSettings.CHECK_NULLABILITY;
import static org.hibernate.cfg.AvailableSettings.CUSTOM_ENTITY_DIRTINESS_STRATEGY;
import static org.hibernate.cfg.AvailableSettings.DEFAULT_BATCH_FETCH_SIZE;
import static org.hibernate.cfg.AvailableSettings.DEFAULT_ENTITY_MODE;
import static org.hibernate.cfg.AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS;
import static org.hibernate.cfg.AvailableSettings.FLUSH_BEFORE_COMPLETION;
import static org.hibernate.cfg.AvailableSettings.GENERATE_STATISTICS;
import static org.hibernate.cfg.AvailableSettings.HQL_BULK_ID_STRATEGY;
import static org.hibernate.cfg.AvailableSettings.INTERCEPTOR;
import static org.hibernate.cfg.AvailableSettings.JPAQL_STRICT_COMPLIANCE;
import static org.hibernate.cfg.AvailableSettings.JTA_TRACK_BY_THREAD;
import static org.hibernate.cfg.AvailableSettings.LOG_SESSION_METRICS;
import static org.hibernate.cfg.AvailableSettings.MAX_FETCH_DEPTH;
import static org.hibernate.cfg.AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER;
import static org.hibernate.cfg.AvailableSettings.ORDER_INSERTS;
import static org.hibernate.cfg.AvailableSettings.ORDER_UPDATES;
import static org.hibernate.cfg.AvailableSettings.PREFER_USER_TRANSACTION;
import static org.hibernate.cfg.AvailableSettings.PROCEDURE_NULL_PARAM_PASSING;
import static org.hibernate.cfg.AvailableSettings.QUERY_CACHE_FACTORY;
import static org.hibernate.cfg.AvailableSettings.QUERY_STARTUP_CHECKING;
import static org.hibernate.cfg.AvailableSettings.QUERY_SUBSTITUTIONS;
import static org.hibernate.cfg.AvailableSettings.RELEASE_CONNECTIONS;
import static org.hibernate.cfg.AvailableSettings.SESSION_FACTORY_NAME;
import static org.hibernate.cfg.AvailableSettings.SESSION_FACTORY_NAME_IS_JNDI;
import static org.hibernate.cfg.AvailableSettings.STATEMENT_BATCH_SIZE;
import static org.hibernate.cfg.AvailableSettings.STATEMENT_FETCH_SIZE;
import static org.hibernate.cfg.AvailableSettings.STATEMENT_INSPECTOR;
import static org.hibernate.cfg.AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES;
import static org.hibernate.cfg.AvailableSettings.USE_GET_GENERATED_KEYS;
import static org.hibernate.cfg.AvailableSettings.USE_IDENTIFIER_ROLLBACK;
import static org.hibernate.cfg.AvailableSettings.USE_MINIMAL_PUTS;
import static org.hibernate.cfg.AvailableSettings.USE_QUERY_CACHE;
import static org.hibernate.cfg.AvailableSettings.USE_SCROLLABLE_RESULTSET;
import static org.hibernate.cfg.AvailableSettings.USE_SECOND_LEVEL_CACHE;
import static org.hibernate.cfg.AvailableSettings.USE_SQL_COMMENTS;
import static org.hibernate.cfg.AvailableSettings.USE_STRUCTURED_CACHE;
import static org.hibernate.cfg.AvailableSettings.WRAP_RESULT_SETS;
import static org.hibernate.cfg.AvailableSettings.*;
import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN;
/**
@ -527,6 +484,7 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
private boolean strictJpaQueryLanguageCompliance;
private boolean namedQueryStartupCheckingEnabled;
private final boolean procedureParameterNullPassingEnabled;
private final boolean collectionJoinSubqueryRewriteEnabled;
// Caching
private boolean secondLevelCacheEnabled;
@ -643,6 +601,7 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
this.strictJpaQueryLanguageCompliance = cfgService.getSetting( JPAQL_STRICT_COMPLIANCE, BOOLEAN, false );
this.namedQueryStartupCheckingEnabled = cfgService.getSetting( QUERY_STARTUP_CHECKING, BOOLEAN, true );
this.procedureParameterNullPassingEnabled = cfgService.getSetting( PROCEDURE_NULL_PARAM_PASSING, BOOLEAN, false );
this.collectionJoinSubqueryRewriteEnabled = cfgService.getSetting( COLLECTION_JOIN_SUBQUERY, BOOLEAN, true );
this.secondLevelCacheEnabled = cfgService.getSetting( USE_SECOND_LEVEL_CACHE, BOOLEAN, true );
this.queryCacheEnabled = cfgService.getSetting( USE_QUERY_CACHE, BOOLEAN, false );
@ -897,6 +856,11 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
return procedureParameterNullPassingEnabled;
}
@Override
public boolean isCollectionJoinSubqueryRewriteEnabled() {
return collectionJoinSubqueryRewriteEnabled;
}
@Override
public boolean isSecondLevelCacheEnabled() {
return secondLevelCacheEnabled;
@ -1173,6 +1137,11 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
return options.isProcedureParameterNullPassingEnabled();
}
@Override
public boolean isCollectionJoinSubqueryRewriteEnabled() {
return options.isCollectionJoinSubqueryRewriteEnabled();
}
@Override
public boolean isSecondLevelCacheEnabled() {
return options.isSecondLevelCacheEnabled();

View File

@ -8,7 +8,6 @@ package org.hibernate.boot.internal;
import java.util.Map;
import org.hibernate.ConnectionAcquisitionMode;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.CustomEntityDirtinessStrategy;
import org.hibernate.EntityMode;
@ -90,6 +89,7 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
private final boolean strictJpaQueryLanguageCompliance;
private final boolean namedQueryStartupCheckingEnabled;
private final boolean procedureParameterNullPassingEnabled;
private final boolean collectionJoinSubqueryRewriteEnabled;
// Caching
private final boolean secondLevelCacheEnabled;
@ -161,6 +161,7 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
this.strictJpaQueryLanguageCompliance = state.isStrictJpaQueryLanguageCompliance();
this.namedQueryStartupCheckingEnabled = state.isNamedQueryStartupCheckingEnabled();
this.procedureParameterNullPassingEnabled = state.isProcedureParameterNullPassingEnabled();
this.collectionJoinSubqueryRewriteEnabled = state.isCollectionJoinSubqueryRewriteEnabled();
this.secondLevelCacheEnabled = state.isSecondLevelCacheEnabled();
this.queryCacheEnabled = state.isQueryCacheEnabled();
@ -343,6 +344,11 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
return procedureParameterNullPassingEnabled;
}
@Override
public boolean isCollectionJoinSubqueryRewriteEnabled() {
return collectionJoinSubqueryRewriteEnabled;
}
@Override
public boolean isSecondLevelCacheEnabled() {
return secondLevelCacheEnabled;

View File

@ -101,6 +101,8 @@ public interface SessionFactoryOptionsState {
boolean isProcedureParameterNullPassingEnabled();
boolean isCollectionJoinSubqueryRewriteEnabled();
boolean isSecondLevelCacheEnabled();
boolean isQueryCacheEnabled();

View File

@ -204,6 +204,10 @@ public abstract class AbstractDelegatingSessionFactoryOptions implements Session
}
@Override
public boolean isCollectionJoinSubqueryRewriteEnabled() {
return delegate.isCollectionJoinSubqueryRewriteEnabled();
}
public boolean isSecondLevelCacheEnabled() {
return delegate.isSecondLevelCacheEnabled();
}

View File

@ -8,7 +8,6 @@ package org.hibernate.boot.spi;
import java.util.Map;
import org.hibernate.ConnectionAcquisitionMode;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.CustomEntityDirtinessStrategy;
import org.hibernate.EntityMode;
@ -20,7 +19,6 @@ import org.hibernate.SessionFactoryObserver;
import org.hibernate.boot.SchemaAutoTooling;
import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.cache.spi.CacheKeysFactory;
import org.hibernate.cache.spi.QueryCacheFactory;
import org.hibernate.cfg.BaselineSessionEventsListenerBuilder;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
@ -182,4 +180,6 @@ public interface SessionFactoryOptions {
boolean isPreferUserTransaction();
boolean isProcedureParameterNullPassingEnabled();
boolean isCollectionJoinSubqueryRewriteEnabled();
}

View File

@ -1265,4 +1265,14 @@ public interface AvailableSettings {
* @since 5.1
*/
String CREATE_EMPTY_COMPOSITES_ENABLED = "hibernate.create_empty_composites.enabled";
/**
* Setting which indicates whether or not the new JOINS over collection tables should be rewritten to subqueries.
* <p/>
* Default is {@code true}. Existing applications may want to disable this (set it {@code false}) for
* upgrade compatibility.
*
* @since 5.2, 5.1.8
*/
String COLLECTION_JOIN_SUBQUERY = "hibernate.collection_join_subquery";
}

View File

@ -34,6 +34,7 @@ import org.hibernate.type.AssociationType;
*/
public class JoinSequence {
private final SessionFactoryImplementor factory;
private final boolean collectionJoinSubquery;
private final StringBuilder conditions = new StringBuilder();
private final List<Join> joins = new ArrayList<Join>();
@ -52,6 +53,7 @@ public class JoinSequence {
*/
public JoinSequence(SessionFactoryImplementor factory) {
this.factory = factory;
this.collectionJoinSubquery = factory.getSessionFactoryOptions().isCollectionJoinSubqueryRewriteEnabled();
}
/**
@ -196,7 +198,8 @@ public class JoinSequence {
last = rootJoinable;
}
else if (
withClauseFragment != null
collectionJoinSubquery
&& withClauseFragment != null
&& joins.size() > 1
&& withClauseFragment.contains( withClauseJoinAlias )
&& ( first = ( iter = joins.iterator() ).next() ).joinType == JoinType.LEFT_OUTER_JOIN
@ -212,6 +215,7 @@ public class JoinSequence {
"([a-zA-Z0-9_]+) | " + // Normal identifiers
"('[a-zA-Z0-9_]+' ((''[a-zA-Z0-9_]+)+')?) | " + // Single quoted identifiers
"(\"[a-zA-Z0-9_]+\" ((\"\"[a-zA-Z0-9_]+)+\")?) | " + // Double quoted identifiers
"(`[a-zA-Z0-9_]+` ((``[a-zA-Z0-9_]+)+`)?) | " + // MySQL quoted identifiers
"(\\[[a-zA-Z0-9_\\s]+\\])" + // MSSQL quoted identifiers
")"
);

View File

@ -12,6 +12,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -19,6 +20,8 @@ import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
@ -186,6 +189,29 @@ public class WithClauseTest extends BaseCoreFunctionalTestCase {
data.cleanup();
}
@Test
@TestForIssue(jiraKey = "HHH-11157")
public void testWithClauseAsNonSubqueryWithKey() {
rebuildSessionFactory( Collections.singletonMap( AvailableSettings.COLLECTION_JOIN_SUBQUERY, "false" ) );
TestData data = new TestData();
data.prepare();
Session s = openSession();
Transaction txn = s.beginTransaction();
// Since family has a join table, we will first left join all family members and then do the WITH clause on the target entity table join
// Normally this produces 2 results which is wrong and can only be circumvented by converting the join table and target entity table join to a subquery
List list = s.createQuery("from Human h left join h.family as f with key(f) like 'son1' where h.description = 'father'")
.list();
assertEquals("subquery rewriting of join table was not disabled", 2, list.size());
txn.commit();
s.close();
data.cleanup();
}
private class TestData {
public void prepare() {
Session session = openSession();

View File

@ -9,10 +9,7 @@ package org.hibernate.testing.junit4;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.*;
import javax.persistence.SharedCacheMode;
import org.hibernate.HibernateException;
@ -97,8 +94,17 @@ public abstract class BaseCoreFunctionalTestCase extends BaseUnitTestCase {
@BeforeClassOnce
@SuppressWarnings( {"UnusedDeclaration"})
protected void buildSessionFactory() {
buildSessionFactory( emptyPropertyMap() );
}
protected void buildSessionFactory(Map<String, String> properties) {
// for now, build the configuration to get all the property settings
configuration = constructAndConfigureConfiguration();
if ( properties != null && !properties.isEmpty() ) {
for ( Map.Entry<String, String> entry : properties.entrySet() ) {
configuration.setProperty( entry.getKey(), entry.getValue() );
}
}
BootstrapServiceRegistry bootRegistry = buildBootstrapServiceRegistry();
serviceRegistry = buildServiceRegistry( bootRegistry, configuration );
// this is done here because Configuration does not currently support 4.0 xsd
@ -108,6 +114,15 @@ public abstract class BaseCoreFunctionalTestCase extends BaseUnitTestCase {
}
protected void rebuildSessionFactory() {
rebuildSessionFactory( emptyPropertyMap() );
}
private Map<String,String> emptyPropertyMap() {
Map<String, String> map = Collections.emptyMap();
return map;
}
protected void rebuildSessionFactory(Map<String, String> properties) {
if ( sessionFactory == null ) {
return;
}
@ -121,7 +136,7 @@ public abstract class BaseCoreFunctionalTestCase extends BaseUnitTestCase {
catch (Exception ignore) {
}
buildSessionFactory();
buildSessionFactory( properties );
}
protected Configuration buildConfiguration() {