Add property for disabling subquery join rewrites and handle mysql quoted identifiers

This commit is contained in:
Christian Beikov 2016-10-21 18:14:42 +02:00 committed by Steve Ebersole
parent 9e8fd60e26
commit 556aa265c0
9 changed files with 84 additions and 55 deletions

View File

@ -61,55 +61,7 @@ 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.ALLOW_JTA_TRANSACTION_ACCESS;
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.CONNECTION_HANDLING;
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.JDBC_TIME_ZONE;
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.SESSION_SCOPED_INTERCEPTOR;
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.ALLOW_UPDATE_OUTSIDE_TRANSACTION;
import static org.hibernate.cfg.AvailableSettings.*;
import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN;
import static org.hibernate.jpa.AvailableSettings.DISCARD_PC_ON_CLOSE;
@ -571,6 +523,7 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
private boolean strictJpaQueryLanguageCompliance;
private boolean namedQueryStartupCheckingEnabled;
private final boolean procedureParameterNullPassingEnabled;
private final boolean collectionJoinSubqueryRewriteEnabled;
// Caching
private boolean secondLevelCacheEnabled;
@ -690,6 +643,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 );
@ -1086,6 +1040,11 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
return procedureParameterNullPassingEnabled;
}
@Override
public boolean isCollectionJoinSubqueryRewriteEnabled() {
return collectionJoinSubqueryRewriteEnabled;
}
@Override
public boolean isSecondLevelCacheEnabled() {
return secondLevelCacheEnabled;
@ -1391,6 +1350,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

@ -96,6 +96,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;
@ -173,6 +174,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();
@ -372,6 +374,11 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
return procedureParameterNullPassingEnabled;
}
@Override
public boolean isCollectionJoinSubqueryRewriteEnabled() {
return collectionJoinSubqueryRewriteEnabled;
}
@Override
public boolean isAllowOutOfTransactionUpdateOperations() {
return allowOutOfTransactionUpdateOperations;

View File

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

View File

@ -214,6 +214,11 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
return delegate.isProcedureParameterNullPassingEnabled();
}
@Override
public boolean isCollectionJoinSubqueryRewriteEnabled() {
return delegate.isCollectionJoinSubqueryRewriteEnabled();
}
@Override
public boolean isAllowOutOfTransactionUpdateOperations() {
return delegate.isAllowOutOfTransactionUpdateOperations();

View File

@ -202,6 +202,8 @@ public interface SessionFactoryOptions {
boolean isProcedureParameterNullPassingEnabled();
boolean isCollectionJoinSubqueryRewriteEnabled();
boolean isAllowOutOfTransactionUpdateOperations();
boolean isReleaseResourcesOnCloseEnabled();

View File

@ -1548,4 +1548,14 @@ public interface AvailableSettings {
* @since 5.2
*/
String ALLOW_UPDATE_OUTSIDE_TRANSACTION = "hibernate.allow_update_outside_transaction";
/**
* 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
*/
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();
}
/**
@ -192,7 +194,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
@ -208,6 +211,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

@ -7,6 +7,7 @@
package org.hibernate.test.hql;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -15,6 +16,7 @@ import org.hibernate.Query;
import org.hibernate.QueryException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.hql.internal.ast.InvalidWithClauseException;
import org.hibernate.testing.TestForIssue;
@ -220,6 +222,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;
@ -98,8 +95,17 @@ public abstract class BaseCoreFunctionalTestCase extends BaseUnitTestCase {
@BeforeClassOnce
@SuppressWarnings( {"UnusedDeclaration"})
protected void buildSessionFactory() {
buildSessionFactory( Collections.emptyMap() );
}
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
@ -109,6 +115,10 @@ public abstract class BaseCoreFunctionalTestCase extends BaseUnitTestCase {
}
protected void rebuildSessionFactory() {
rebuildSessionFactory( Collections.emptyMap() );
}
protected void rebuildSessionFactory(Map<String, String> properties) {
if ( sessionFactory == null ) {
return;
}
@ -122,7 +132,7 @@ public abstract class BaseCoreFunctionalTestCase extends BaseUnitTestCase {
catch (Exception ignore) {
}
buildSessionFactory();
buildSessionFactory( properties );
}
protected Configuration buildConfiguration() {