Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java
Original file line number Diff line number Diff line change
Expand Up @@ -6208,4 +6208,25 @@ public boolean supportsRowValueConstructorSyntaxInInSubQuery() {
return supportsRowValueConstructorSyntaxInInList();
}

/**
* If the dialect supports {@link org.hibernate.dialect.Dialect#supportsRowValueConstructorSyntax() row values},
* does it allow using them in a derived table within an EXISTS predicate that emulates an IN-list?
* <p>
* For example:
* <pre>
* SELECT * FROM EntityTable T
* WHERE EXISTS (
* SELECT 1 FROM (VALUES (?, ?), (?, ?)) AS V(FIELD1, FIELD2)
* WHERE T.FIELD1 = V.FIELD1 AND T.FIELD2 = V.FIELD2
* )
* </pre>
* This pattern avoids the SQL length and performance issues of large disjunctions,
* and emulates tuple-based IN-list comparisons using a derived VALUES table.
*
* @return True if this SQL dialect is known to support row value constructors in
* derived tables for EXISTS-based IN-list emulation; false otherwise.
*/
public boolean supportsRowValueConstructorSyntaxInDerivedTableInList() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1245,7 +1245,7 @@ public boolean supportsSimpleQueryGrouping() {

@Override
public boolean supportsRowValueConstructorSyntax() {
return false;
return getVersion().isSameOrAfter( 10 );
}

@Override
Expand All @@ -1263,4 +1263,8 @@ public boolean supportsRowValueConstructorSyntaxInInList() {
return false;
}

@Override
public boolean supportsRowValueConstructorSyntaxInDerivedTableInList() {
return getVersion().isSameOrAfter( 10 );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The minimum supported version is already 11.x (SQL Server 2012), so no need for this I think.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import org.hibernate.AssertionFailure;
import org.hibernate.Internal;
Expand Down Expand Up @@ -7642,6 +7643,52 @@ public void visitInListPredicate(InListPredicate inListPredicate) {
getSqlTuple( listExpression )
.getExpressions().get( 0 );
}
else if ( dialect.supportsRowValueConstructorSyntaxInDerivedTableInList() ) {
if ( inListPredicate.isNegated() ) {
appendSql( "not " );
}
appendSql( "exists (select 1 from (values " );

List<SqlTuple> listTuples = listExpressions.stream()
.map( SqlTupleContainer::getSqlTuple )
.toList();
for ( int i = 0; i < listTuples.size(); i++ ) {
if ( i > 0 ) {
appendSql( ", " );
}

appendSql( OPEN_PARENTHESIS );
renderCommaSeparatedSelectExpression( listTuples.get(i).getExpressions() );
appendSql( CLOSE_PARENTHESIS );
}

List<Expression> expressions = lhsTuple.getExpressions()
.stream()
.filter( expression -> expression.getColumnReference() != null )
.collect( Collectors.toList() );
appendSql( ") as v(" );
for ( int i = 0; i < expressions.size(); i++ ) {
if ( i > 0 ) {
appendSql( ", " );
}
appendSql( expressions.get( i ).getColumnReference().getColumnExpression() );
}

appendSql( ") where " );
for ( int i = 0; i < expressions.size(); i++ ) {
if ( i > 0 ) {
appendSql( " and " );
}

Expression expression = expressions.get( i );
expression.accept( this );
appendSql( " = v." );
appendSql( expression.getColumnReference().getColumnExpression() );
}

appendSql( CLOSE_PARENTHESIS );
return;
}
else if ( !dialect.supportsRowValueConstructorSyntaxInInList() ) {
final ComparisonOperator comparisonOperator = inListPredicate.isNegated() ?
ComparisonOperator.NOT_EQUAL :
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CompositeId> compositeIds = new ArrayList<>();
compositeIds.add( new CompositeId( 1,2 ) );
compositeIds.add( new CompositeId( 3,4 ) );

scope.inTransaction( entityManager -> {
entityManager.createQuery( "SELECT e FROM EntityWithCompositeId e WHERE e.id IN (:ids)" )
.setParameter( "ids", compositeIds )
.getResultList();
}
);

var query = sqlStatementInterceptor.getSqlQueries().get( 0 );
assertTrue( query.endsWith( "where exists (select 1 from (values (?,?), (?,?)) as v(id1, id2) where ewci1_0.id1 = v.id1 and ewci1_0.id2 = v.id2)" ) );
}

@Test
public void testCompositeQueryWithNotInPredicate(EntityManagerFactoryScope scope) {
final SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
sqlStatementInterceptor.clear();

List<CompositeId> compositeIds = new ArrayList<>();
compositeIds.add( new CompositeId( 1,2 ) );
compositeIds.add( new CompositeId( 3,4 ) );

scope.inTransaction( entityManager -> {
entityManager.createQuery( "SELECT e FROM EntityWithCompositeId e WHERE e.id NOT IN (:ids)" )
.setParameter( "ids", compositeIds )
.getResultList();
}
);

var query = sqlStatementInterceptor.getSqlQueries().get( 0 );
assertTrue( query.endsWith( "where not exists (select 1 from (values (?,?), (?,?)) as v(id1, id2) where ewci1_0.id1 = v.id1 and ewci1_0.id2 = v.id2)" ) );
}

@Test
public void testCompositeQueryWithMultiplePredicatesIncludingIn(EntityManagerFactoryScope scope) {
final SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
sqlStatementInterceptor.clear();

List<CompositeId> compositeIds = new ArrayList<>();
compositeIds.add( new CompositeId( 1,2 ) );
compositeIds.add( new CompositeId( 3,4 ) );

scope.inTransaction( entityManager -> {
entityManager.createQuery( "SELECT e FROM EntityWithCompositeId e WHERE e.description = :description AND e.id IN (:ids)" )
.setParameter( "ids", compositeIds )
.setParameter( "description", "test" )
.getResultList();
}
);

var query = sqlStatementInterceptor.getSqlQueries().get( 0 );
assertTrue( query.endsWith( "where ewci1_0.description=? and exists (select 1 from (values (?,?), (?,?)) as v(id1, id2) where ewci1_0.id1 = v.id1 and ewci1_0.id2 = v.id2)" ) );
}
}
Loading