From 82c5e0ae26c61e61446dbd096ff6b449767b4d61 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Tue, 27 Oct 2015 21:40:25 -0500 Subject: [PATCH] HHH-1400 - formula-based property leads to generation of invalid SQL with subselect fetches --- .../hibernate/engine/spi/SubselectFetch.java | 45 +++++++- .../hibernate/test/subselectfetch/Name.java | 35 ++++++ .../SubselectFetchWithFormulaTest.java | 106 ++++++++++++++++++ .../hibernate/test/subselectfetch/Value.java | 31 +++++ .../test/subselectfetch/Name.hbm.xml | 27 +++++ .../test/subselectfetch/ParentChild.hbm.xml | 0 .../test/subselectfetch/Value.hbm.xml | 20 ++++ 7 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/subselectfetch/Name.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/subselectfetch/SubselectFetchWithFormulaTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/subselectfetch/Value.java create mode 100644 hibernate-core/src/test/resources/org/hibernate/test/subselectfetch/Name.hbm.xml rename hibernate-core/src/test/{java => resources}/org/hibernate/test/subselectfetch/ParentChild.hbm.xml (100%) create mode 100644 hibernate-core/src/test/resources/org/hibernate/test/subselectfetch/Value.hbm.xml diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SubselectFetch.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SubselectFetch.java index db510595fa..e9c314abd3 100755 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SubselectFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SubselectFetch.java @@ -17,6 +17,8 @@ import org.hibernate.persister.entity.PropertyMapping; * @author Gavin King */ public class SubselectFetch { + private static final String FROM_STRING = " from "; + private final Set resultingEntityKeys; private final String queryString; private final String alias; @@ -39,13 +41,52 @@ public class SubselectFetch { //TODO: ugly here: final String queryString = queryParameters.getFilteredSQL(); - int fromIndex = queryString.indexOf( " from " ); - int orderByIndex = queryString.lastIndexOf( "order by" ); + final int fromIndex = getFromIndex( queryString ); + final int orderByIndex = queryString.lastIndexOf( "order by" ); this.queryString = orderByIndex > 0 ? queryString.substring( fromIndex, orderByIndex ) : queryString.substring( fromIndex ); } + private static int getFromIndex(String queryString) { + int index = queryString.indexOf( FROM_STRING ); + + if ( index < 0 ) { + return index; + } + + while ( !parenthesesMatch( queryString.substring( 0, index ) ) ) { + String subString = queryString.substring( index + FROM_STRING.length() ); + + int subIndex = subString.indexOf( FROM_STRING ); + + if ( subIndex < 0 ) { + return subIndex; + } + + index += FROM_STRING.length() + subIndex; + } + + return index; + } + + private static boolean parenthesesMatch(String string) { + int parenCount = 0; + + for ( int i = 0; i < string.length(); i++ ) { + char character = string.charAt( i ); + + if ( character == '(' ) { + parenCount++; + } + else if ( character == ')' ) { + parenCount--; + } + } + + return parenCount == 0; + } + public QueryParameters getQueryParameters() { return queryParameters; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/subselectfetch/Name.java b/hibernate-core/src/test/java/org/hibernate/test/subselectfetch/Name.java new file mode 100644 index 0000000000..3969f591ce --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/subselectfetch/Name.java @@ -0,0 +1,35 @@ +/* + * 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 . + */ +package org.hibernate.test.subselectfetch; + +import java.io.Serializable; +import java.util.List; + +public class Name implements Serializable { + private int id; + private String name; + private int nameLength; + private List values; + + public int getId() { return id; } + public String getName() { return name; } + public int getNameLength() { return nameLength; } + public List getValues() { return values; } + + public void setId(int id) { this.id = id; } + public void setName(String name) { this.name = name; } + public void setNameLength(int nameLength) { this.nameLength = nameLength; } + public void setValues(List values) { this.values = values; } + + public boolean equals(Object obj) { + if (!(obj instanceof Name )) return false; + Name other = (Name) obj; + return other.id == this.id; + } + + public int hashCode() { return id; } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/subselectfetch/SubselectFetchWithFormulaTest.java b/hibernate-core/src/test/java/org/hibernate/test/subselectfetch/SubselectFetchWithFormulaTest.java new file mode 100644 index 0000000000..37174e95ab --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/subselectfetch/SubselectFetchWithFormulaTest.java @@ -0,0 +1,106 @@ +/* + * 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 . + */ +package org.hibernate.test.subselectfetch; + +import java.util.List; + +import org.hibernate.Session; +import org.hibernate.mapping.Collection; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class SubselectFetchWithFormulaTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected String getBaseForMappings() { + return ""; + } + + @Override + protected String[] getMappings() { + return new String[] { + "org/hibernate/test/subselectfetch/Name.hbm.xml", + "org/hibernate/test/subselectfetch/Value.hbm.xml" + }; + } + + @Before + public void before() { + Session session = openSession(); + session.getTransaction().begin(); + + Name chris = new Name(); + chris.setId( 1 ); + chris.setName( "chris" ); + Value cat = new Value(); + cat.setId(1); + cat.setName( chris ); + cat.setValue( "cat" ); + Value canary = new Value(); + canary.setId( 2 ); + canary.setName( chris ); + canary.setValue( "canary" ); + + session.persist( chris ); + session.persist( cat ); + session.persist( canary ); + + Name sam = new Name(); + sam.setId(2); + sam.setName( "sam" ); + Value seal = new Value(); + seal.setId( 3 ); + seal.setName( sam ); + seal.setValue( "seal" ); + Value snake = new Value(); + snake.setId( 4 ); + snake.setName( sam ); + snake.setValue( "snake" ); + + session.persist( sam ); + session.persist(seal); + session.persist( snake ); + + session.getTransaction().commit(); + session.close(); + } + + @After + public void after() { + Session session = openSession(); + session.getTransaction().begin(); + session.createQuery( "delete Value" ).executeUpdate(); + session.createQuery( "delete Name" ).executeUpdate(); + session.getTransaction().commit(); + session.close(); + } + + + @Test + public void checkSubselectWithFormula() throws Exception { + // as a pre-condition make sure that subselect fetching is enabled for the collection... + Collection collectionBinding = metadata().getCollectionBinding( Name.class.getName() + ".values" ); + assertThat( collectionBinding.isSubselectLoadable(), is( true ) ); + + // Now force the subselect fetch and make sure we do not get SQL errors + Session session = openSession(); + session.getTransaction().begin(); + List results = session.createCriteria(Name.class).list(); + for (Object result : results) { + Name name = (Name) result; + name.getValues().size(); + } + session.getTransaction().commit(); + session.close(); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/subselectfetch/Value.java b/hibernate-core/src/test/java/org/hibernate/test/subselectfetch/Value.java new file mode 100644 index 0000000000..9cd2d67486 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/subselectfetch/Value.java @@ -0,0 +1,31 @@ +/* + * 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 . + */ +package org.hibernate.test.subselectfetch; + +import java.io.Serializable; + +public class Value implements Serializable { + private int id; + private Name name; + private String value; + + public int getId() { return id; } + public Name getName() { return name; } + public String getValue() { return value; } + + public void setId(int id) { this.id = id; } + public void setName(Name name) { this.name = name; } + public void setValue(String value) { this.value = value; } + + public boolean equals(Object obj) { + if (!(obj instanceof Value )) return false; + Value other = (Value) obj; + return other.id == this.id; + } + + public int hashCode() { return id; } +} diff --git a/hibernate-core/src/test/resources/org/hibernate/test/subselectfetch/Name.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/subselectfetch/Name.hbm.xml new file mode 100644 index 0000000000..d909a43122 --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/test/subselectfetch/Name.hbm.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/subselectfetch/ParentChild.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/subselectfetch/ParentChild.hbm.xml similarity index 100% rename from hibernate-core/src/test/java/org/hibernate/test/subselectfetch/ParentChild.hbm.xml rename to hibernate-core/src/test/resources/org/hibernate/test/subselectfetch/ParentChild.hbm.xml diff --git a/hibernate-core/src/test/resources/org/hibernate/test/subselectfetch/Value.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/test/subselectfetch/Value.hbm.xml new file mode 100644 index 0000000000..1fc7a7698a --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/test/subselectfetch/Value.hbm.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + +