HHH-13796 - Missing from clause in query from BinaryLogicOperatorNode row value constructor translation

https://hibernate.atlassian.net/browse/HHH-13796
This commit is contained in:
Jan-Willem Gmelig Meyling 2019-12-15 22:24:44 +01:00 committed by Andrea Boriero
parent 38f0131160
commit 85bfcc6e21
10 changed files with 223 additions and 59 deletions

View File

@ -24,7 +24,7 @@
*
* @author Steve Ebersole
*/
public abstract class AbstractMapComponentNode extends FromReferenceNode implements HqlSqlTokenTypes {
public abstract class AbstractMapComponentNode extends FromReferenceNode implements HqlSqlTokenTypes, TableReferenceNode {
private FromElement mapFromElement;
private String[] columns;

View File

@ -159,35 +159,43 @@ protected void translate(
ParameterSpecification lhsEmbeddedCompositeParameterSpecification,
ParameterSpecification rhsEmbeddedCompositeParameterSpecification,
AST container) {
Node leftHandOperand = this.getLeftHandOperand();
Node rightHandOperand = this.getRightHandOperand();
for ( int i = valueElements - 1; i > 0; i-- ) {
if ( i == 1 ) {
AST op1 = getASTFactory().create( comparisonType, comparisonText );
AST lhs1 = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, lhsElementTexts[0] );
AST rhs1 = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, rhsElementTexts[0] );
SqlFragment lhs1 = (SqlFragment) getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, lhsElementTexts[0] );
SqlFragment rhs1 = (SqlFragment) getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, rhsElementTexts[0] );
copyReferencedTables( leftHandOperand, lhs1 );
copyReferencedTables( rightHandOperand, rhs1 );
op1.setFirstChild( lhs1 );
lhs1.setNextSibling( rhs1 );
container.setFirstChild( op1 );
AST op2 = getASTFactory().create( comparisonType, comparisonText );
AST lhs2 = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, lhsElementTexts[1] );
AST rhs2 = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, rhsElementTexts[1] );
SqlFragment lhs2 = (SqlFragment) getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, lhsElementTexts[1] );
SqlFragment rhs2 = (SqlFragment) getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, rhsElementTexts[1] );
copyReferencedTables( leftHandOperand, lhs2 );
copyReferencedTables( rightHandOperand, rhs2 );
op2.setFirstChild( lhs2 );
lhs2.setNextSibling( rhs2 );
op1.setNextSibling( op2 );
// "pass along" our initial embedded parameter node(s) to the first generated
// sql fragment so that it can be handled later for parameter binding...
SqlFragment fragment = (SqlFragment) lhs1;
if ( lhsEmbeddedCompositeParameterSpecification != null ) {
fragment.addEmbeddedParameter( lhsEmbeddedCompositeParameterSpecification );
lhs1.addEmbeddedParameter( lhsEmbeddedCompositeParameterSpecification );
}
if ( rhsEmbeddedCompositeParameterSpecification != null ) {
fragment.addEmbeddedParameter( rhsEmbeddedCompositeParameterSpecification );
lhs1.addEmbeddedParameter( rhsEmbeddedCompositeParameterSpecification );
}
}
else {
AST op = getASTFactory().create( comparisonType, comparisonText );
AST lhs = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, lhsElementTexts[i] );
AST rhs = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, rhsElementTexts[i] );
SqlFragment lhs = (SqlFragment) getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, lhsElementTexts[i] );
SqlFragment rhs = (SqlFragment) getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, rhsElementTexts[i] );
copyReferencedTables( leftHandOperand, lhs );
copyReferencedTables( rightHandOperand, rhs );
op.setFirstChild( lhs );
lhs.setNextSibling( rhs );
AST newContainer = getASTFactory().create( container.getType(), container.getText() );
@ -198,6 +206,13 @@ protected void translate(
}
}
private static void copyReferencedTables(Node from, SqlFragment to) {
if (from instanceof TableReferenceNode) {
TableReferenceNode tableReferenceNode = (TableReferenceNode) from;
to.setReferencedTables( tableReferenceNode.getReferencedTables() );
}
}
protected static String[] extractMutationTexts(Node operand, int count) {
if ( operand instanceof ParameterNode ) {
String[] rtn = new String[count];

View File

@ -36,7 +36,7 @@
*
* @author Joshua Davis
*/
public class DotNode extends FromReferenceNode implements DisplayableNode, SelectExpression {
public class DotNode extends FromReferenceNode implements DisplayableNode, SelectExpression, TableReferenceNode {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( DotNode.class );
///////////////////////////////////////////////////////////////////////////
@ -77,12 +77,6 @@ public static enum DereferenceType {
*/
private String propertyName;
/**
* The identifier that is the name of the property. In comparison with {@link #propertyName}
* it is always identical with identifier in the query, it is not changed during processing.
*/
private String originalPropertyName;
/**
* The full path, to the root alias of this dot node.
*/
@ -168,7 +162,6 @@ public void resolveFirstChild() throws SemanticException {
// Set the attributes of the property reference expression.
String propName = property.getText();
propertyName = propName;
originalPropertyName = propName;
// If the uresolved property path isn't set yet, just use the property name.
if ( propertyPath == null ) {
propertyPath = propName;
@ -703,21 +696,15 @@ public Type getDataType() {
@Override
public String[] getReferencedTables() {
String[] referencedTables = null;
AST firstChild = getFirstChild();
if ( firstChild != null ) {
if ( firstChild instanceof FromReferenceNode ) {
FromReferenceNode fromReferenceNode = (FromReferenceNode) firstChild;
FromElement fromElement = fromReferenceNode.getFromElement();
if ( fromElement != null ) {
String table = fromElement.getPropertyTableName( getOriginalPropertyName() );
if ( table != null ) {
referencedTables = new String[] { table };
}
}
FromReferenceNode lhs = ( (FromReferenceNode) getFirstChild() );
if ( lhs != null) {
FromElement fromElement = lhs.getFromElement();
if ( fromElement != null ) {
String propertyTableName = fromElement.getPropertyTableName( propertyPath );
return new String[] { propertyTableName };
}
}
return referencedTables;
return null;
}
public void setPropertyPath(String propertyPath) {
@ -728,14 +715,6 @@ public String getPropertyPath() {
return propertyPath;
}
public String getPropertyName() {
return propertyName;
}
public String getOriginalPropertyName() {
return originalPropertyName;
}
public FromReferenceNode getLhs() {
FromReferenceNode lhs = ( (FromReferenceNode) getFirstChild() );
if ( lhs == null ) {

View File

@ -377,7 +377,12 @@ public String getPropertyTableName(String propertyName) {
checkInitialized();
if ( this.persister != null ) {
AbstractEntityPersister aep = (AbstractEntityPersister) this.persister;
return aep.getPropertyTableName( propertyName );
try {
return aep.getSubclassTableName( aep.getSubclassPropertyTableNumber( propertyName ) );
}
catch (QueryException e) {
return null;
}
}
return null;
}

View File

@ -137,14 +137,4 @@ protected boolean isFromElementUpdateOrDeleteRoot(FromElement element) {
|| getWalker().getStatementType() == HqlSqlTokenTypes.UPDATE;
}
/**
* Returns table names which are referenced by this node. If the tables
* can not be determined it returns null.
*
* @return table names or null.
*/
public String[] getReferencedTables() {
return null;
}
}

View File

@ -17,9 +17,10 @@
*
* @author josh
*/
public class SqlFragment extends Node implements ParameterContainer {
public class SqlFragment extends Node implements ParameterContainer, TableReferenceNode {
private JoinFragment joinFragment;
private FromElement fromElement;
private String[] referencedTables;
public void setJoinFragment(JoinFragment joinFragment) {
this.joinFragment = joinFragment;
@ -56,4 +57,13 @@ public boolean hasEmbeddedParameters() {
public ParameterSpecification[] getEmbeddedParameters() {
return embeddedParameters.toArray( new ParameterSpecification[ embeddedParameters.size() ] );
}
public String[] getReferencedTables() {
return referencedTables;
}
public void setReferencedTables(String[] referencedTables) {
this.referencedTables = referencedTables;
}
}

View File

@ -0,0 +1,23 @@
/*
* 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.hql.internal.ast.tree;
/**
* @author Jan-Willem Gmelig Meyling
*/
public interface TableReferenceNode {
/**
* Returns table names which are referenced by this node. If the tables
* can not be determined it returns null.
*
* @return table names or null.
*/
public String[] getReferencedTables();
}

View File

@ -42,13 +42,10 @@ public LinkedHashMap<String, Object> createNodeProperties(AST node) {
EntityPersister entityPersister = fromElement != null ? fromElement.getEntityPersister() : null;
String entityPersisterStr = entityPersister != null ? entityPersister.toString() : null;
props.put( "persister", entityPersisterStr );
String referencedTablesStr = Arrays.toString( frn.getReferencedTables() );
props.put( "referencedTables", referencedTablesStr );
}
if ( node instanceof DotNode ) {
DotNode dn = (DotNode) node;
props.put( "path", dn.getPath() );
props.put( "originalPropertyName", dn.getOriginalPropertyName() );
}
if ( node instanceof IdentNode ) {
IdentNode in = (IdentNode) node;

View File

@ -33,6 +33,7 @@
import org.hibernate.hql.internal.ast.tree.ParameterContainer;
import org.hibernate.hql.internal.ast.tree.QueryNode;
import org.hibernate.hql.internal.ast.tree.SqlFragment;
import org.hibernate.hql.internal.ast.tree.TableReferenceNode;
import org.hibernate.hql.internal.classic.ParserHelper;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
@ -167,14 +168,14 @@ private Set<String> findQueryReferencedTables(QueryNode query) {
private void collectReferencedTables(ASTIterator iterator, Set<String> result) {
while ( iterator.hasNext() ) {
AST node = iterator.nextNode();
if ( node instanceof FromReferenceNode ) {
FromReferenceNode fromReferenceNode = (FromReferenceNode) node;
if ( node instanceof TableReferenceNode) {
TableReferenceNode fromReferenceNode = (TableReferenceNode) node;
String[] tables = fromReferenceNode.getReferencedTables();
if ( tables != null ) {
Collections.addAll(result, tables);
}
}
else if (node instanceof SqlFragment) {
if (node instanceof SqlFragment) {
SqlFragment sqlFragment = (SqlFragment) node;
FromElement fromElement = sqlFragment.getFromElement();

View File

@ -6,13 +6,34 @@
*/
package org.hibernate.jpa.test.jointable;
import org.hibernate.annotations.NaturalId;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.engine.query.spi.HQLQueryPlan;
import org.hibernate.hql.spi.QueryTranslator;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQuery;
import javax.persistence.SecondaryTable;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Collections;
import java.util.Objects;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@ -24,11 +45,19 @@ public class ManyToOneJoinTableTest extends BaseCoreFunctionalTestCase {
protected Class<?>[] getAnnotatedClasses() {
return new Class[] {
Person.class,
Address.class
Address.class,
ResourceImpl.class,
IssuerImpl.class
};
}
@Test
@Override
protected void configure(Configuration configuration) {
super.configure(configuration);
// configuration.setProperty(AvailableSettings.OMIT_JOIN_OF_SUPERCLASS_TABLES, Boolean.FALSE.toString());
}
@Test
public void testAvoidJoin() {
final HQLQueryPlan plan = sessionFactory().getQueryPlanCache().getHQLQueryPlan(
"SELECT e.id FROM Person e",
@ -42,4 +71,119 @@ public void testAvoidJoin() {
// Since *ToOne join tables are treated like secondary or subclass/superclass tables, the proper fix will allow many more optimizations
assertFalse( generatedSql.contains( "join" ) );
}
@Test
public void testRegression() {
doInHibernate( this::sessionFactory, session -> {
session.createNamedQuery(IssuerImpl.SELECT_RESOURCES_BY_ISSUER)
.setParameter("issuer", session.getReference(IssuerImpl.class, new Identifier(1l, "ABC")))
.getResultList();
} );
}
public interface Issuer extends Resource {}
@Entity(name = IssuerImpl.ENTITY_NAME)
@SecondaryTable(name = IssuerImpl.TABLE_NAME)
@NamedQuery(name = IssuerImpl.SELECT_RESOURCES_BY_ISSUER, query = "SELECT resource.identifier FROM " + ResourceImpl.ENTITY_NAME + " resource WHERE resource.identifier.issuer IN (SELECT issuer.identifier.issuer FROM " + IssuerImpl.ENTITY_NAME + " issuer WHERE issuer.parentIssuer = :issuer OR issuer = :issuer)")
public static class IssuerImpl extends ResourceImpl implements Issuer {
private static final String SELECT_RESOURCES_BY_ISSUER = "SELECT_RESOURCES_BY_ISSUER";
private static final String ENTITY_NAME = "Issuer";
public static final String PARENT_ISSUER_COLUMN = "parent_issuer";
public static final String PARENT_IDENTIFIER_COLUMN = "parent_identifier";
public static final String TABLE_NAME = "issuer_impl";
@ManyToOne(targetEntity = IssuerImpl.class)
@JoinColumns({
@JoinColumn(name = PARENT_ISSUER_COLUMN, table = TABLE_NAME, referencedColumnName = "issuer"),
@JoinColumn(name = PARENT_IDENTIFIER_COLUMN, table = TABLE_NAME, referencedColumnName = "identifier")
})
private Issuer parentIssuer;
public Identifier getIdentifier() {
return identifier;
}
public void setIdentifier(Identifier identifier) {
this.identifier = identifier;
}
public Issuer getParentIssuer() {
return parentIssuer;
}
public void setParentIssuer(Issuer parentIssuer) {
this.parentIssuer = parentIssuer;
}
}
@Embeddable
public static class Identifier implements Serializable {
Long issuer;
String identifier;
public Long getIssuer() {
return issuer;
}
public void setIssuer(Long issuer) {
this.issuer = issuer;
}
public String getIdentifier() {
return identifier;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public Identifier() {
}
public Identifier(Long issuer, String identifier) {
this.issuer = issuer;
this.identifier = identifier;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Identifier that = (Identifier) o;
return Objects.equals(issuer, that.issuer) &&
Objects.equals(identifier, that.identifier);
}
@Override
public int hashCode() {
return Objects.hash(issuer, identifier);
}
}
public interface Resource {
}
@Entity(name = ResourceImpl.ENTITY_NAME)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public static class ResourceImpl implements Resource {
private static final String ENTITY_NAME = "Resource";
@EmbeddedId
Identifier identifier;
public Identifier getIdentifier() {
return identifier;
}
public void setIdentifier(Identifier identifier) {
this.identifier = identifier;
}
}
}