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:
parent
8600058784
commit
284b2c5677
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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,9 +58,23 @@ public class OrderByFragmentTranslator {
|
|||
|
||||
final ParseTreeVisitor visitor = new ParseTreeVisitor( pluralAttributeMapping, context );
|
||||
|
||||
final List<SortSpecification> interpretation = visitor.visitOrderByFragment( parseTree );
|
||||
final List<SortSpecification> tree = visitor.visitOrderByFragment( parseTree );
|
||||
|
||||
throw new NotYetImplementedFor6Exception( OrderByFragmentTranslator.class );
|
||||
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 );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue