Skip to content

Commit ac56d4e

Browse files
sebersolebrmeyer
authored andcommitted
HHH-8318 - Problem determining qualifier to use for column names from HQL query parser in certain circumstances
1 parent 8a63228 commit ac56d4e

File tree

6 files changed

+169
-56
lines changed

6 files changed

+169
-56
lines changed

hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.hibernate.internal.util.StringHelper;
3939
import org.hibernate.persister.collection.QueryableCollection;
4040
import org.hibernate.persister.entity.EntityPersister;
41+
import org.hibernate.persister.entity.Queryable;
4142
import org.hibernate.sql.JoinFragment;
4243
import org.hibernate.sql.JoinType;
4344
import org.hibernate.type.CollectionType;
@@ -282,19 +283,36 @@ private void dereferenceCollection(CollectionType collectionType, boolean implic
282283
String propName = getPath();
283284
FromClause currentFromClause = getWalker().getCurrentFromClause();
284285

285-
if ( getWalker().getStatementType() != SqlTokenTypes.SELECT && indexed && classAlias == null ) {
286-
// should indicate that we are processing an INSERT/UPDATE/DELETE
287-
// query with a subquery implied via a collection property
288-
// function. Here, we need to use the table name itself as the
289-
// qualification alias.
290-
// TODO : verify this works for all databases...
291-
// TODO : is this also the case in non-"indexed" scenarios?
292-
String alias = getLhs().getFromElement().getQueryable().getTableName();
293-
columns = getFromElement().toColumns( alias, propertyPath, false, true );
286+
// determine whether we should use the table name or table alias to qualify the column names...
287+
// we need to use the table-name when:
288+
// 1) the top-level statement is not a SELECT
289+
// 2) the LHS FromElement is *the* FromElement from the top-level statement
290+
//
291+
// there is a caveat here.. if the update/delete statement are "multi-table" we should continue to use
292+
// the alias also, even if the FromElement is the root one...
293+
//
294+
// in all other cases, we should use the table alias
295+
final FromElement lhsFromElement = getLhs().getFromElement();
296+
if ( getWalker().getStatementType() != SqlTokenTypes.SELECT ) {
297+
if ( isFromElementUpdateOrDeleteRoot( lhsFromElement ) ) {
298+
// at this point we know we have the 2 conditions above,
299+
// lets see if we have the mentioned "multi-table" caveat...
300+
boolean useAlias = false;
301+
if ( getWalker().getStatementType() != SqlTokenTypes.INSERT ) {
302+
final Queryable persister = lhsFromElement.getQueryable();
303+
if ( persister.isMultiTable() ) {
304+
useAlias = true;
305+
}
306+
}
307+
if ( ! useAlias ) {
308+
final String lhsTableName = lhsFromElement.getQueryable().getTableName();
309+
columns = getFromElement().toColumns( lhsTableName, propertyPath, false, true );
310+
}
311+
}
294312
}
295313

296-
//We do not look for an existing join on the same path, because
297-
//it makes sense to join twice on the same collection role
314+
// We do not look for an existing join on the same path, because
315+
// it makes sense to join twice on the same collection role
298316
FromElementFactory factory = new FromElementFactory(
299317
currentFromClause,
300318
getLhs().getFromElement(),

hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromReferenceNode.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import antlr.collections.AST;
2828
import org.jboss.logging.Logger;
2929

30+
import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes;
3031
import org.hibernate.internal.CoreMessageLogger;
3132

3233
/**
@@ -130,4 +131,15 @@ public FromElement getImpliedJoin() {
130131
return null;
131132
}
132133

134+
@SuppressWarnings("SimplifiableIfStatement")
135+
protected boolean isFromElementUpdateOrDeleteRoot(FromElement element) {
136+
if ( element.getFromClause().getParentFromClause() != null ) {
137+
// its not even a root...
138+
return false;
139+
}
140+
141+
return getWalker().getStatementType() == HqlSqlTokenTypes.DELETE
142+
|| getWalker().getStatementType() == HqlSqlTokenTypes.UPDATE;
143+
}
144+
133145
}

hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IdentNode.java

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -150,42 +150,54 @@ else if (result == COMPONENT_REF) {
150150

151151
private boolean resolveAsAlias() {
152152
// This is not actually a constant, but a reference to FROM element.
153-
FromElement element = getWalker().getCurrentFromClause().getFromElement( getText() );
154-
if ( element != null ) {
155-
setType( SqlTokenTypes.ALIAS_REF );
156-
setFromElement( element );
157-
String[] columnExpressions = element.getIdentityColumns();
158-
final boolean isInNonDistinctCount = getWalker().isInCount() && ! getWalker().isInCountDistinct();
159-
final boolean isCompositeValue = columnExpressions.length > 1;
160-
if ( isCompositeValue ) {
161-
if ( isInNonDistinctCount && ! getWalker().getSessionFactoryHelper().getFactory().getDialect().supportsTupleCounts() ) {
162-
setText( columnExpressions[0] );
163-
}
164-
else {
165-
String joinedFragment = StringHelper.join( ", ", columnExpressions );
166-
// avoid wrapping in parenthesis (explicit tuple treatment) if possible due to varied support for
167-
// tuple syntax across databases..
168-
final boolean shouldSkipWrappingInParenthesis =
169-
getWalker().isInCount()
170-
|| getWalker().getCurrentTopLevelClauseType() == HqlSqlTokenTypes.ORDER
171-
|| getWalker().getCurrentTopLevelClauseType() == HqlSqlTokenTypes.GROUP;
172-
if ( ! shouldSkipWrappingInParenthesis ) {
173-
joinedFragment = "(" + joinedFragment + ")";
174-
}
175-
setText( joinedFragment );
176-
}
177-
return true;
153+
final FromElement element = getWalker().getCurrentFromClause().getFromElement( getText() );
154+
if ( element == null ) {
155+
return false;
156+
}
157+
158+
setType( SqlTokenTypes.ALIAS_REF );
159+
setFromElement( element );
160+
161+
String[] columnExpressions = element.getIdentityColumns();
162+
163+
// determine whether to apply qualification (table alias) to the column(s)...
164+
if ( ! isFromElementUpdateOrDeleteRoot( element ) ) {
165+
if ( StringHelper.isNotEmpty( element.getTableAlias() ) ) {
166+
// apparently we also need to check that they are not already qualified. Ugh!
167+
columnExpressions = StringHelper.qualifyIfNot( element.getTableAlias(), columnExpressions );
178168
}
179-
else if ( columnExpressions.length > 0 ) {
169+
}
170+
171+
final boolean isInNonDistinctCount = getWalker().isInCount() && ! getWalker().isInCountDistinct();
172+
final boolean isCompositeValue = columnExpressions.length > 1;
173+
if ( isCompositeValue ) {
174+
if ( isInNonDistinctCount && ! getWalker().getSessionFactoryHelper().getFactory().getDialect().supportsTupleCounts() ) {
180175
setText( columnExpressions[0] );
181-
return true;
182176
}
177+
else {
178+
String joinedFragment = StringHelper.join( ", ", columnExpressions );
179+
// avoid wrapping in parenthesis (explicit tuple treatment) if possible due to varied support for
180+
// tuple syntax across databases..
181+
final boolean shouldSkipWrappingInParenthesis =
182+
getWalker().isInCount()
183+
|| getWalker().getCurrentTopLevelClauseType() == HqlSqlTokenTypes.ORDER
184+
|| getWalker().getCurrentTopLevelClauseType() == HqlSqlTokenTypes.GROUP;
185+
if ( ! shouldSkipWrappingInParenthesis ) {
186+
joinedFragment = "(" + joinedFragment + ")";
187+
}
188+
setText( joinedFragment );
189+
}
190+
return true;
183191
}
192+
else if ( columnExpressions.length > 0 ) {
193+
setText( columnExpressions[0] );
194+
return true;
195+
}
196+
184197
return false;
185198
}
186199

187-
private Type getNakedPropertyType(FromElement fromElement)
188-
{
200+
private Type getNakedPropertyType(FromElement fromElement) {
189201
if (fromElement == null) {
190202
return null;
191203
}

hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,14 +464,34 @@ public static String qualify(String prefix, String name) {
464464
}
465465

466466
public static String[] qualify(String prefix, String[] names) {
467-
if ( prefix == null ) return names;
467+
if ( prefix == null ) {
468+
return names;
469+
}
468470
int len = names.length;
469471
String[] qualified = new String[len];
470472
for ( int i = 0; i < len; i++ ) {
471473
qualified[i] = qualify( prefix, names[i] );
472474
}
473475
return qualified;
474476
}
477+
478+
public static String[] qualifyIfNot(String prefix, String[] names) {
479+
if ( prefix == null ) {
480+
return names;
481+
}
482+
int len = names.length;
483+
String[] qualified = new String[len];
484+
for ( int i = 0; i < len; i++ ) {
485+
if ( names[i].indexOf( '.' ) < 0 ) {
486+
qualified[i] = qualify( prefix, names[i] );
487+
}
488+
else {
489+
qualified[i] = names[i];
490+
}
491+
}
492+
return qualified;
493+
}
494+
475495
public static int firstIndexOfChar(String sqlString, BitSet keys, int startindex) {
476496
for ( int i = startindex, size = sqlString.length(); i < size; i++ ) {
477497
if ( keys.get( sqlString.charAt( i ) ) ) {

hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -453,21 +453,6 @@ public void testCollectionSQLOverriding() {
453453
tx.rollback();
454454
s.close();
455455
}
456-
457-
@Test
458-
@TestForIssue( jiraKey = "HHH-8318" )
459-
@FailureExpected( jiraKey = "HHH-8318" )
460-
public void testDeleteMemberOf() {
461-
Session s = openSession();
462-
s.getTransaction().begin();
463-
s.createQuery(
464-
"delete Attrvalue aval where aval.id in ( "
465-
+ "select val2.id from Employee e, Employeegroup eg, Attrset aset, Attrvalue val2 "
466-
+ "where eg.id = e.employeegroup.id " + "and aset.id = e.attrset.id "
467-
+ "and val2 member of aset.attrvalues)" ).executeUpdate();
468-
s.getTransaction().commit();
469-
s.close();
470-
}
471456

472457
@Override
473458
protected Class[] getAnnotatedClasses() {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
5+
* indicated by the @author tags or express copyright attribution
6+
* statements applied by the authors. All third-party contributions are
7+
* distributed under license by Red Hat Inc.
8+
*
9+
* This copyrighted material is made available to anyone wishing to use, modify,
10+
* copy, or redistribute it subject to the terms and conditions of the GNU
11+
* Lesser General Public License, as published by the Free Software Foundation.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15+
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16+
* for more details.
17+
*
18+
* You should have received a copy of the GNU Lesser General Public License
19+
* along with this distribution; if not, write to:
20+
* Free Software Foundation, Inc.
21+
* 51 Franklin Street, Fifth Floor
22+
* Boston, MA 02110-1301 USA
23+
*/
24+
package org.hibernate.test.hql;
25+
26+
import org.hibernate.Session;
27+
28+
import org.junit.Test;
29+
30+
import org.hibernate.testing.TestForIssue;
31+
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
32+
import org.hibernate.test.annotations.query.Attrset;
33+
import org.hibernate.test.annotations.query.Attrvalue;
34+
import org.hibernate.test.annotations.query.Employee;
35+
import org.hibernate.test.annotations.query.Employeegroup;
36+
37+
/**
38+
* @author Steve Ebersole
39+
*/
40+
public class DeleteWhereMemberOfTest extends BaseCoreFunctionalTestCase {
41+
42+
@Override
43+
protected Class[] getAnnotatedClasses() {
44+
return new Class[] {
45+
Attrset.class,
46+
Attrvalue.class,
47+
Employee.class,
48+
Employeegroup.class
49+
};
50+
}
51+
52+
@Test
53+
@TestForIssue( jiraKey = "HHH-8318" )
54+
// @FailureExpected( jiraKey = "HHH-8318" )
55+
public void testDeleteMemberOf() {
56+
final String qry = "delete Attrvalue aval where aval.id in ( "
57+
+ "select val2.id from Employee e, Employeegroup eg, Attrset aset, Attrvalue val2 "
58+
+ "where eg.id = e.employeegroup.id " + "and aset.id = e.attrset.id "
59+
+ "and val2 member of aset.attrvalues)";
60+
Session s = openSession();
61+
s.getTransaction().begin();
62+
s.createQuery( qry ).executeUpdate();
63+
s.getTransaction().commit();
64+
s.close();
65+
}
66+
}

0 commit comments

Comments
 (0)