HHH-13778: `@OrderBy` handling using SQL AST

- parsing in PluralAttributeMapping implemented
- still need to convert the OrderByFragment to SQL AST and add to the SQL AST order-by-clause
This commit is contained in:
Steve Ebersole 2019-12-12 13:34:37 -06:00
parent 8600058784
commit 284b2c5677
11 changed files with 235 additions and 64 deletions

View File

@ -46,11 +46,7 @@ public interface CollectionPart extends ModelPart, Fetchable {
return ID;
}
throw new IllegalArgumentException(
"Unrecognized CollectionPart Nature name [" + name
+ "]; expecting `" + ELEMENT.name + "` or `"
+ INDEX.name + "`"
);
return null;
}
}

View File

@ -9,6 +9,7 @@ package org.hibernate.metamodel.mapping;
import java.util.function.Consumer;
import org.hibernate.loader.ast.spi.Loadable;
import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
import org.hibernate.sql.results.graph.Fetchable;
@ -37,6 +38,9 @@ public interface PluralAttributeMapping
CollectionIdentifierDescriptor getIdentifierDescriptor();
OrderByFragment getOrderByFragment();
OrderByFragment getManyToManyOrderByFragment();
@Override
default void visitKeyFetchables(Consumer<Fetchable> fetchableConsumer, EntityMappingType treatTargetType) {
final CollectionPart indexDescriptor = getIndexDescriptor();

View File

@ -31,6 +31,9 @@ import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
import org.hibernate.metamodel.mapping.ordering.OrderByFragmentTranslator;
import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Joinable;
@ -59,10 +62,14 @@ import org.hibernate.sql.results.graph.collection.internal.EagerCollectionFetch;
import org.hibernate.type.EntityType;
import org.hibernate.type.ForeignKeyDirection;
import org.jboss.logging.Logger;
/**
* @author Steve Ebersole
*/
public class PluralAttributeMappingImpl extends AbstractAttributeMapping implements PluralAttributeMapping {
private static final Logger log = Logger.getLogger( PluralAttributeMappingImpl.class );
public interface Aware {
void injectAttributeMapping(PluralAttributeMapping attributeMapping);
@ -89,6 +96,9 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme
private ForeignKeyDescriptor manyToManyFkDescriptor;
private OrderByFragment orderByFragment;
private OrderByFragment manyToManyOrderByFragment;
@SuppressWarnings("WeakerAccess")
public PluralAttributeMappingImpl(
String attributeName,
@ -211,6 +221,40 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme
);
}
}
final boolean hasOrder = bootDescriptor.getOrderBy() != null;
final boolean hasManyToManyOrder = bootDescriptor.getManyToManyOrdering() != null;
if ( hasOrder || hasManyToManyOrder ) {
final TranslationContext context = new TranslationContext() {
};
if ( hasOrder ) {
log.debugf(
"Translating order-by fragment [%s] for collection role : %s",
bootDescriptor.getOrderBy(),
collectionDescriptor.getRole()
);
orderByFragment = OrderByFragmentTranslator.translate(
bootDescriptor.getOrderBy(),
this,
context
);
}
if ( hasManyToManyOrder ) {
log.debugf(
"Translating many-to-many order-by fragment [%s] for collection role : %s",
bootDescriptor.getOrderBy(),
collectionDescriptor.getRole()
);
manyToManyOrderByFragment = OrderByFragmentTranslator.translate(
bootDescriptor.getManyToManyOrdering(),
this,
context
);
}
}
}
@Override
@ -248,6 +292,16 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme
return identifierDescriptor;
}
@Override
public OrderByFragment getOrderByFragment() {
return orderByFragment;
}
@Override
public OrderByFragment getManyToManyOrderByFragment() {
return manyToManyOrderByFragment;
}
@Override
public String getSeparateCollectionTable() {
return separateCollectionTable;

View File

@ -0,0 +1,34 @@
/*
* 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.metamodel.mapping.ordering;
import java.util.List;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.select.SortSpecification;
/**
* Represents the translation result
*
* @author Steve Ebersole
*/
public interface OrderByFragment {
// Something like:
List<SortSpecification> toSqlAst(TableGroup tableGroup, SqlAstCreationState creationState);
/**
* Inject table aliases into the translated fragment to properly qualify column references, using
* the given 'aliasResolver' to determine the the proper table alias to use for each column reference.
*
* @param aliasResolver The strategy to resolver the proper table alias to use per column
*
* @return The fully translated and replaced fragment.
*/
String injectAliases(AliasResolver aliasResolver);
}

View File

@ -14,7 +14,8 @@ import org.hibernate.grammars.ordering.OrderingParser;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.ast.ParseTreeVisitor;
import org.hibernate.metamodel.mapping.ordering.ast.SortSpecification;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.jboss.logging.Logger;
@ -57,11 +58,25 @@ public class OrderByFragmentTranslator {
final ParseTreeVisitor visitor = new ParseTreeVisitor( pluralAttributeMapping, context );
final List<SortSpecification> interpretation = visitor.visitOrderByFragment( parseTree );
final List<SortSpecification> tree = visitor.visitOrderByFragment( parseTree );
return new OrderByFragment() {
final List<SortSpecification> sortSpecifications = tree;
@Override
public List<org.hibernate.sql.ast.tree.select.SortSpecification> toSqlAst(
TableGroup tableGroup,
SqlAstCreationState creationState) {
throw new NotYetImplementedFor6Exception( OrderByFragmentTranslator.class );
}
@Override
public String injectAliases(AliasResolver aliasResolver) {
throw new NotYetImplementedFor6Exception( OrderByFragmentTranslator.class );
}
};
}
private static OrderingParser.OrderByFragmentContext buildParseTree(TranslationContext context, String fragment) {
final OrderingLexer lexer = new OrderingLexer( CharStreams.fromString( fragment ) );
@ -90,18 +105,4 @@ public class OrderByFragmentTranslator {
}
}
/**
* Represents the translation result
*/
public interface OrderByFragment {
/**
* Inject table aliases into the translated fragment to properly qualify column references, using
* the given 'aliasResolver' to determine the the proper table alias to use for each column reference.
*
* @param aliasResolver The strategy to resolver the proper table alias to use per column
*
* @return The fully translated and replaced fragment.
*/
String injectAliases(AliasResolver aliasResolver);
}
}

