Skip to content

Commit 10a5f4a

Browse files
committed
HHH-1400 - formula-based property leads to generation of invalid SQL with subselect fetches
(cherry picked from commit 82c5e0a)
1 parent 32fce20 commit 10a5f4a

File tree

7 files changed

+262
-2
lines changed

7 files changed

+262
-2
lines changed

hibernate-core/src/main/java/org/hibernate/engine/spi/SubselectFetch.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* @author Gavin King
1818
*/
1919
public class SubselectFetch {
20+
private static final String FROM_STRING = " from ";
21+
2022
private final Set resultingEntityKeys;
2123
private final String queryString;
2224
private final String alias;
@@ -39,13 +41,52 @@ public SubselectFetch(
3941

4042
//TODO: ugly here:
4143
final String queryString = queryParameters.getFilteredSQL();
42-
int fromIndex = queryString.indexOf( " from " );
43-
int orderByIndex = queryString.lastIndexOf( "order by" );
44+
final int fromIndex = getFromIndex( queryString );
45+
final int orderByIndex = queryString.lastIndexOf( "order by" );
4446
this.queryString = orderByIndex > 0
4547
? queryString.substring( fromIndex, orderByIndex )
4648
: queryString.substring( fromIndex );
4749
}
4850

51+
private static int getFromIndex(String queryString) {
52+
int index = queryString.indexOf( FROM_STRING );
53+
54+
if ( index < 0 ) {
55+
return index;
56+
}
57+
58+
while ( !parenthesesMatch( queryString.substring( 0, index ) ) ) {
59+
String subString = queryString.substring( index + FROM_STRING.length() );
60+
61+
int subIndex = subString.indexOf( FROM_STRING );
62+
63+
if ( subIndex < 0 ) {
64+
return subIndex;
65+
}
66+
67+
index += FROM_STRING.length() + subIndex;
68+
}
69+
70+
return index;
71+
}
72+
73+
private static boolean parenthesesMatch(String string) {
74+
int parenCount = 0;
75+
76+
for ( int i = 0; i < string.length(); i++ ) {
77+
char character = string.charAt( i );
78+
79+
if ( character == '(' ) {
80+
parenCount++;
81+
}
82+
else if ( character == ')' ) {
83+
parenCount--;
84+
}
85+
}
86+
87+
return parenCount == 0;
88+
}
89+
4990
public QueryParameters getQueryParameters() {
5091
return queryParameters;
5192
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.test.subselectfetch;
8+
9+
import java.io.Serializable;
10+
import java.util.List;
11+
12+
public class Name implements Serializable {
13+
private int id;
14+
private String name;
15+
private int nameLength;
16+
private List values;
17+
18+
public int getId() { return id; }
19+
public String getName() { return name; }
20+
public int getNameLength() { return nameLength; }
21+
public List getValues() { return values; }
22+
23+
public void setId(int id) { this.id = id; }
24+
public void setName(String name) { this.name = name; }
25+
public void setNameLength(int nameLength) { this.nameLength = nameLength; }
26+
public void setValues(List values) { this.values = values; }
27+
28+
public boolean equals(Object obj) {
29+
if (!(obj instanceof Name )) return false;
30+
Name other = (Name) obj;
31+
return other.id == this.id;
32+
}
33+
34+
public int hashCode() { return id; }
35+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.test.subselectfetch;
8+
9+
import java.util.List;
10+
11+
import org.hibernate.Session;
12+
import org.hibernate.mapping.Collection;
13+
14+
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
15+
import org.junit.After;
16+
import org.junit.Before;
17+
import org.junit.Test;
18+
19+
import static org.hamcrest.CoreMatchers.is;
20+
import static org.junit.Assert.assertThat;
21+
22+
public class SubselectFetchWithFormulaTest extends BaseNonConfigCoreFunctionalTestCase {
23+
@Override
24+
protected String getBaseForMappings() {
25+
return "";
26+
}
27+
28+
@Override
29+
protected String[] getMappings() {
30+
return new String[] {
31+
"org/hibernate/test/subselectfetch/Name.hbm.xml",
32+
"org/hibernate/test/subselectfetch/Value.hbm.xml"
33+
};
34+
}
35+
36+
@Before
37+
public void before() {
38+
Session session = openSession();
39+
session.getTransaction().begin();
40+
41+
Name chris = new Name();
42+
chris.setId( 1 );
43+
chris.setName( "chris" );
44+
Value cat = new Value();
45+
cat.setId(1);
46+
cat.setName( chris );
47+
cat.setValue( "cat" );
48+
Value canary = new Value();
49+
canary.setId( 2 );
50+
canary.setName( chris );
51+
canary.setValue( "canary" );
52+
53+
session.persist( chris );
54+
session.persist( cat );
55+
session.persist( canary );
56+
57+
Name sam = new Name();
58+
sam.setId(2);
59+
sam.setName( "sam" );
60+
Value seal = new Value();
61+
seal.setId( 3 );
62+
seal.setName( sam );
63+
seal.setValue( "seal" );
64+
Value snake = new Value();
65+
snake.setId( 4 );
66+
snake.setName( sam );
67+
snake.setValue( "snake" );
68+
69+
session.persist( sam );
70+
session.persist(seal);
71+
session.persist( snake );
72+
73+
session.getTransaction().commit();
74+
session.close();
75+
}
76+
77+
@After
78+
public void after() {
79+
Session session = openSession();
80+
session.getTransaction().begin();
81+
session.createQuery( "delete Value" ).executeUpdate();
82+
session.createQuery( "delete Name" ).executeUpdate();
83+
session.getTransaction().commit();
84+
session.close();
85+
}
86+
87+
88+
@Test
89+
public void checkSubselectWithFormula() throws Exception {
90+
// as a pre-condition make sure that subselect fetching is enabled for the collection...
91+
Collection collectionBinding = metadata().getCollectionBinding( Name.class.getName() + ".values" );
92+
assertThat( collectionBinding.isSubselectLoadable(), is( true ) );
93+
94+
// Now force the subselect fetch and make sure we do not get SQL errors
95+
Session session = openSession();
96+
session.getTransaction().begin();
97+
List results = session.createCriteria(Name.class).list();
98+
for (Object result : results) {
99+
Name name = (Name) result;
100+
name.getValues().size();
101+
}
102+
session.getTransaction().commit();
103+
session.close();
104+
}
105+
106+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.test.subselectfetch;
8+
9+
import java.io.Serializable;
10+
11+
public class Value implements Serializable {
12+
private int id;
13+
private Name name;
14+
private String value;
15+
16+
public int getId() { return id; }
17+
public Name getName() { return name; }
18+
public String getValue() { return value; }
19+
20+
public void setId(int id) { this.id = id; }
21+
public void setName(Name name) { this.name = name; }
22+
public void setValue(String value) { this.value = value; }
23+
24+
public boolean equals(Object obj) {
25+
if (!(obj instanceof Value )) return false;
26+
Value other = (Value) obj;
27+
return other.id == this.id;
28+
}
29+
30+
public int hashCode() { return id; }
31+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
~ Hibernate, Relational Persistence for Idiomatic Java
4+
~
5+
~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
6+
~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
7+
-->
8+
<!DOCTYPE hibernate-mapping
9+
PUBLIC "-//Hibernate?Hibernate Mapping DTD//EN"
10+
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
11+
12+
<hibernate-mapping>
13+
14+
<class name="org.hibernate.test.subselectfetch.Name" table="Name">
15+
<id name="id" column="id"/>
16+
<property name="name" column="name"/>
17+
18+
<property name="nameLength" formula="(select length(name) from name where id=name.id)"/>
19+
20+
<bag name="values" inverse="true" lazy="false" fetch="subselect">
21+
<key column="name_id"/>
22+
<one-to-many class="org.hibernate.test.subselectfetch.Value"/>
23+
</bag>
24+
25+
</class>
26+
27+
</hibernate-mapping>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
~ Hibernate, Relational Persistence for Idiomatic Java
4+
~
5+
~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
6+
~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
7+
-->
8+
<!DOCTYPE hibernate-mapping
9+
PUBLIC "-//Hibernate?Hibernate Mapping DTD//EN"
10+
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
11+
12+
<hibernate-mapping>
13+
14+
<class name="org.hibernate.test.subselectfetch.Value" table="Value">
15+
<id name="id" column="id"/>
16+
<property name="value" column="value"/>
17+
<many-to-one name="name" column="name_id" insert="false" update="false" />
18+
</class>
19+
20+
</hibernate-mapping>

0 commit comments

Comments
 (0)