HHH-7374 - Support KEY, ENTRY and VALUE qualifiers in WHERE clause

This commit is contained in:
Steve Ebersole 2012-06-06 17:22:58 -05:00
parent d971317e1b
commit 67c5000885
9 changed files with 201 additions and 46 deletions

View File

@ -652,6 +652,9 @@ addrExpr! [ boolean root ]
#addrExpr = #(#i, #lhs2, #rhs2);
processIndex(#addrExpr);
}
| mcr:mapComponentReference {
#addrExpr = #mcr;
}
| p:identifier {
// #addrExpr = #p;
// resolve(#addrExpr);

View File

@ -645,13 +645,13 @@ identPrimary
( options { greedy=true; } :
( op:OPEN^ { #op.setType(METHOD_CALL);} e:exprList CLOSE! ) {
AST path = #e.getFirstChild();
if ( #i.getText().equals( "key" ) ) {
if ( #i.getText().equalsIgnoreCase( "key" ) ) {
#identPrimary = #( [KEY], path );
}
else if ( #i.getText().equals( "value" ) ) {
else if ( #i.getText().equalsIgnoreCase( "value" ) ) {
#identPrimary = #( [VALUE], path );
}
else if ( #i.getText().equals( "entry" ) ) {
else if ( #i.getText().equalsIgnoreCase( "entry" ) ) {
#identPrimary = #( [ENTRY], path );
}
}

View File

@ -436,6 +436,7 @@ addrExpr
| i:ALIAS_REF { out(i); }
| j:INDEX_OP { out(j); }
| v:RESULT_VARIABLE_REF { out(v); }
| mcr:mapComponentReference { out(mcr); }
;
sqlToken

View File

@ -28,6 +28,7 @@ import java.util.Map;
import antlr.SemanticException;
import antlr.collections.AST;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes;
import org.hibernate.hql.internal.ast.util.ColumnHelper;
import org.hibernate.internal.util.StringHelper;

View File

@ -210,7 +210,7 @@ class FromElementType {
getTableAlias(),
getSuffix( size, k ),
allProperties
);
);
return trimLeadingCommaAndSpaces( fragment );
}
}
@ -327,15 +327,17 @@ class FromElementType {
String[] toColumns(String tableAlias, String path, boolean inSelect, boolean forceAlias) {
checkInitialized();
PropertyMapping propertyMapping = getPropertyMapping( path );
// If this from element is a collection and the path is a collection property (maxIndex, etc.) then
// generate a sub-query.
//
// NOTE : in the case of this being a collection property in the select, not generating the subquery
// will not generally work. The specific cases I am thinking about are the minIndex, maxIndex
// (most likely minElement, maxElement as well) cases.
// todo : if ^^ is the case we should thrown an exception here rather than waiting for the sql error
// if the dialect supports select-clause subqueries we could go ahead and generate the subquery also
if ( !inSelect && queryableCollection != null && CollectionProperties.isCollectionProperty( path ) ) {
// If this from element is a collection and the path is a collection property (maxIndex, etc.)
// requiring a sub-query then generate a sub-query.
//h
// Unless we are in the select clause, because some dialects do not support
// Note however, that some dialects do not However, in the case of this being a collection property reference being in the select, not generating the subquery
// will not generally work. The specific cases I am thinking about are the minIndex, maxIndex
// (most likely minElement, maxElement as well) cases.
// todo : if ^^ is the case we should thrown an exception here rather than waiting for the sql error
// if the dialect supports select-clause subqueries we could go ahead and generate the subquery also
Map enabledFilters = fromElement.getWalker().getEnabledFilters();
String subquery = CollectionSubqueryFactory.createCollectionSubquery(
joinSequence.copy().setUseThetaStyle( true ),

View File

@ -39,7 +39,12 @@ public class MapKeyNode extends AbstractMapComponentNode {
@Override
protected String[] resolveColumns(QueryableCollection collectionPersister) {
return collectionPersister.getIndexColumnNames();
final FromElement fromElement = getFromElement();
return fromElement.toColumns(
fromElement.getCollectionTableAlias(),
"index", // the JPA KEY "qualifier" is the same concept as the HQL INDEX function/property
getWalker().isInSelect()
);
}
@Override

View File

@ -39,7 +39,12 @@ public class MapValueNode extends AbstractMapComponentNode {
@Override
protected String[] resolveColumns(QueryableCollection collectionPersister) {
return collectionPersister.getElementColumnNames();
final FromElement fromElement = getFromElement();
return fromElement.toColumns(
fromElement.getCollectionTableAlias(),
"elements", // the JPA VALUE "qualifier" is the same concept as the HQL ELEMENTS function/property
getWalker().isInSelect()
);
}
@Override

View File

@ -250,7 +250,7 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase {
@Test
@SuppressWarnings( {"unchecked"})
public void testJPAQLQualifiedIdentificationVariables() {
public void testJPAQLMapKeyQualifier() {
Session s = openSession();
s.beginTransaction();
Human me = new Human();
@ -264,47 +264,183 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase {
s.getTransaction().commit();
s.close();
s = openSession();
s.beginTransaction();
List results = s.createQuery( "select entry(h.family) from Human h" ).list();
assertEquals( 1, results.size() );
Object result = results.get(0);
assertTrue( Map.Entry.class.isAssignableFrom( result.getClass() ) );
Map.Entry entry = (Map.Entry) result;
assertTrue( String.class.isAssignableFrom( entry.getKey().getClass() ) );
assertTrue( Human.class.isAssignableFrom( entry.getValue().getClass() ) );
s.getTransaction().commit();
s.close();
// in SELECT clause
{
// hibernate-only form
s = openSession();
s.beginTransaction();
List results = s.createQuery( "select distinct key(h.family) from Human h" ).list();
assertEquals( 1, results.size() );
Object key = results.get(0);
assertTrue( String.class.isAssignableFrom( key.getClass() ) );
s.getTransaction().commit();
s.close();
}
{
// jpa form
s = openSession();
s.beginTransaction();
List results = s.createQuery( "select distinct KEY(f) from Human h join h.family f" ).list();
assertEquals( 1, results.size() );
Object key = results.get(0);
assertTrue( String.class.isAssignableFrom( key.getClass() ) );
s.getTransaction().commit();
s.close();
}
// in WHERE clause
{
// hibernate-only form
s = openSession();
s.beginTransaction();
Long count = (Long) s.createQuery( "select count(*) from Human h where KEY(h.family) = 'son'" ).uniqueResult();
assertEquals( (Long)1L, count );
s.getTransaction().commit();
s.close();
}
{
// jpa form
s = openSession();
s.beginTransaction();
Long count = (Long) s.createQuery( "select count(*) from Human h join h.family f where key(f) = 'son'" ).uniqueResult();
assertEquals( (Long)1L, count );
s.getTransaction().commit();
s.close();
}
s = openSession();
s.beginTransaction();
results = s.createQuery( "select entry(f) from Human h join h.family f" ).list();
assertEquals( 1, results.size() );
result = results.get(0);
assertTrue( Map.Entry.class.isAssignableFrom( result.getClass() ) );
entry = (Map.Entry) result;
assertTrue( String.class.isAssignableFrom( entry.getKey().getClass() ) );
assertTrue( Human.class.isAssignableFrom( entry.getValue().getClass() ) );
s.delete( me );
s.delete( joe );
s.getTransaction().commit();
s.close();
}
@Test
@SuppressWarnings( {"unchecked"})
public void testJPAQLMapEntryQualifier() {
Session s = openSession();
s.beginTransaction();
Human me = new Human();
me.setName( new Name( "Steve", null, "Ebersole" ) );
Human joe = new Human();
me.setName( new Name( "Joe", null, "Ebersole" ) );
me.setFamily( new HashMap() );
me.getFamily().put( "son", joe );
s.save( me );
s.save( joe );
s.getTransaction().commit();
s.close();
s = openSession();
s.beginTransaction();
results = s.createQuery( "select distinct key(h.family) from Human h" ).list();
assertEquals( 1, results.size() );
Object key = results.get(0);
assertTrue( String.class.isAssignableFrom( key.getClass() ) );
s.getTransaction().commit();
s.close();
// in SELECT clause
{
// hibernate-only form
s = openSession();
s.beginTransaction();
List results = s.createQuery( "select entry(h.family) from Human h" ).list();
assertEquals( 1, results.size() );
Object result = results.get(0);
assertTrue( Map.Entry.class.isAssignableFrom( result.getClass() ) );
Map.Entry entry = (Map.Entry) result;
assertTrue( String.class.isAssignableFrom( entry.getKey().getClass() ) );
assertTrue( Human.class.isAssignableFrom( entry.getValue().getClass() ) );
s.getTransaction().commit();
s.close();
}
{
// jpa form
s = openSession();
s.beginTransaction();
List results = s.createQuery( "select ENTRY(f) from Human h join h.family f" ).list();
assertEquals( 1, results.size() );
Object result = results.get(0);
assertTrue( Map.Entry.class.isAssignableFrom( result.getClass() ) );
Map.Entry entry = (Map.Entry) result;
assertTrue( String.class.isAssignableFrom( entry.getKey().getClass() ) );
assertTrue( Human.class.isAssignableFrom( entry.getValue().getClass() ) );
s.getTransaction().commit();
s.close();
}
// not exactly sure of the syntax of ENTRY in the WHERE clause...
s = openSession();
s.beginTransaction();
results = s.createQuery( "select distinct key(f) from Human h join h.family f" ).list();
assertEquals( 1, results.size() );
key = results.get(0);
assertTrue( String.class.isAssignableFrom( key.getClass() ) );
s.delete( me );
s.delete( joe );
s.getTransaction().commit();
s.close();
}
@Test
@SuppressWarnings( {"unchecked"})
public void testJPAQLMapValueQualifier() {
Session s = openSession();
s.beginTransaction();
Human me = new Human();
me.setName( new Name( "Steve", null, "Ebersole" ) );
Human joe = new Human();
me.setName( new Name( "Joe", null, "Ebersole" ) );
me.setFamily( new HashMap() );
me.getFamily().put( "son", joe );
s.save( me );
s.save( joe );
s.getTransaction().commit();
s.close();
// in SELECT clause
{
// hibernate-only form
s = openSession();
s.beginTransaction();
List results = s.createQuery( "select value(h.family) from Human h" ).list();
assertEquals( 1, results.size() );
Object result = results.get(0);
assertTrue( Human.class.isAssignableFrom( result.getClass() ) );
s.getTransaction().commit();
s.close();
}
{
// jpa form
s = openSession();
s.beginTransaction();
List results = s.createQuery( "select VALUE(f) from Human h join h.family f" ).list();
assertEquals( 1, results.size() );
Object result = results.get(0);
assertTrue( Human.class.isAssignableFrom( result.getClass() ) );
s.getTransaction().commit();
s.close();
}
// in WHERE clause
{
// hibernate-only form
s = openSession();
s.beginTransaction();
Long count = (Long) s.createQuery( "select count(*) from Human h where VALUE(h.family) = :joe" ).setParameter( "joe", joe ).uniqueResult();
// ACTUALLY EXACTLY THE SAME AS:
// select count(*) from Human h where h.family = :joe
assertEquals( (Long)1L, count );
s.getTransaction().commit();
s.close();
}
{
// jpa form
s = openSession();
s.beginTransaction();
Long count = (Long) s.createQuery( "select count(*) from Human h join h.family f where value(f) = :joe" ).setParameter( "joe", joe ).uniqueResult();
// ACTUALLY EXACTLY THE SAME AS:
// select count(*) from Human h join h.family f where f = :joe
assertEquals( (Long)1L, count );
s.getTransaction().commit();
s.close();
}
s = openSession();
s.beginTransaction();

View File

@ -11,3 +11,5 @@ log4j.logger.org.hibernate.testing.cache=debug
# SQL Logging - HHH-6833
log4j.logger.org.hibernate.SQL=debug
log4j.logger.org.hibernate.hql.internal.ast=debug