View File

@ -15,7 +15,7 @@ import java.util.List;
*
* @author Steve Ebersole
*/
public class FunctionExpression {
public class FunctionExpression implements SortExpression {
private final String name;
private final List<SortExpression> arguments;

View File

@ -10,16 +10,17 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.SortOrder;
import org.hibernate.grammars.ordering.OrderingParser;
import org.hibernate.grammars.ordering.OrderingParser.ExpressionContext;
import org.hibernate.grammars.ordering.OrderingParserBaseVisitor;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
import org.hibernate.persister.collection.CollectionPersister;
import org.jboss.logging.Logger;
import org.antlr.v4.runtime.tree.TerminalNode;
/**
* @author Steve Ebersole
*/
@ -46,10 +47,9 @@ public class ParseTreeVisitor extends OrderingParserBaseVisitor {
this.specifications = new ArrayList<>( parsedSortSpecifications.size() );
for ( OrderingParser.SortSpecificationContext parsedSortSpecification : parsedSortSpecifications ) {
visitSortSpecification( parsedSortSpecification );
specifications.add( visitSortSpecification( parsedSortSpecification ) );
}
Objects.requireNonNull( specifications );
return specifications;
}
@ -76,8 +76,63 @@ public class ParseTreeVisitor extends OrderingParserBaseVisitor {
}
@Override
public SortExpression visitExpression(OrderingParser.ExpressionContext ctx) {
throw new NotYetImplementedFor6Exception( getClass() );
public SortExpression visitExpression(ExpressionContext ctx) {
if ( ctx.function() != null ) {
return visitFunction( ctx.function() );
}
if ( ctx.identifier() != null ) {
pathConsumer.consumeIdentifier( ctx.identifier().getText(), true, true );
return (SortExpression) pathConsumer.getConsumedPart();
}
assert ctx.dotIdentifier() != null;
final int numberOfParts = ctx.dotIdentifier().IDENTIFIER().size();
boolean firstPass = true;
for ( int i = 0; i < numberOfParts; i++ ) {
final TerminalNode partNode = ctx.dotIdentifier().IDENTIFIER().get( i );
partNode.getText();
pathConsumer.consumeIdentifier(
ctx.identifier().getText(),
firstPass,
true
);
firstPass = false;
}
return (SortExpression) pathConsumer.getConsumedPart();
}
@Override
public FunctionExpression visitFunction(OrderingParser.FunctionContext ctx) {
if ( ctx.simpleFunction() != null ) {
final FunctionExpression function = new FunctionExpression(
ctx.simpleFunction().identifier().getText(),
ctx.simpleFunction().functionArguments().expression().size()
);
for ( int i = 0; i < ctx.simpleFunction().functionArguments().expression().size(); i++ ) {
final ExpressionContext arg = ctx.simpleFunction().functionArguments().expression( i );
function.addArgument( visitExpression( arg ) );
}
return function;
}
assert ctx.packagedFunction() != null;
final FunctionExpression function = new FunctionExpression(
ctx.packagedFunction().dotIdentifier().getText(),
ctx.packagedFunction().functionArguments().expression().size()
);
for ( int i = 0; i < ctx.packagedFunction().functionArguments().expression().size(); i++ ) {
final ExpressionContext arg = ctx.packagedFunction().functionArguments().expression( i );
function.addArgument( visitExpression( arg ) );
}
return function;
}
@Override

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.metamodel.mapping.ordering.ast;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
@ -35,6 +36,19 @@ public class RootSequencePart implements SequencePart {
return new CollectionSubPath( pluralAttributeMapping, subPart );
}
// the above checks for explicit `{element}` or `{index}` usage. Try also as an implicit element sub-part reference
if ( pluralAttributeMapping.getElementDescriptor() instanceof EmbeddableValuedModelPart ) {
final EmbeddableValuedModelPart elementDescriptor = (EmbeddableValuedModelPart) pluralAttributeMapping.getElementDescriptor();
final ModelPart elementSubPart = elementDescriptor.findSubPart( name, null );
if ( elementSubPart != null ) {
final CollectionSubPath elementPath = new CollectionSubPath(
pluralAttributeMapping,
elementDescriptor
);
return new SubDomainPath( elementPath, elementSubPart );
}
}
if ( isTerminal ) {
// assume a column-reference
return new ColumnReference( name );

View File

@ -141,10 +141,7 @@ public abstract class AbstractCollectionPersister
private final String sqlWhereStringTemplate;
private final boolean hasOrder;
// private final OrderByTranslation orderByTranslation;
private final boolean hasManyToManyOrder;
// private final OrderByTranslation manyToManyOrderByTranslation;
private final int baseIndex;
@ -601,21 +598,7 @@ public abstract class AbstractCollectionPersister
}
hasOrder = collectionBootDescriptor.getOrderBy() != null;
if ( hasOrder ) {
LOG.debugf( "Translating order-by fragment [%s] for collection role : %s", collectionBootDescriptor.getOrderBy(), getRole() );
// orderByTranslation = Template.translateOrderBy(
// collectionBinding.getOrderBy(),
// new ColumnMapperImpl(),
// factory,
// dialect,
// factory.getSqlFunctionRegistry()
// );
throw new NotYetImplementedFor6Exception( getClass() );
}
else {
// orderByTranslation = null;
}
hasManyToManyOrder = collectionBootDescriptor.getManyToManyOrdering() != null;
// Handle any filters applied to this collectionBinding
filterHelper = new FilterHelper( collectionBootDescriptor.getFilters(), factory);
@ -629,22 +612,6 @@ public abstract class AbstractCollectionPersister
null :
Template.renderWhereStringTemplate( manyToManyWhereString, factory.getDialect(), factory.getSqlFunctionRegistry() );
hasManyToManyOrder = collectionBootDescriptor.getManyToManyOrdering() != null;
if ( hasManyToManyOrder ) {
LOG.debugf( "Translating many-to-many order-by fragment [%s] for collection role : %s", collectionBootDescriptor.getOrderBy(), getRole() );
// manyToManyOrderByTranslation = Template.translateOrderBy(
// collectionBinding.getManyToManyOrdering(),
// new ColumnMapperImpl(),
// factory,
// dialect,
// factory.getSqlFunctionRegistry()
// );
throw new NotYetImplementedFor6Exception( getClass() );
}
else {
// manyToManyOrderByTranslation = null;
}
comparator = collectionBootDescriptor.getComparator();
initCollectionPropertyMap();

View File

@ -7,6 +7,7 @@
package org.hibernate.orm.test.metamodel.mapping.collections;
import org.hibernate.Hibernate;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.gambit.EntityOfMaps;
@ -21,6 +22,9 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author Steve Ebersole
*/
@ -99,4 +103,17 @@ public class MapOperationTests {
// re-create it so the drop-data can succeed
createData( scope );
}
@Test
public void testOrderedMap(SessionFactoryScope scope) {
// atm we can only check the fragment translation
final CollectionPersister collectionDescriptor = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel()
.findCollectionDescriptor( EntityOfMaps.class.getName() + ".componentByBasicOrdered" );
assertThat(
collectionDescriptor.getAttributeMapping().getOrderByFragment(),
notNullValue()
);
}
}

View File

@ -7,6 +7,7 @@
package org.hibernate.testing.orm.domain.gambit;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.persistence.Convert;
import javax.persistence.ElementCollection;
@ -14,9 +15,12 @@ import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.MapKeyColumn;
import javax.persistence.MapKeyEnumerated;
import javax.persistence.OneToMany;
import org.hibernate.annotations.OrderBy;
/**
* @author Steve Ebersole
*/
@ -37,6 +41,8 @@ public class EntityOfMaps {
private Map<SimpleEntity,String> basicByOneToMany;
private Map<String, SimpleEntity> manyToManyByBasic;
private Map<String, SimpleComponent> componentByBasicOrdered;
public EntityOfMaps() {
}
@ -178,7 +184,7 @@ public class EntityOfMaps {
this.oneToManyByBasic = oneToManyByBasic;
}
public void addOneToManyByComponent(String key, SimpleEntity value) {
public void addOneToManyByBasic(String key, SimpleEntity value) {
if ( oneToManyByBasic == null ) {
oneToManyByBasic = new HashMap<>();
}
@ -225,4 +231,27 @@ public class EntityOfMaps {
manyToManyByBasic.put( key, value );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// componentByBasicOrdered
// NOTE : effectively the same as a natural-sorted map in terms of reading
@ElementCollection
@MapKeyColumn( name = "ordered_component_key")
@OrderBy( clause = "ordered_component_key, ordered_component_key" )
public Map<String, SimpleComponent> getComponentByBasicOrdered() {
return componentByBasicOrdered;
}
public void setComponentByBasicOrdered(Map<String, SimpleComponent> componentByBasicOrdered) {
this.componentByBasicOrdered = componentByBasicOrdered;
}
public void addComponentByBasicOrdered(String key, SimpleComponent value) {
if ( componentByBasicOrdered == null ) {
componentByBasicOrdered = new LinkedHashMap<>();
}
componentByBasicOrdered.put( key, value );
}
}