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

- complete support other than function support which is still overall not implemented
This commit is contained in:
Steve Ebersole 2019-12-16 15:01:56 -06:00
parent 1d4bb08ef7
commit 0ec232a326
53 changed files with 990 additions and 321 deletions

View File

@ -7,8 +7,7 @@
package org.hibernate;
/**
* Hibernate often deals with compound names/paths. This interface
* defines a standard way of interacting with them
* Hibernate often deals with compound names/paths. This interface defines a standard way of interacting with them
*
* @author Steve Ebersole
*/

View File

@ -8,7 +8,9 @@ package org.hibernate.loader.ast.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@ -27,6 +29,7 @@ import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SimpleFromClauseAccessImpl;
@ -172,9 +175,9 @@ public class LoaderSelectBuilder {
this.jdbcParameterConsumer = jdbcParameterConsumer;
}
private SelectStatement generateSelect() {
final NavigablePath rootNavigablePath = new NavigablePath( loadable.getRootPathName() );
final QuerySpec rootQuerySpec = new QuerySpec( true );
final List<DomainResult> domainResults;
@ -187,8 +190,6 @@ public class LoaderSelectBuilder {
creationContext
);
final NavigablePath rootNavigablePath = new NavigablePath( loadable.getRootPathName() );
final TableGroup rootTableGroup = loadable.createRootTableGroup(
rootNavigablePath,
null,
@ -203,6 +204,10 @@ public class LoaderSelectBuilder {
rootQuerySpec.getFromClause().addRoot( rootTableGroup );
sqlAstCreationState.getFromClauseAccess().registerTableGroup( rootNavigablePath, rootTableGroup );
if ( loadable instanceof PluralAttributeMapping ) {
applyOrdering( rootTableGroup, (PluralAttributeMapping) loadable );
}
if ( partsToSelect != null && !partsToSelect.isEmpty() ) {
domainResults = new ArrayList<>();
for ( ModelPart part : partsToSelect ) {
@ -253,6 +258,12 @@ public class LoaderSelectBuilder {
sqlAstCreationState
);
if ( orderByFragments != null ) {
orderByFragments.forEach(
(orderByFragment, tableGroup) -> orderByFragment.apply( rootQuerySpec, tableGroup, sqlAstCreationState )
);
}
return new SelectStatement( rootQuerySpec, domainResults );
}
@ -344,10 +355,31 @@ public class LoaderSelectBuilder {
}
}
private Map<OrderByFragment,TableGroup> orderByFragments;
private void applyOrdering(TableGroup tableGroup, PluralAttributeMapping pluralAttributeMapping) {
if ( pluralAttributeMapping.getOrderByFragment() != null ) {
applyOrdering( tableGroup, pluralAttributeMapping.getOrderByFragment() );
}
if ( pluralAttributeMapping.getManyToManyOrderByFragment() != null ) {
applyOrdering( tableGroup, pluralAttributeMapping.getManyToManyOrderByFragment() );
}
}
private void applyOrdering(
TableGroup tableGroup,
OrderByFragment orderByFragment) {
if ( orderByFragments == null ) {
orderByFragments = new LinkedHashMap<>();
}
orderByFragments.put( orderByFragment, tableGroup );
}
private final CircularFetchDetector circularFetchDetector = new CircularFetchDetector();
private int fetchDepth = 0;
private List<Fetch> visitFetches(FetchParent fetchParent, LoaderSqlAstCreationState creationState) {
private List<Fetch> visitFetches(FetchParent fetchParent, QuerySpec querySpec, LoaderSqlAstCreationState creationState) {
log.tracef( "Starting visitation of FetchParent's Fetchables : %s", fetchParent.getNavigablePath() );
final List<Fetch> fetches = new ArrayList<>();
@ -393,7 +425,7 @@ public class LoaderSelectBuilder {
}
try {
if(!(fetchable instanceof BasicValuedModelPart)) {
if ( ! (fetchable instanceof BasicValuedModelPart) ) {
fetchDepth--;
}
Fetch fetch = fetchable.generateFetch(
@ -406,9 +438,18 @@ public class LoaderSelectBuilder {
creationState
);
fetches.add( fetch );
if ( fetchable instanceof PluralAttributeMapping && fetchTiming == FetchTiming.IMMEDIATE ) {
applyOrdering(
querySpec,
fetchablePath,
( (PluralAttributeMapping) fetchable ),
creationState
);
}
}
finally {
if(!(fetchable instanceof BasicValuedModelPart)) {
if ( ! (fetchable instanceof BasicValuedModelPart) ) {
fetchDepth--;
}
}
@ -419,7 +460,22 @@ public class LoaderSelectBuilder {
referencedMappingContainer.visitFetchables( processor, null );
return fetches;
} private SelectStatement generateSelect(SubselectFetch subselect) {
}
private void applyOrdering(
QuerySpec ast,
NavigablePath navigablePath,
PluralAttributeMapping pluralAttributeMapping,
LoaderSqlAstCreationState sqlAstCreationState) {
assert pluralAttributeMapping.getAttributeName().equals( navigablePath.getLocalName() );
final TableGroup tableGroup = sqlAstCreationState.getFromClauseAccess().getTableGroup( navigablePath );
assert tableGroup != null;
applyOrdering( tableGroup, pluralAttributeMapping );
}
private SelectStatement generateSelect(SubselectFetch subselect) {
// todo (6.0) : i think we may even be able to convert this to a join by piecing together
// parts from the subselect-fetch sql-ast..
@ -460,6 +516,9 @@ public class LoaderSelectBuilder {
rootQuerySpec.getFromClause().addRoot( rootTableGroup );
sqlAstCreationState.getFromClauseAccess().registerTableGroup( rootNavigablePath, rootTableGroup );
// NOTE : no need to check - we are explicitly processing a plural-attribute
applyOrdering( rootTableGroup, (PluralAttributeMapping) loadable );
// generate and apply the restriction
applySubSelectRestriction(
rootQuerySpec,

View File

@ -43,20 +43,24 @@ import org.hibernate.sql.results.graph.FetchParent;
*/
public class LoaderSqlAstCreationState
implements SqlAstProcessingState, SqlAstCreationState, DomainResultCreationState, QueryOptions {
interface FetchProcessor {
List<Fetch> visitFetches(FetchParent fetchParent, QuerySpec querySpec, LoaderSqlAstCreationState creationState);
}
private final QuerySpec querySpec;
private final SqlAliasBaseManager sqlAliasBaseManager;
private final SqlAstCreationContext sf;
private final SqlAstQuerySpecProcessingStateImpl processingState;
private final FromClauseAccess fromClauseAccess;
private final LockOptions lockOptions;
private final BiFunction<FetchParent, LoaderSqlAstCreationState, List<Fetch>> fetchProcessor;
private final FetchProcessor fetchProcessor;
public LoaderSqlAstCreationState(
QuerySpec querySpec,
SqlAliasBaseManager sqlAliasBaseManager,
FromClauseAccess fromClauseAccess,
LockOptions lockOptions,
BiFunction<FetchParent, LoaderSqlAstCreationState, List<Fetch>> fetchProcessor,
FetchProcessor fetchProcessor,
SqlAstCreationContext sf) {
this.querySpec = querySpec;
this.sqlAliasBaseManager = sqlAliasBaseManager;
@ -83,7 +87,7 @@ public class LoaderSqlAstCreationState
sqlAliasBaseManager,
new FromClauseIndex(),
lockOptions,
(fetchParent,state) -> Collections.emptyList(),
(fetchParent, ast, state) -> Collections.emptyList(),
sf
);
}
@ -124,7 +128,7 @@ public class LoaderSqlAstCreationState
@Override
public List<Fetch> visitFetches(FetchParent fetchParent) {
return fetchProcessor.apply( fetchParent, this );
return fetchProcessor.visitFetches( fetchParent, getQuerySpec(), this );
}
@Override

View File

@ -27,6 +27,24 @@ import org.hibernate.type.spi.TypeConfiguration;
* @author Steve Ebersole
*/
public interface Bindable {
/*
* todo (6.0) : much of this contract uses Clause which (1) kludgy and (2) not always necessary
* - e.g. see the note below wrt "2 forms of JDBC-type visiting"
*
* Instead, in keeping with the general shift away from the getter paradigm to a more functional (Consumer,
* Function, etc) paradigm, I propose something more like:
*
* interface Bindable {
* void apply(UpdateStatement sqlAst, ..., SqlAstCreationState creationState);
* void apply(DeleteStatement sqlAst, ..., SqlAstCreationState creationState);
*
* Expression toSqlAst(..., SqlAstCreationState creationState);
*
* // plus the `DomainResult`, `Fetch` (via `DomainResultProducer` and `Fetchable`)
* // handling most impls already provide
* }
*/
default int getJdbcTypeCount(TypeConfiguration typeConfiguration) {
final AtomicInteger value = new AtomicInteger( 0 );
visitJdbcTypes(

View File

@ -28,17 +28,22 @@ public interface CollectionPart extends ModelPart, Fetchable {
}
public static Nature fromName(String name) {
// NOTE : the `$x$` form comes form order-by handling
// todo (6.0) : ^^ convert these to use the `{x}` form instead?
if ( "key".equals( name ) || "{key}".equals( name )
|| "keys".equals( name ) || "{keys}".equals( name )
|| "index".equals( name ) || "{index}".equals( name )
|| "indices".equals( name ) || "{indices}".equals( name ) ) {
|| "indices".equals( name ) || "{indices}".equals( name )
|| "$index$".equals( name )) {
return INDEX;
}
if ( "element".equals( name ) || "{element}".equals( name )
|| "elements".equals( name ) || "{elements}".equals( name )
|| "value".equals( name ) || "{value}".equals( name )
|| "values".equals( name ) || "{values}".equals( name ) ) {
|| "values".equals( name ) || "{values}".equals( name )
|| "$element$".equals( name ) ) {
return ELEMENT;
}

View File

@ -10,11 +10,11 @@ import java.util.function.BiConsumer;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
/**
@ -24,8 +24,6 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
* @see DomainResultProducer
* @see javax.persistence.metamodel.Bindable
*
* todo (6.0) : do we need to expose ModelPartContainer here? Only if _necessary_
*
* @author Steve Ebersole
*/
public interface ModelPart extends MappingModelExpressable {

View File

@ -10,6 +10,8 @@ package org.hibernate.metamodel.mapping;
* Unifying contract for things that are capable of being an expression in
* the SQL AST.
*
* todo (6.0) : consider adding `#toSqlExpression` returning a {@link org.hibernate.sql.ast.tree.expression.Expression}
*
* @author Steve Ebersole
*/
public interface SqlExpressable {

View File

@ -168,6 +168,7 @@ public class EmbeddedAttributeMapping
this,
fetchParent,
fetchTiming,
selected,
getAttributeMetadataAccess().resolveAttributeMetadata( null ).isNullable(),
creationState
);

View File

@ -13,9 +13,11 @@ import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
@ -41,6 +43,7 @@ import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
/**
* @author Steve Ebersole
@ -114,6 +117,30 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
return FetchStrategy.IMMEDIATE_JOIN;
}
@Override
public int getJdbcTypeCount(TypeConfiguration typeConfiguration) {
return getEmbeddableTypeDescriptor().getJdbcTypeCount( typeConfiguration );
}
@Override
public List<JdbcMapping> getJdbcMappings(TypeConfiguration typeConfiguration) {
return getEmbeddableTypeDescriptor().getJdbcMappings( typeConfiguration );
}
@Override
public void visitJdbcTypes(Consumer<JdbcMapping> action, Clause clause, TypeConfiguration typeConfiguration) {
getEmbeddableTypeDescriptor().visitJdbcTypes( action, clause, typeConfiguration );
}
@Override
public void visitJdbcValues(
Object value,
Clause clause,
JdbcValuesConsumer valuesConsumer,
SharedSessionContractImplementor session) {
getEmbeddableTypeDescriptor().visitJdbcValues( value, clause, valuesConsumer, session );
}
@Override
public Fetch generateFetch(
FetchParent fetchParent,
@ -128,6 +155,7 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
this,
fetchParent,
FetchTiming.IMMEDIATE,
selected,
false,
creationState
);

View File

@ -257,6 +257,7 @@ public class EmbeddedIdentifierMappingImpl
this,
fetchParent,
fetchTiming,
selected,
attributeMetadataAccess.resolveAttributeMetadata( null ).isNullable(),
creationState
);

View File

@ -386,6 +386,7 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme
return new EagerCollectionFetch(
fetchablePath,
this,
collectionTableGroup,
getAttributeMetadataAccess().resolveAttributeMetadata( null ).isNullable(),
fetchParent,
creationState

View File

@ -6,29 +6,24 @@
*/
package org.hibernate.metamodel.mapping.ordering;
import java.util.List;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.sql.ast.tree.select.QuerySpec;
/**
* Represents the translation result
* Represents the translation result. Defines the ability to apply the indicated ordering to the SQL AST
* being built
*
* @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.
* Apply the ordering to the given SQL AST
*
* @param aliasResolver The strategy to resolver the proper table alias to use per column
*
* @return The fully translated and replaced fragment.
* @param ast The SQL AST
* @param tableGroup The TableGroup the order-by is applied "against"
* @param creationState The SQL AST creation state
*/
String injectAliases(AliasResolver aliasResolver);
void apply(QuerySpec ast, TableGroup tableGroup, SqlAstCreationState creationState);
}

View File

@ -8,14 +8,15 @@ package org.hibernate.metamodel.mapping.ordering;
import java.util.List;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.grammars.ordering.OrderingLexer;
import org.hibernate.grammars.ordering.OrderingParser;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.ast.OrderingSpecification;
import org.hibernate.metamodel.mapping.ordering.ast.ParseTreeVisitor;
import org.hibernate.metamodel.mapping.ordering.ast.SortSpecification;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.jboss.logging.Logger;
@ -58,21 +59,24 @@ public class OrderByFragmentTranslator {
final ParseTreeVisitor visitor = new ParseTreeVisitor( pluralAttributeMapping, context );
final List<SortSpecification> tree = visitor.visitOrderByFragment( parseTree );
final List<OrderingSpecification> specs = visitor.visitOrderByFragment( parseTree );
return new OrderByFragment() {
final List<SortSpecification> sortSpecifications = tree;
private final List<OrderingSpecification> fragmentSpecs = specs;
@Override
public List<org.hibernate.sql.ast.tree.select.SortSpecification> toSqlAst(
TableGroup tableGroup,
SqlAstCreationState creationState) {
throw new NotYetImplementedFor6Exception( OrderByFragmentTranslator.class );
}
public void apply(QuerySpec ast, TableGroup tableGroup, SqlAstCreationState creationState) {
for ( int i = 0; i < fragmentSpecs.size(); i++ ) {
final OrderingSpecification orderingSpec = fragmentSpecs.get( i );
@Override
public String injectAliases(AliasResolver aliasResolver) {
throw new NotYetImplementedFor6Exception( OrderByFragmentTranslator.class );
orderingSpec.getExpression().apply(
ast,
tableGroup,
orderingSpec.getCollation(),
orderingSpec.getSortOrder(),
creationState
);
}
}
};
}

View File

@ -0,0 +1,70 @@
/*
* 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.ast;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedCollectionPart;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
import org.hibernate.query.NavigablePath;
/**
* Represents a part of a `CollectionPart` (element or index descriptor) as a DomainPath
*
* @author Steve Ebersole
*/
public class CollectionPartPath implements DomainPath {
private final NavigablePath navigablePath;
private final PluralAttributePath lhs;
private final CollectionPart referencedPart;
CollectionPartPath(
PluralAttributePath lhs,
CollectionPart referencedPart) {
this.lhs = lhs;
this.referencedPart = referencedPart;
this.navigablePath = lhs.getNavigablePath().append( referencedPart.getPartName() );
}
@Override
public PluralAttributeMapping getPluralAttribute() {
return lhs.getPluralAttribute();
}
@Override
public NavigablePath getNavigablePath() {
return navigablePath;
}
@Override
public PluralAttributePath getLhs() {
return lhs;
}
@Override
public CollectionPart getReferenceModelPart() {
return referencedPart;
}
@Override
public SequencePart resolvePathPart(
String name,
boolean isTerminal,
TranslationContext translationContext) {
if ( referencedPart instanceof EmbeddedCollectionPart ) {
final ModelPart subPart = ( (EmbeddedCollectionPart) referencedPart ).findSubPart( name, null );
return new DomainPathContinuation( navigablePath.append( name ), this, subPart );
}
throw new PathResolutionException(
"Could not resolve order-by path : " + navigablePath + " -> " + name
);
}
}

View File

@ -1,49 +0,0 @@
/*
* 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.ast;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
/**
* @author Steve Ebersole
*/
public class CollectionSubPath implements DomainPath {
private final PluralAttributeMapping pluralAttribute;
private final ModelPart referenceModelPart;
public CollectionSubPath(
PluralAttributeMapping pluralAttribute,
ModelPart referenceModelPart) {
this.pluralAttribute = pluralAttribute;
this.referenceModelPart = referenceModelPart;
}
@Override
public PluralAttributeMapping getPluralAttribute() {
return pluralAttribute;
}
@Override
public DomainPath getLhs() {
return null;
}
@Override
public ModelPart getReferenceModelPart() {
return referenceModelPart;
}
@Override
public SequencePart resolvePathPart(
String name,
boolean isTerminal,
TranslationContext translationContext) {
return null;
}
}

View File

@ -6,7 +6,15 @@
*/
package org.hibernate.metamodel.mapping.ordering.ast;
import org.hibernate.SortOrder;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SortSpecification;
/**
* Represents a column-reference used in an order-by fragment
@ -16,11 +24,13 @@ import org.hibernate.metamodel.mapping.ordering.TranslationContext;
*
* @author Steve Ebersole
*/
public class ColumnReference implements SortExpression, SequencePart {
public class ColumnReference implements OrderingExpression, SequencePart {
private final String columnExpression;
private final NavigablePath rootPath;
public ColumnReference(String columnExpression) {
public ColumnReference(String columnExpression, NavigablePath rootPath) {
this.columnExpression = columnExpression;
this.rootPath = rootPath;
}
public String getColumnExpression() {
@ -34,4 +44,34 @@ public class ColumnReference implements SortExpression, SequencePart {
TranslationContext translationContext) {
throw new UnsupportedOperationException( "ColumnReference cannot be de-referenced" );
}
@Override
public void apply(
QuerySpec ast,
TableGroup tableGroup,
String collation,
SortOrder sortOrder,
SqlAstCreationState creationState) {
final TableReference primaryTableReference = tableGroup.getPrimaryTableReference();
final SqlExpressionResolver sqlExpressionResolver = creationState.getSqlExpressionResolver();
ast.addSortSpecification(
new SortSpecification(
sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( primaryTableReference, columnExpression ),
sqlAstProcessingState -> new org.hibernate.sql.ast.tree.expression.ColumnReference(
tableGroup.getPrimaryTableReference(),
columnExpression,
// because these ordering fragments are only ever part of the order-by clause, there
// is no need for the JdbcMapping
null,
creationState.getCreationContext().getSessionFactory()
)
),
collation,
sortOrder
)
);
}
}

View File

@ -6,15 +6,29 @@
*/
package org.hibernate.metamodel.mapping.ordering.ast;
import org.hibernate.SortOrder;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SortSpecification;
/**
* Represents a domain-path (model part path) used in an order-by fragment
*
* @author Steve Ebersole
*/
public interface DomainPath extends SortExpression, SequencePart {
public interface DomainPath extends OrderingExpression, SequencePart {
NavigablePath getNavigablePath();
DomainPath getLhs();
ModelPart getReferenceModelPart();
@ -22,4 +36,61 @@ public interface DomainPath extends SortExpression, SequencePart {
default PluralAttributeMapping getPluralAttribute() {
return getLhs().getPluralAttribute();
}
@Override
default void apply(
QuerySpec ast,
TableGroup tableGroup,
String collation,
SortOrder sortOrder,
SqlAstCreationState creationState) {
final SqlAstCreationContext creationContext = creationState.getCreationContext();
final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory();
final SqlExpressionResolver sqlExprResolver = creationState.getSqlExpressionResolver();
if ( getReferenceModelPart() instanceof BasicValuedModelPart ) {
final BasicValuedModelPart basicValuedPart = (BasicValuedModelPart) getReferenceModelPart();
final TableReference tableReference = tableGroup.resolveTableReference( basicValuedPart.getContainingTableExpression() );
ast.addSortSpecification(
new SortSpecification(
new ColumnReference(
tableReference,
basicValuedPart.getMappedColumnExpression(),
basicValuedPart.getJdbcMapping(),
creationState.getCreationContext().getSessionFactory()
),
collation,
sortOrder
)
);
}
else {
getReferenceModelPart().visitColumns(
(tableExpression, columnExpression, jdbcMapping) -> {
final TableReference tableReference = tableGroup.resolveTableReference( tableExpression );
ast.addSortSpecification(
new SortSpecification(
sqlExprResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
tableExpression,
columnExpression
),
sqlAstProcessingState -> new ColumnReference(
tableReference,
columnExpression,
jdbcMapping,
sessionFactory
)
),
collation,
sortOrder
)
);
}
);
}
}
}

View File

@ -0,0 +1,73 @@
/*
* 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.ast;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
import org.hibernate.query.NavigablePath;
/**
* A path relative to either a CollectionPartPath (element/index DomainPath) or another DomainPathContinuation
*
* @author Steve Ebersole
*/
public class DomainPathContinuation implements DomainPath {
private final NavigablePath navigablePath;
private final DomainPath lhs;
private final ModelPart referencedModelPart;
public DomainPathContinuation(NavigablePath navigablePath, DomainPath lhs, ModelPart referencedModelPart) {
this.navigablePath = navigablePath;
this.lhs = lhs;
this.referencedModelPart = referencedModelPart;
}
@Override
public NavigablePath getNavigablePath() {
return navigablePath;
}
@Override
public DomainPath getLhs() {
return lhs;
}
@Override
public ModelPart getReferenceModelPart() {
return referencedModelPart;
}
@Override
public SequencePart resolvePathPart(
String name,
boolean isTerminal,
TranslationContext translationContext) {
if ( referencedModelPart.getPartMappingType() instanceof EmbeddableValuedModelPart ) {
final EmbeddableValuedModelPart embeddableValuedPart =
(EmbeddableValuedModelPart) referencedModelPart.getPartMappingType();
final ModelPart subPart = embeddableValuedPart.findSubPart( name, null );
if ( subPart == null ) {
throw new PathResolutionException(
"Could not resolve path token : " + referencedModelPart + " -> " + name
);
}
return new DomainPathContinuation(
navigablePath.append( name ),
this,
subPart
);
}
throw new PathResolutionException(
"Domain path of type `" + referencedModelPart.getPartMappingType() +
"` -> `" + name + "`"
);
}
}

View File

@ -10,14 +10,20 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.SortOrder;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.select.QuerySpec;
/**
* Represents a function used in an order-by fragment
*
* @author Steve Ebersole
*/
public class FunctionExpression implements SortExpression {
public class FunctionExpression implements OrderingExpression {
private final String name;
private final List<SortExpression> arguments;
private final List<OrderingExpression> arguments;
public FunctionExpression(String name, int numberOfArguments) {
this.name = name;
@ -30,11 +36,21 @@ public class FunctionExpression implements SortExpression {
return name;
}
public List<SortExpression> getArguments() {
public List<OrderingExpression> getArguments() {
return arguments;
}
public void addArgument(SortExpression argument) {
public void addArgument(OrderingExpression argument) {
arguments.add( argument );
}
@Override
public void apply(
QuerySpec ast,
TableGroup tableGroup,
String collation,
SortOrder sortOrder,
SqlAstCreationState creationState) {
throw new NotYetImplementedFor6Exception( getClass() );
}
}

View File

@ -0,0 +1,24 @@
/*
* 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.ast;
import org.hibernate.SortOrder;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.select.QuerySpec;
/**
* Contract for anything that can be a sort expression
*
* @author Steve Ebersole
*/
public interface OrderingExpression extends Node {
/**
* Apply the SQL AST sort-specifications associated with this ordering-expression
*/
void apply(QuerySpec ast, TableGroup tableGroup, String collation, SortOrder sortOrder, SqlAstCreationState creationState);
}

View File

@ -14,19 +14,24 @@ import org.hibernate.SortOrder;
*
* @author Steve Ebersole
*/
public class SortSpecification implements Node {
private final SortExpression sortExpression;
public class OrderingSpecification implements Node {
private final OrderingExpression orderingExpression;
private String collation;
private SortOrder sortOrder;
private NullPrecedence nullPrecedence = NullPrecedence.NONE;
public SortSpecification(SortExpression sortExpression) {
this.sortExpression = sortExpression;
public OrderingSpecification(OrderingExpression orderingExpression) {
this.orderingExpression = orderingExpression;
}
public SortExpression getSortExpression() {
return sortExpression;
public OrderingSpecification(OrderingExpression orderingExpression, SortOrder sortOrder) {
this.orderingExpression = orderingExpression;
this.sortOrder = sortOrder;
}
public OrderingExpression getExpression() {
return orderingExpression;
}
public String getCollation() {

View File

@ -7,6 +7,7 @@
package org.hibernate.metamodel.mapping.ordering.ast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@ -30,8 +31,6 @@ public class ParseTreeVisitor extends OrderingParserBaseVisitor {
private final PathConsumer pathConsumer;
private final TranslationContext translationContext;
private List<SortSpecification> specifications;
public ParseTreeVisitor(
PluralAttributeMapping pluralAttributeMapping,
TranslationContext translationContext) {
@ -40,11 +39,15 @@ public class ParseTreeVisitor extends OrderingParserBaseVisitor {
}
@Override
public List<SortSpecification> visitOrderByFragment(OrderingParser.OrderByFragmentContext parsedFragment) {
public List<OrderingSpecification> visitOrderByFragment(OrderingParser.OrderByFragmentContext parsedFragment) {
final List<OrderingParser.SortSpecificationContext> parsedSortSpecifications = parsedFragment.sortSpecification();
Objects.requireNonNull( parsedSortSpecifications );
assert parsedSortSpecifications != null;
this.specifications = new ArrayList<>( parsedSortSpecifications.size() );
if ( parsedSortSpecifications.size() == 1 ) {
return Collections.singletonList( visitSortSpecification( parsedSortSpecifications.get( 0 ) ) );
}
final List<OrderingSpecification> specifications = new ArrayList<>( parsedSortSpecifications.size() );
for ( OrderingParser.SortSpecificationContext parsedSortSpecification : parsedSortSpecifications ) {
specifications.add( visitSortSpecification( parsedSortSpecification ) );
@ -54,20 +57,21 @@ public class ParseTreeVisitor extends OrderingParserBaseVisitor {
}
@Override
public SortSpecification visitSortSpecification(OrderingParser.SortSpecificationContext parsedSpec) {
public OrderingSpecification visitSortSpecification(OrderingParser.SortSpecificationContext parsedSpec) {
assert parsedSpec != null;
assert parsedSpec.expression() != null;
final SortSpecification result = new SortSpecification( visitExpression( parsedSpec.expression() ) );
final OrderingSpecification result = new OrderingSpecification( visitExpression( parsedSpec.expression() ) );
if ( parsedSpec.collationSpecification() != null ) {
result.setCollation( parsedSpec.collationSpecification().identifier().getText() );
}
if ( parsedSpec.direction() != null ) {
if ( parsedSpec.direction().ASC() != null ) {
result.setSortOrder( SortOrder.ASCENDING );
}
if ( parsedSpec.direction() != null && parsedSpec.direction().DESC() != null ) {
result.setSortOrder( SortOrder.DESCENDING );
}
else {
result.setSortOrder( SortOrder.ASCENDING );
}
// todo (6.0) : null-precedence (see grammar notes)
@ -76,14 +80,14 @@ public class ParseTreeVisitor extends OrderingParserBaseVisitor {
}
@Override
public SortExpression visitExpression(ExpressionContext ctx) {
public OrderingExpression 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();
return (OrderingExpression) pathConsumer.getConsumedPart();
}
assert ctx.dotIdentifier() != null;
@ -101,7 +105,7 @@ public class ParseTreeVisitor extends OrderingParserBaseVisitor {
firstPass = false;
}
return (SortExpression) pathConsumer.getConsumedPart();
return (OrderingExpression) pathConsumer.getConsumedPart();
}
@Override

View File

@ -30,7 +30,8 @@ public class PathConsumer {
private SequencePart currentPart;
public PathConsumer(
PluralAttributeMapping pluralAttributeMapping, TranslationContext translationContext) {
PluralAttributeMapping pluralAttributeMapping,
TranslationContext translationContext) {
this.translationContext = translationContext;
this.rootSequencePart = new RootSequencePart( pluralAttributeMapping );

View File

@ -9,10 +9,16 @@ package org.hibernate.metamodel.mapping.ordering.ast;
import org.hibernate.HibernateException;
/**
* Indicates a problem resolving a domain-path occurring in an order-by fragment
*
* @author Steve Ebersole
*/
public class UnexpectedTokenException extends HibernateException {
public UnexpectedTokenException(String message) {
public class PathResolutionException extends HibernateException {
public PathResolutionException(String message) {
super( message );
}
public PathResolutionException(String message, Throwable cause) {
super( message, cause );
}
}

View File

@ -0,0 +1,99 @@
/*
* 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.ast;
import org.hibernate.metamodel.mapping.CollectionPart;
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;
import org.hibernate.query.NavigablePath;
/**
* Represents the collection as a DomainPath
*
* @see RootSequencePart
*
* @author Steve Ebersole
*/
public class PluralAttributePath implements DomainPath {
private final NavigablePath navigablePath;
private final PluralAttributeMapping pluralAttributeMapping;
PluralAttributePath(PluralAttributeMapping pluralAttributeMapping) {
this.navigablePath = new NavigablePath( pluralAttributeMapping.getRootPathName() );
this.pluralAttributeMapping = pluralAttributeMapping;
}
@Override
public NavigablePath getNavigablePath() {
return navigablePath;
}
@Override
public DomainPath getLhs() {
return null;
}
@Override
public PluralAttributeMapping getReferenceModelPart() {
return pluralAttributeMapping;
}
@Override
public DomainPath resolvePathPart(
String name,
boolean isTerminal,
TranslationContext translationContext) {
final ModelPart subPart = pluralAttributeMapping.findSubPart( name, null );
if ( subPart != null ) {
assert subPart instanceof CollectionPart;
return new CollectionPartPath( this, (CollectionPart) subPart );
}
// the above checks for explicit element or index descriptor references
// try also as an implicit element or index sub-part reference...
if ( pluralAttributeMapping.getElementDescriptor() instanceof EmbeddableValuedModelPart ) {
final EmbeddableValuedModelPart elementDescriptor = (EmbeddableValuedModelPart) pluralAttributeMapping.getElementDescriptor();
final ModelPart elementSubPart = elementDescriptor.findSubPart( name, null );
if ( elementSubPart != null ) {
// create the CollectionSubPath to use as the `lhs` for the element sub-path
final CollectionPartPath elementPath = new CollectionPartPath(
this,
(CollectionPart) elementDescriptor
);
return new DomainPathContinuation(
elementPath.getNavigablePath().append( name ),
this,
elementSubPart
);
}
}
if ( pluralAttributeMapping.getIndexDescriptor() instanceof EmbeddableValuedModelPart ) {
final EmbeddableValuedModelPart indexDescriptor = (EmbeddableValuedModelPart) pluralAttributeMapping.getIndexDescriptor();
final ModelPart indexSubPart = indexDescriptor.findSubPart( name, null );
if ( indexSubPart != null ) {
// create the CollectionSubPath to use as the `lhs` for the element sub-path
final CollectionPartPath indexPath = new CollectionPartPath(
this,
(CollectionPart) indexDescriptor
);
return new DomainPathContinuation(
indexPath.getNavigablePath().append( name ),
this,
indexSubPart
);
}
}
return null;
}
}

View File

@ -6,21 +6,21 @@
*/
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;
/**
* PathPart implementation used to translate the root of a path
* SequencePart implementation used to translate the root of a path
*
* @see PluralAttributePath
*
* @author Steve Ebersole
*/
public class RootSequencePart implements SequencePart {
private final PluralAttributeMapping pluralAttributeMapping;
private final PluralAttributePath pluralAttributePath;
public RootSequencePart(PluralAttributeMapping pluralAttributeMapping) {
this.pluralAttributeMapping = pluralAttributeMapping;
this.pluralAttributePath = new PluralAttributePath( pluralAttributeMapping );
}
@Override
@ -30,33 +30,19 @@ public class RootSequencePart implements SequencePart {
TranslationContext translationContext) {
// could be a column-reference (isTerminal would have to be true) or a domain-path
final ModelPart subPart = pluralAttributeMapping.findSubPart( name, null );
if ( subPart != null ) {
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 );
}
final DomainPath subDomainPath = pluralAttributePath.resolvePathPart( name, isTerminal, translationContext );
if ( subDomainPath != null ) {
return subDomainPath;
}
if ( isTerminal ) {
// assume a column-reference
return new ColumnReference( name );
return new ColumnReference( name, pluralAttributePath.getNavigablePath() );
}
throw new UnexpectedTokenException(
"Could not resolve order-by token : " +
pluralAttributeMapping.getCollectionDescriptor().getRole() + " -> " + name
throw new PathResolutionException(
"Could not resolve order-by path : " +
pluralAttributePath.getReferenceModelPart().getCollectionDescriptor().getRole() + " -> " + name
);
}
}

View File

@ -1,15 +0,0 @@
/*
* 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.ast;
/**
* Contract for anything that can be a sort expression
*
* @author Steve Ebersole
*/
public interface SortExpression extends Node {
}

View File

@ -1,59 +0,0 @@
/*
* 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.ast;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.MappingTypedModelPart;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
/**
* @author Steve Ebersole
*/
public class SubDomainPath implements DomainPath {
private final DomainPath lhs;
private final ModelPart referencedModelPart;
public SubDomainPath(DomainPath lhs, ModelPart referencedModelPart) {
this.lhs = lhs;
this.referencedModelPart = referencedModelPart;
}
@Override
public DomainPath getLhs() {
return lhs;
}
@Override
public ModelPart getReferenceModelPart() {
return referencedModelPart;
}
@Override
public SequencePart resolvePathPart(
String name,
boolean isTerminal,
TranslationContext translationContext) {
if ( referencedModelPart.getPartMappingType() instanceof ManagedMappingType ) {
final ManagedMappingType partMappingType = (ManagedMappingType) referencedModelPart.getPartMappingType();
final ModelPart subPart = partMappingType.findSubPart( name, null );
if ( subPart == null ) {
throw new UnexpectedTokenException(
"Could not resolve path token : " +
referencedModelPart + " -> " + name
);
}
return new SubDomainPath( this, subPart );
}
throw new UnexpectedTokenException(
"Domain path of type `" + referencedModelPart.getPartMappingType() +
"` -> `" + name + "`"
);
}
}

View File

@ -6,31 +6,35 @@
*/
package org.hibernate.metamodel.model.domain;
import java.io.Serializable;
import java.util.Objects;
import org.hibernate.DotIdentifierSequence;
import org.hibernate.internal.util.StringHelper;
/**
* A representation of the static "Navigable" path relative to some "root entity".
* Poorly named.
*
* Should have been named `org.hibernate.metamodel.model.mapping.MappingRole`
*
* Represents a compound path of `ModelPart` nodes rooted at an entity-name.
*
* @author Steve Ebersole
*/
public class NavigableRole implements Serializable {
public class NavigableRole implements DotIdentifierSequence {
public static final String IDENTIFIER_MAPPER_PROPERTY = "_identifierMapper";
private final NavigableRole parent;
private final String navigableName;
private final String localName;
private final String fullPath;
public NavigableRole(NavigableRole parent, String navigableName) {
public NavigableRole(NavigableRole parent, String localName) {
this.parent = parent;
this.navigableName = navigableName;
this.localName = localName;
// the _identifierMapper is a "hidden" property on entities with composite keys.
// concatenating it will prevent the path from correctly being used to look up
// various things such as criteria paths and fetch profile association paths
if ( IDENTIFIER_MAPPER_PROPERTY.equals( navigableName ) ) {
if ( IDENTIFIER_MAPPER_PROPERTY.equals( localName ) ) {
this.fullPath = parent != null ? parent.getFullPath() : "";
}
else {
@ -48,12 +52,12 @@ public class NavigableRole implements Serializable {
prefix = "";
}
this.fullPath = prefix + navigableName;
this.fullPath = prefix + localName;
}
}
public NavigableRole(String navigableName) {
this( null, navigableName );
public NavigableRole(String localName) {
this( null, localName );
}
public NavigableRole() {
@ -68,8 +72,13 @@ public class NavigableRole implements Serializable {
return parent;
}
@Override
public String getLocalName() {
return localName;
}
public String getNavigableName() {
return navigableName;
return getLocalName();
}
public String getFullPath() {
@ -77,7 +86,7 @@ public class NavigableRole implements Serializable {
}
public boolean isRoot() {
return parent == null && StringHelper.isEmpty( navigableName );
return parent == null && StringHelper.isEmpty( localName );
}
@Override

View File

@ -10,13 +10,10 @@ import java.util.Objects;
import org.hibernate.DotIdentifierSequence;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.metamodel.model.domain.NavigableRole;
/**
* A representation of the path to a particular Navigable
* as part of a query relative to a "navigable root".
*
* @see NavigableRole
* Compound-name where each path references to a domain or mapping model-part relative to a root path. Generally
* this root path is an entity name or a collection-role.
*
* @author Steve Ebersole
*/

View File

@ -347,6 +347,8 @@ public abstract class BaseSqmToSqlAstConverter
);
try {
prepareQuerySpec( sqlQuerySpec );
// we want to visit the from-clause first
visitFromClause( sqmQuerySpec.getFromClause() );
@ -388,6 +390,8 @@ public abstract class BaseSqmToSqlAstConverter
sqlQuerySpec.setLimitClauseExpression( visitLimitExpression( sqmQuerySpec.getLimitExpression() ) );
sqlQuerySpec.setOffsetClauseExpression( visitOffsetExpression( sqmQuerySpec.getOffsetExpression() ) );
postProcessQuerySpec( sqlQuerySpec );
return sqlQuerySpec;
}
finally {
@ -395,6 +399,12 @@ public abstract class BaseSqmToSqlAstConverter
}
}
protected void prepareQuerySpec(QuerySpec sqlQuerySpec) {
}
protected void postProcessQuerySpec(QuerySpec sqlQuerySpec) {
}
@Override
public SelectClause visitSelectClause(SqmSelectClause selectClause) {
currentClauseStack.push( Clause.SELECT );

View File

@ -12,7 +12,8 @@ import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.sql.ast.tree.expression.Expression;
/**
* Interpretation of a {@link SqmPath} as part of the translation to SQL AST
* Interpretation of a {@link SqmPath} as part of the translation to SQL AST. We need specialized handling
* for path interpretations because it can (and likely) contains multiple SqlExpressions (entity to its columns, e.g.)
*
* @see org.hibernate.query.sqm.sql.SqmToSqlAstConverter
* @see #getInterpretedSqmPath

View File

@ -6,9 +6,11 @@
*/
package org.hibernate.query.sqm.sql.internal;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.hibernate.HibernateException;
@ -21,7 +23,11 @@ import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.graph.spi.AttributeNodeImplementor;
import org.hibernate.graph.spi.GraphImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.internal.util.collections.StandardStack;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.DynamicInstantiationNature;
@ -55,14 +61,14 @@ import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.results.graph.instantiation.internal.DynamicInstantiation;
import org.hibernate.sql.results.spi.CircularFetchDetector;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.entity.EntityResultGraphNode;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.Fetchable;
import org.hibernate.sql.results.graph.entity.EntityResultGraphNode;
import org.hibernate.sql.results.graph.instantiation.internal.DynamicInstantiation;
import org.hibernate.sql.results.spi.CircularFetchDetector;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
/**
@ -129,6 +135,35 @@ public class StandardSqmSelectTranslator
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// walker
private final Stack<OrderByFragmentConsumer> orderByFragmentConsumerStack = new StandardStack<>();
private interface OrderByFragmentConsumer {
void accept(OrderByFragment orderByFragment, TableGroup tableGroup);
void visitFragments(BiConsumer<OrderByFragment,TableGroup> consumer);
}
private static class StandardOrderByFragmentConsumer implements OrderByFragmentConsumer {
private Map<OrderByFragment, TableGroup> fragments;
@Override
public void accept(OrderByFragment orderByFragment, TableGroup tableGroup) {
if ( fragments == null ) {
fragments = new LinkedHashMap<>();
}
fragments.put( orderByFragment, tableGroup );
}
@Override
public void visitFragments(BiConsumer<OrderByFragment, TableGroup> consumer) {
if ( fragments == null || fragments.isEmpty() ) {
return;
}
fragments.forEach( consumer );
}
}
@Override
public SelectStatement visitSelectStatement(SqmSelectStatement statement) {
final QuerySpec querySpec = visitQuerySpec( statement.getQuerySpec() );
@ -136,6 +171,33 @@ public class StandardSqmSelectTranslator
return new SelectStatement( querySpec, domainResults );
}
@Override
protected void prepareQuerySpec(QuerySpec sqlQuerySpec) {
final boolean topLevel = orderByFragmentConsumerStack.isEmpty();
if ( topLevel ) {
orderByFragmentConsumerStack.push( new StandardOrderByFragmentConsumer() );
}
else {
orderByFragmentConsumerStack.push( null );
}
}
@Override
protected void postProcessQuerySpec(QuerySpec sqlQuerySpec) {
try {
final OrderByFragmentConsumer orderByFragmentConsumer = orderByFragmentConsumerStack.getCurrent();
if ( orderByFragmentConsumer != null ) {
orderByFragmentConsumer.visitFragments(
(orderByFragment, tableGroup) -> {
orderByFragment.apply( sqlQuerySpec, tableGroup, this );
}
);
}
}
finally {
orderByFragmentConsumerStack.pop();
}
}
@Override
public Void visitSelection(SqmSelection sqmSelection) {
@ -314,7 +376,7 @@ public class StandardSqmSelectTranslator
}
try {
return fetchable.generateFetch(
final Fetch fetch = fetchable.generateFetch(
fetchParent,
fetchablePath,
fetchTiming,
@ -323,6 +385,25 @@ public class StandardSqmSelectTranslator
alias,
StandardSqmSelectTranslator.this
);
final OrderByFragmentConsumer orderByFragmentConsumer = orderByFragmentConsumerStack.getCurrent();
if ( orderByFragmentConsumer != null ) {
if ( fetchable instanceof PluralAttributeMapping && fetch.getTiming() == FetchTiming.IMMEDIATE ) {
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable;
final TableGroup tableGroup = getFromClauseIndex().getTableGroup( fetchablePath );
assert tableGroup.getModelPart() == pluralAttributeMapping;
if ( pluralAttributeMapping.getOrderByFragment() != null ) {
orderByFragmentConsumer.accept( pluralAttributeMapping.getOrderByFragment(), tableGroup );
}
if ( pluralAttributeMapping.getManyToManyOrderByFragment() != null ) {
orderByFragmentConsumer.accept( pluralAttributeMapping.getManyToManyOrderByFragment(), tableGroup );
}
}
}
return fetch;
}
catch (RuntimeException e) {
throw new HibernateException(

View File

@ -0,0 +1,15 @@
/*
* 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 for the translation of SQM into SQL AST
*
* @see org.hibernate.query.sqm.sql.SqmTranslation
* @see org.hibernate.query.sqm.sql.SqmTranslator
* @see org.hibernate.query.sqm.sql.SqmTranslatorFactory
*/
package org.hibernate.query.sqm.sql;

View File

@ -0,0 +1,39 @@
/*
* 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.sql.ast.spi;
import org.hibernate.sql.ast.SqlTreeCreationLogger;
/**
* Standard SqlAliasBase impl
*
* @author Steve Ebersole
*/
public class SqlAliasBaseImpl implements SqlAliasBase {
private final String stem;
private int aliasCount;
public SqlAliasBaseImpl(String stem) {
this.stem = stem;
}
@Override
public String getAliasStem() {
return stem;
}
@Override
public String generateNewAlias() {
synchronized (this) {
final String alias = stem + "_" + ( aliasCount++ );
if ( SqlTreeCreationLogger.DEBUG_ENABLED ) {
SqlTreeCreationLogger.LOGGER.debugf( "Created new SQL alias : %s", alias );
}
return alias;
}
}
}

View File

@ -9,8 +9,6 @@ package org.hibernate.sql.ast.spi;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.sql.ast.SqlTreeCreationLogger;
/**
* Helper used in creating unique SQL table aliases for a SQL AST
*
@ -32,28 +30,4 @@ public class SqlAliasBaseManager implements SqlAliasBaseGenerator {
return new SqlAliasBaseImpl( stem + acronymCount );
}
private static class SqlAliasBaseImpl implements SqlAliasBase {
private final String stem;
private int aliasCount;
SqlAliasBaseImpl(String stem) {
this.stem = stem;
}
@Override
public String getAliasStem() {
return stem;
}
@Override
public String generateNewAlias() {
synchronized ( this ) {
final String alias = stem + "_" + ( aliasCount++ );
if ( SqlTreeCreationLogger.DEBUG_ENABLED ) {
SqlTreeCreationLogger.LOGGER.debugf( "Created new SQL alias : %s", alias );
}
return alias;
}
}
}
}

View File

@ -9,12 +9,12 @@ package org.hibernate.sql.ast.spi;
import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
/**
* todo (6.0) : most of this is SQM -> SQL specific. Should move to that package.
* As-is, this complicates SQL AST creation from model walking e.g.
* Access to stuff used while creating a SQL AST
*
* @author Steve Ebersole
*/

View File

@ -8,27 +8,15 @@ package org.hibernate.sql.results.graph;
import java.util.function.Consumer;
import org.hibernate.sql.results.graph.basic.BasicResultGraphNode;
import org.hibernate.sql.results.graph.instantiation.DynamicInstantiationResult;
import org.hibernate.sql.results.graph.entity.EntityResult;
/**
* Represents a result value in the domain query results. Acts as the
* producer for the {@link DomainResultAssembler} for this result as well
* as any {@link Initializer} instances needed
* <p/>
* Not the same as a result column in the JDBC ResultSet! This contract
* represents an individual domain-model-level query result. A QueryResult
* will usually consume multiple JDBC result columns.
* <p/>
* QueryResult is distinctly different from a {@link Fetch} and so modeled as
* completely separate hierarchy.
* Represents a result value in the domain query results. Acts as the producer for the
* {@link DomainResultAssembler} for this result as well as any {@link Initializer} instances needed
*
* Not the same as a result column in the JDBC ResultSet! This contract represents an individual
* domain-model-level query result. A DomainResult will usually consume multiple JDBC result columns.
*
* DomainResult is distinctly different from a {@link Fetch} and so modeled as completely separate hierarchy.
*
* @see BasicResultGraphNode
* @see DynamicInstantiationResult
* @see EntityResult
* @see CollectionResult
* @see CompositeResult
* @see Fetch
*
* @author Steve Ebersole

View File

@ -8,6 +8,7 @@ package org.hibernate.sql.results.graph;
import java.util.function.Consumer;
import org.hibernate.engine.FetchTiming;
import org.hibernate.query.NavigablePath;
/**
@ -15,16 +16,16 @@ import org.hibernate.query.NavigablePath;
* producer for the {@link DomainResultAssembler} for this result as well
* as any {@link Initializer} instances needed
*
* todo (6.0) : we have fetch -> fetch-parent at the initializer level. Do we also need fetch-parent -> fetch(es)?
* - depends how the parent state gets resolved for injection into the parent instance
*
* @see EntityFetch
* @see CollectionFetch
* @see CompositeFetch
*
* @author Steve Ebersole
*/
public interface Fetch extends DomainResultGraphNode {
/**
* Get the property path to this fetch
*
* @return The property path
*/
NavigablePath getNavigablePath();
/**
* Obtain the owner of this fetch. Ultimately used to identify
* the thing that "owns" this fetched navigable for the purpose of:
@ -33,6 +34,10 @@ public interface Fetch extends DomainResultGraphNode {
* * inject the fetched instance into the parent and potentially inject
* the parent reference into the fetched instance if it defines
* such injection (e.g. {@link org.hibernate.annotations.Parent})
*
* todo (6.0) : remove?
* - this is never used. not sure its useful, and it creates a bi-directional link between
* Fetch#getParent and FetchParent#getFetches
*/
FetchParent getFetchParent();
@ -42,11 +47,14 @@ public interface Fetch extends DomainResultGraphNode {
Fetchable getFetchedMapping();
/**
* Get the property path to this fetch
*
* @return The property path
* immediate or delayed?
*/
NavigablePath getNavigablePath();
FetchTiming getTiming();
/**
* Is the TableGroup associated with this Fetch defined?
*/
boolean hasTableGroup();
/**
* Is this fetch nullable? Meaning is it mapped as being optional?

View File

@ -51,6 +51,8 @@ public class BasicFetch<T> implements Fetch, BasicResultGraphNode<T> {
this.valuedMapping = valuedMapping;
this.fetchTiming = fetchTiming;
// todo (6.0) : account for lazy basic attributes (bytecode)
//noinspection unchecked
this.assembler = new BasicResultAssembler(
valuesArrayPosition,
@ -60,6 +62,16 @@ public class BasicFetch<T> implements Fetch, BasicResultGraphNode<T> {
}
@Override
public FetchTiming getTiming() {
return fetchTiming;
}
@Override
public boolean hasTableGroup() {
return fetchTiming == FetchTiming.IMMEDIATE;
}
@Override
public FetchParent getFetchParent() {
return fetchParent;

View File

@ -8,6 +8,7 @@ package org.hibernate.sql.results.graph.collection.internal;
import java.util.function.Consumer;
import org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.results.graph.AssemblerCreationState;
@ -43,6 +44,16 @@ public class DelayedCollectionFetch extends CollectionFetch {
);
}
@Override
public FetchTiming getTiming() {
return FetchTiming.DELAYED;
}
@Override
public boolean hasTableGroup() {
return false;
}
@Override
public JavaTypeDescriptor getResultJavaTypeDescriptor() {
return getFetchedMapping().getJavaTypeDescriptor();

View File

@ -12,6 +12,7 @@ import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.collection.spi.CollectionInitializerProducer;
import org.hibernate.collection.spi.CollectionSemantics;
import org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
@ -47,13 +48,13 @@ public class EagerCollectionFetch extends CollectionFetch implements FetchParent
public EagerCollectionFetch(
NavigablePath fetchedPath,
PluralAttributeMapping fetchedAttribute,
TableGroup collectionTableGroup,
boolean nullable,
FetchParent fetchParent,
DomainResultCreationState creationState) {
super( fetchedPath, fetchedAttribute, nullable, fetchParent );
final FromClauseAccess fromClauseAccess = creationState.getSqlAstCreationState().getFromClauseAccess();
final TableGroup collectionTableGroup = fromClauseAccess.getTableGroup( fetchedPath );
final NavigablePath parentPath = fetchedPath.getParent();
final TableGroup parentTableGroup = parentPath == null ? null : fromClauseAccess.findTableGroup( parentPath );
@ -135,6 +136,16 @@ public class EagerCollectionFetch extends CollectionFetch implements FetchParent
return new EagerCollectionAssembler( getFetchedMapping(), initializer );
}
@Override
public FetchTiming getTiming() {
return FetchTiming.IMMEDIATE;
}
@Override
public boolean hasTableGroup() {
return true;
}
@Override
public FetchableContainer getReferencedMappingContainer() {
return getFetchedMapping();

View File

@ -33,6 +33,7 @@ import org.hibernate.sql.results.graph.Initializer;
public class EmbeddableFetchImpl extends AbstractFetchParent implements EmbeddableResultGraphNode, Fetch {
private final FetchParent fetchParent;
private final FetchTiming fetchTiming;
private final boolean hasTableGroup;
private final boolean nullable;
public EmbeddableFetchImpl(
@ -40,12 +41,14 @@ public class EmbeddableFetchImpl extends AbstractFetchParent implements Embeddab
EmbeddableValuedFetchable embeddedPartDescriptor,
FetchParent fetchParent,
FetchTiming fetchTiming,
boolean hasTableGroup,
boolean nullable,
DomainResultCreationState creationState) {
super( embeddedPartDescriptor.getEmbeddableTypeDescriptor(), navigablePath );
this.fetchParent = fetchParent;
this.fetchTiming = fetchTiming;
this.hasTableGroup = hasTableGroup;
this.nullable = nullable;
creationState.getSqlAstCreationState().getFromClauseAccess().resolveTableGroup(
@ -72,6 +75,16 @@ public class EmbeddableFetchImpl extends AbstractFetchParent implements Embeddab
afterInitialize( creationState );
}
@Override
public FetchTiming getTiming() {
return fetchTiming;
}
@Override
public boolean hasTableGroup() {
return hasTableGroup;
}
@Override
public FetchParent getFetchParent() {
return fetchParent;

View File

@ -9,6 +9,7 @@ package org.hibernate.sql.results.graph.entity.internal;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.internal.SingularAssociationAttributeMapping;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.results.graph.AssemblerCreationState;
@ -43,6 +44,16 @@ public class EntityFetchDelayedImpl extends AbstractNonJoinedEntityFetch {
this.keyResult = keyResult;
}
@Override
public FetchTiming getTiming() {
return FetchTiming.DELAYED;
}
@Override
public boolean hasTableGroup() {
return false;
}
@Override
public boolean isNullable() {
return nullable;

View File

@ -9,6 +9,7 @@ package org.hibernate.sql.results.graph.entity.internal;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchTiming;
import org.hibernate.sql.results.graph.entity.AbstractNonLazyEntityFetch;
import org.hibernate.sql.results.graph.entity.EntityInitializer;
import org.hibernate.sql.results.graph.entity.EntityValuedFetchable;
@ -60,4 +61,14 @@ public class EntityFetchJoinedImpl extends AbstractNonLazyEntityFetch {
creationState
);
}
@Override
public FetchTiming getTiming() {
return FetchTiming.IMMEDIATE;
}
@Override
public boolean hasTableGroup() {
return true;
}
}

View File

@ -9,6 +9,7 @@ package org.hibernate.sql.results.graph.entity.internal;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.internal.SingularAssociationAttributeMapping;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.results.graph.AssemblerCreationState;
@ -41,6 +42,16 @@ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch {
this.result = result;
}
@Override
public FetchTiming getTiming() {
return FetchTiming.IMMEDIATE;
}
@Override
public boolean hasTableGroup() {
return false;
}
@Override
public boolean isNullable() {
return nullable;

View File

@ -31,16 +31,20 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
* @author Andrea Boriero
*/
public class BiDirectionalFetchImpl implements BiDirectionalFetch, Fetchable {
private final FetchTiming timing;
private final NavigablePath navigablePath;
private Fetchable fetchable;
private NavigablePath referencedNavigablePath;
private final Fetchable fetchable;
private final FetchParent fetchParent;
private final NavigablePath referencedNavigablePath;
public BiDirectionalFetchImpl(
FetchTiming timing,
NavigablePath navigablePath,
FetchParent fetchParent,
Fetchable fetchable,
NavigablePath referencedNavigablePath) {
this.timing = timing;
this.fetchParent = fetchParent;
this.navigablePath = navigablePath;
this.fetchable = fetchable;
@ -88,6 +92,16 @@ public class BiDirectionalFetchImpl implements BiDirectionalFetch, Fetchable {
);
}
@Override
public FetchTiming getTiming() {
return timing;
}
@Override
public boolean hasTableGroup() {
return true;
}
@Override
public String getFetchableName() {
return fetchable.getFetchableName();

View File

@ -7,6 +7,7 @@
package org.hibernate.sql.results.spi;
import org.hibernate.engine.FetchTiming;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.Fetchable;
@ -33,6 +34,7 @@ public class CircularFetchDetector {
final NavigablePath navigablePath = fetchParent.getNavigablePath();
if ( navigablePath.getParent().getParent() == null ) {
return new BiDirectionalFetchImpl(
FetchTiming.IMMEDIATE,
navigablePath,
fetchParent,
fetchable,
@ -41,6 +43,9 @@ public class CircularFetchDetector {
}
else {
return new BiDirectionalFetchImpl(
// todo (6.0) : needs to be the parent-parent's fetch timing
// atm we do not have the ability to get this
null,
navigablePath.append( fetchable.getFetchableName() ),
fetchParent,
fetchable,

View File

@ -115,5 +115,9 @@ public class MapOperationTests {
collectionDescriptor.getAttributeMapping().getOrderByFragment(),
notNullValue()
);
scope.inTransaction(
session -> session.createQuery( "from EntityOfMaps e join fetch e.componentByBasicOrdered" ).list()
);
}
}

View File

@ -65,7 +65,7 @@ public class PluralAttributeMappingTests {
final MappingMetamodel domainModel = scope.getSessionFactory().getDomainModel();
final EntityMappingType containerEntityDescriptor = domainModel.getEntityDescriptor( EntityOfSets.class );
assertThat( containerEntityDescriptor.getNumberOfAttributeMappings(), is( 9 ) );
assertThat( containerEntityDescriptor.getNumberOfAttributeMappings(), is( 10 ) );
final AttributeMapping setOfBasics = containerEntityDescriptor.findAttributeMapping( "setOfBasics" );
assertThat( setOfBasics, notNullValue() );
@ -73,6 +73,9 @@ public class PluralAttributeMappingTests {
final AttributeMapping sortedSetOfBasics = containerEntityDescriptor.findAttributeMapping( "sortedSetOfBasics" );
assertThat( sortedSetOfBasics, notNullValue() );
final AttributeMapping orderedSetOfBasics = containerEntityDescriptor.findAttributeMapping( "orderedSetOfBasics" );
assertThat( orderedSetOfBasics, notNullValue() );
final AttributeMapping setOfEnums = containerEntityDescriptor.findAttributeMapping( "setOfEnums" );
assertThat( setOfEnums, notNullValue() );

View File

@ -9,6 +9,7 @@ package org.hibernate.orm.test.metamodel.mapping.collections;
import java.util.Iterator;
import org.hibernate.Hibernate;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.testing.hamcrest.CollectionMatchers;
import org.hibernate.testing.hamcrest.InitializationCheckMatcher;
@ -174,4 +175,21 @@ public class SetOperationTests {
}
);
}
@Test
public void testOrderedSet(SessionFactoryScope scope) {
// atm we can only check the fragment translation
final CollectionPersister collectionDescriptor = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel()
.findCollectionDescriptor( EntityOfSets.class.getName() + ".orderedSetOfBasics" );
assertThat(
collectionDescriptor.getAttributeMapping().getOrderByFragment(),
notNullValue()
);
scope.inTransaction(
session -> session.createQuery( "from EntityOfSets e join fetch e.orderedSetOfBasics" ).list()
);
}
}

View File

@ -10,14 +10,25 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SortOrder;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.persister.collection.BasicCollectionPersister;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAliasBaseImpl;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Assert;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author Steve Ebersole
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
@ -33,10 +44,10 @@ public class ElementCollectionSortingTest extends BaseCoreFunctionalTestCase {
Session session = openSession();
session.beginTransaction();
session.createQuery( "from Person p join fetch p.nickNamesAscendingNaturalSort" ).list();
session.createQuery( "from Person p join fetch p.nickNamesDescendingNaturalSort" ).list();
session.createQuery( "from Person p join fetch p.addressesAscendingNaturalSort" ).list();
// session.createQuery( "from Person p join fetch p.nickNamesAscendingNaturalSort" ).list();
// session.createQuery( "from Person p join fetch p.nickNamesDescendingNaturalSort" ).list();
//
// session.createQuery( "from Person p join fetch p.addressesAscendingNaturalSort" ).list();
session.createQuery( "from Person p join fetch p.addressesDescendingNaturalSort" ).list();
session.createQuery( "from Person p join fetch p.addressesCityAscendingSort" ).list();
session.createQuery( "from Person p join fetch p.addressesCityDescendingSort" ).list();
@ -91,20 +102,20 @@ public class ElementCollectionSortingTest extends BaseCoreFunctionalTestCase {
checkPersonNickNames( steveNamesAsc, steveNamesDesc, result.get( 1 ) );
// Metadata verification.
checkSQLOrderBy( session, Person.class.getName(), "nickNamesAscendingNaturalSort", "asc" );
checkSQLOrderBy( session, Person.class.getName(), "nickNamesDescendingNaturalSort", "desc" );
// checkSQLOrderBy( session, Person.class.getName(), "nickNamesAscendingNaturalSort", "asc" );
// checkSQLOrderBy( session, Person.class.getName(), "nickNamesDescendingNaturalSort", "desc" );
session.getTransaction().rollback();
session.close();
}
private void checkSQLOrderBy(Session session, String entityName, String propertyName, String order) {
String roleName = entityName + "." + propertyName;
String alias = "alias1";
BasicCollectionPersister collectionPersister = (BasicCollectionPersister) session.getSessionFactory().getCollectionMetadata( roleName );
Assert.assertTrue( collectionPersister.hasOrdering() );
Assert.assertEquals( alias + "." + propertyName + " " + order, collectionPersister.getSQLOrderByString( alias ) );
}
// private void checkSQLOrderBy(Session session, String entityName, String propertyName, String order) {
// String roleName = entityName + "." + propertyName;
// String alias = "alias1";
// BasicCollectionPersister collectionPersister = (BasicCollectionPersister) session.getSessionFactory().getCollectionMetadata( roleName );
// Assert.assertTrue( collectionPersister.hasOrdering() );
// Assert.assertEquals( alias + "." + propertyName + " " + order, collectionPersister.getSQLOrderByString( alias ) );
// }
private void checkPersonNickNames(List<String> expectedAscending, List<String> expectedDescending, Person person) {
// Comparing lists to verify ordering.

View File

@ -19,6 +19,7 @@ import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import org.hibernate.annotations.LazyCollection;
@ -37,6 +38,7 @@ public class EntityOfSets {
private Set<String> setOfBasics;
private SortedSet<String> sortedSetOfBasics;
private Set<String> orderedSetOfBasics;
private Set<EnumValue> setOfEnums;
private Set<EnumValue> setOfConvertedEnums;
@ -47,6 +49,7 @@ public class EntityOfSets {
private Set<SimpleEntity> setOfOneToMany;
private Set<SimpleEntity> setOfManyToMany;
public EntityOfSets() {
}
@ -94,6 +97,28 @@ public class EntityOfSets {
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// orderedSetOfBasics
@ElementCollection()
@CollectionTable( name = "EntityOfSet_orderedSetOfBasics")
@OrderBy( "" )
public Set<String> getOrderedSetOfBasics() {
return orderedSetOfBasics;
}
public void setOrderedSetOfBasics(Set<String> orderedSetOfBasics) {
this.orderedSetOfBasics = orderedSetOfBasics;
}
public void addOrderedBasic(String value) {
if ( orderedSetOfBasics == null ) {
orderedSetOfBasics = new TreeSet<>();
}
orderedSetOfBasics.add( value );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// sortedSetOfBasics