diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index f566d03caf27..3ba29c0df717 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -6208,4 +6208,20 @@ public boolean supportsRowValueConstructorSyntaxInInSubQuery() { return supportsRowValueConstructorSyntaxInInList(); } + /** + * This pattern avoids the SQL length and performance issues of large disjunctions, + * and emulates tuple-based IN-list comparisons using a derived VALUES table. + *
+ * For example: + *
+ * SELECT * FROM EntityTable T + * WHERE EXISTS ( + * SELECT 1 FROM (VALUES (?, ?), (?, ?)) AS V(FIELD1, FIELD2) + * WHERE T.FIELD1 = V.FIELD1 AND T.FIELD2 = V.FIELD2 + * ) + *+ */ + public boolean supportsValuesListForInListExistsEmulation() { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java index cce6a6d51412..48480f626c4d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -1263,4 +1263,8 @@ public boolean supportsRowValueConstructorSyntaxInInList() { return false; } + @Override + public boolean supportsValuesListForInListExistsEmulation() { + return getVersion().isSameOrAfter( 10 ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index d140524d8bd0..c0c122b50a24 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -7642,6 +7642,41 @@ public void visitInListPredicate(InListPredicate inListPredicate) { getSqlTuple( listExpression ) .getExpressions().get( 0 ); } + else if ( dialect.supportsValuesListForInListExistsEmulation() ) { + if ( inListPredicate.isNegated() ) { + appendSql( "not " ); + } + + appendSql( "exists (select 1 from (values " ); + for ( int i = 0; i < listExpressions.size(); i++ ) { + if ( i > 0 ) { + appendSql( ", " ); + } + appendSql( OPEN_PARENTHESIS ); + renderCommaSeparatedSelectExpression( List.of( listExpressions.get( i ) ) ); + appendSql( CLOSE_PARENTHESIS ); + } + appendSql( ") as v(" ); + + final List extends SqlAstNode> expressions = lhsTuple.getExpressions(); + for ( int i = 0; i < expressions.size(); i++ ) { + if ( i > 0 ) { + appendSql( ", " ); + } + appendSql( "col_" + i ); + } + appendSql( ") where " ); + + for ( int i = 0; i < expressions.size(); i++ ) { + if ( i > 0 ) { + appendSql( " and " ); + } + expressions.get( i ).accept( this ); + appendSql( " = v.col_" + i ); + } + appendSql( CLOSE_PARENTHESIS ); + return; + } else if ( !dialect.supportsRowValueConstructorSyntaxInInList() ) { final ComparisonOperator comparisonOperator = inListPredicate.isNegated() ? ComparisonOperator.NOT_EQUAL : diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/SQLServerDialectCompositeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/SQLServerDialectCompositeTest.java new file mode 100644 index 000000000000..fe043b4e07e5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/SQLServerDialectCompositeTest.java @@ -0,0 +1,98 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.dialect.functional; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.orm.test.jpa.CompositeId; +import org.hibernate.orm.test.jpa.EntityWithCompositeId; +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Rob Green + */ +@JiraKey( value = "HHH-8370" ) +@RequiresDialect( value = SQLServerDialect.class, majorVersion = 10 ) +@Jpa( + annotatedClasses = { EntityWithCompositeId.class }, + integrationSettings = { + @Setting( name = AvailableSettings.USE_SQL_COMMENTS, value = "true" ), + }, + useCollectingStatementInspector = true +) +public class SQLServerDialectCompositeTest { + @Test + public void testCompositeQueryWithInPredicate(EntityManagerFactoryScope scope) { + final SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector(); + sqlStatementInterceptor.clear(); + + List