diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/SingletonStack.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/SingletonStack.java new file mode 100644 index 0000000000..3e59bc5a37 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/SingletonStack.java @@ -0,0 +1,65 @@ +/* + * 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.internal.util.collections; + +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * @author Steve Ebersole + */ +public class SingletonStack implements Stack { + private final T instance; + + public SingletonStack(T instance) { + this.instance = instance; + } + + @Override + public void push(T newCurrent) { + throw new UnsupportedOperationException( "Cannot push to a singleton Stack" ); + } + + @Override + public T pop() { + throw new UnsupportedOperationException( "Cannot pop from a singleton Stack" ); + } + + @Override + public T getCurrent() { + return instance; + } + + @Override + public T getPrevious() { + return null; + } + + @Override + public int depth() { + return 1; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public void clear() { + } + + @Override + public void visitCurrentFirst(Consumer action) { + action.accept( instance ); + } + + @Override + public X findCurrentFirst(Function action) { + return action.apply( instance ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/Stack.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/Stack.java new file mode 100644 index 0000000000..240d48ab22 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/Stack.java @@ -0,0 +1,65 @@ +/* + * 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.internal.util.collections; + +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Stack implementation exposing useful methods for Hibernate needs. + * + * @param The type of things stored in the stack + * + * @author Steve Ebersole + */ +public interface Stack { + /** + * Push the new element on the top of the stack + */ + void push(T newCurrent); + + /** + * Pop (remove and return) the current element off the stack + */ + T pop(); + + /** + * The element currently at the top of the stack + */ + T getCurrent(); + + /** + * The element previously at the top of the stack before the current one + */ + T getPrevious(); + + /** + * How many elements are currently on the stack? + */ + int depth(); + + /** + * Are there no elements currently in the stack? + */ + boolean isEmpty(); + + /** + * Remmove all elements from the stack + */ + void clear(); + + /** + * Visit all elements in the stack, starting with the current and working back + */ + void visitCurrentFirst(Consumer action); + + /** + * Find an element on the stack and return a value. The first non-null element + * returned from `action` stops the iteration and is returned from here + */ + X findCurrentFirst(Function action); +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/StandardStack.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/StandardStack.java new file mode 100644 index 0000000000..b2025c7c36 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/StandardStack.java @@ -0,0 +1,83 @@ +/* + * 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.internal.util.collections; + +import java.util.LinkedList; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * A general-purpose stack impl. + * + * @param The type of things stored in the stack + * + * @author Steve Ebersole + */ +public class StandardStack implements Stack { + private LinkedList internalStack = new LinkedList<>(); + + public StandardStack() { + } + + public StandardStack(T initial) { + internalStack.add( initial ); + } + + @Override + public void push(T newCurrent) { + internalStack.addFirst( newCurrent ); + } + + @Override + public T pop() { + return internalStack.removeFirst(); + } + + @Override + public T getCurrent() { + return internalStack.peek(); + } + + @Override + public T getPrevious() { + if ( internalStack.size() < 2 ) { + return null; + } + return internalStack.get( internalStack.size() - 2 ); + } + + @Override + public int depth() { + return internalStack.size(); + } + + @Override + public boolean isEmpty() { + return internalStack.isEmpty(); + } + + @Override + public void clear() { + internalStack.clear(); + } + + @Override + public void visitCurrentFirst(Consumer action) { + internalStack.forEach( action ); + } + + @Override + public X findCurrentFirst(Function function) { + for ( T t : internalStack ) { + final X result = function.apply( t ); + if ( result != null ) { + return result; + } + } + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/AbstractManipulationCriteriaQuery.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/AbstractManipulationCriteriaQuery.java index 3f7b79fcda..7a07d93edd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/AbstractManipulationCriteriaQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/AbstractManipulationCriteriaQuery.java @@ -25,6 +25,7 @@ import org.hibernate.query.criteria.internal.compile.InterpretedParameterMetadat import org.hibernate.query.criteria.internal.compile.RenderingContext; import org.hibernate.query.criteria.internal.path.RootImpl; import org.hibernate.query.spi.QueryImplementor; +import org.hibernate.sql.ast.Clause; /** * Base class for commonality between {@link javax.persistence.criteria.CriteriaUpdate} and @@ -155,9 +156,17 @@ public abstract class AbstractManipulationCriteriaQuery implements Compilable } protected void renderRestrictions(StringBuilder jpaql, RenderingContext renderingContext) { - if ( getRestriction() != null) { + if ( getRestriction() == null ) { + return; + } + + renderingContext.getClauseStack().push( Clause.WHERE ); + try { jpaql.append( " where " ) .append( ( (Renderable) getRestriction() ).render( renderingContext ) ); } + finally { + renderingContext.getClauseStack().pop(); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaQueryImpl.java index c975049191..d4f6bfd791 100755 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaQueryImpl.java @@ -33,6 +33,7 @@ import org.hibernate.query.criteria.internal.compile.ImplicitParameterBinding; import org.hibernate.query.criteria.internal.compile.InterpretedParameterMetadata; import org.hibernate.query.criteria.internal.compile.RenderingContext; import org.hibernate.query.spi.QueryImplementor; +import org.hibernate.sql.ast.Clause; import org.hibernate.type.Type; import org.jboss.logging.Logger; @@ -291,16 +292,7 @@ public class CriteriaQueryImpl extends AbstractNode implements CriteriaQuery< queryStructure.render( jpaqlBuffer, renderingContext ); - if ( ! getOrderList().isEmpty() ) { - jpaqlBuffer.append( " order by " ); - String sep = ""; - for ( Order orderSpec : getOrderList() ) { - jpaqlBuffer.append( sep ) - .append( ( ( Renderable ) orderSpec.getExpression() ).render( renderingContext ) ) - .append( orderSpec.isAscending() ? " asc" : " desc" ); - sep = ", "; - } - } + renderOrderByClause( renderingContext, jpaqlBuffer ); final String jpaqlString = jpaqlBuffer.toString(); @@ -385,4 +377,25 @@ public class CriteriaQueryImpl extends AbstractNode implements CriteriaQuery< } }; } + + protected void renderOrderByClause(RenderingContext renderingContext, StringBuilder jpaqlBuffer) { + if ( getOrderList().isEmpty() ) { + return; + } + + renderingContext.getClauseStack().push( Clause.ORDER ); + try { + jpaqlBuffer.append( " order by " ); + String sep = ""; + for ( Order orderSpec : getOrderList() ) { + jpaqlBuffer.append( sep ) + .append( ( (Renderable) orderSpec.getExpression() ).render( renderingContext ) ) + .append( orderSpec.isAscending() ? " asc" : " desc" ); + sep = ", "; + } + } + finally { + renderingContext.getClauseStack().pop(); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaSubqueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaSubqueryImpl.java index 6d544c56d3..91c768a880 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaSubqueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaSubqueryImpl.java @@ -27,6 +27,7 @@ import org.hibernate.query.criteria.internal.compile.RenderingContext; import org.hibernate.query.criteria.internal.expression.DelegatedExpressionImpl; import org.hibernate.query.criteria.internal.expression.ExpressionImpl; import org.hibernate.query.criteria.internal.path.RootImpl; +import org.hibernate.sql.ast.Clause; /** * The Hibernate implementation of the JPA {@link Subquery} contract. Mostlty a set of delegation to its internal @@ -257,14 +258,13 @@ public class CriteriaSubqueryImpl extends ExpressionImpl implements Subque @Override public String render(RenderingContext renderingContext) { + if ( renderingContext.getClauseStack().getCurrent() == Clause.SELECT ) { + throw new IllegalStateException( "Subquery cannot occur in select clause" ); + } + StringBuilder subqueryBuffer = new StringBuilder( "(" ); queryStructure.render( subqueryBuffer, renderingContext ); subqueryBuffer.append( ')' ); return subqueryBuffer.toString(); } - - @Override - public String renderProjection(RenderingContext renderingContext) { - throw new IllegalStateException( "Subquery cannot occur in select clause" ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaUpdateImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaUpdateImpl.java index 3d513842f2..3e4b9a0afd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaUpdateImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/CriteriaUpdateImpl.java @@ -16,6 +16,7 @@ import javax.persistence.metamodel.SingularAttribute; import org.hibernate.query.criteria.internal.compile.RenderingContext; import org.hibernate.query.criteria.internal.path.SingularAttributePath; +import org.hibernate.sql.ast.Clause; /** * Hibernate implementation of the JPA 2.1 {@link CriteriaUpdate} contract. @@ -122,16 +123,23 @@ public class CriteriaUpdateImpl extends AbstractManipulationCriteriaQuery } private void renderAssignments(StringBuilder jpaql, RenderingContext renderingContext) { - jpaql.append( " set " ); - boolean first = true; - for ( Assignment assignment : assignments ) { - if ( ! first ) { - jpaql.append( ", " ); + renderingContext.getClauseStack().push( Clause.UPDATE ); + + try { + jpaql.append( " set " ); + boolean first = true; + for ( Assignment assignment : assignments ) { + if ( !first ) { + jpaql.append( ", " ); + } + jpaql.append( assignment.attributePath.render( renderingContext ) ) + .append( " = " ) + .append( assignment.value.render( renderingContext ) ); + first = false; } - jpaql.append( assignment.attributePath.render( renderingContext ) ) - .append( " = " ) - .append( assignment.value.render( renderingContext ) ); - first = false; + } + finally { + renderingContext.getClauseStack().pop(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/QueryStructure.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/QueryStructure.java index 736c6c1ea1..2f0be9dfe2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/QueryStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/QueryStructure.java @@ -31,6 +31,7 @@ import javax.persistence.metamodel.EntityType; import org.hibernate.query.criteria.internal.compile.RenderingContext; import org.hibernate.query.criteria.internal.path.RootImpl; import org.hibernate.query.criteria.internal.path.RootImpl.TreatedRoot; +import org.hibernate.sql.ast.Clause; /** * Models basic query structure. Used as a delegate in implementing both @@ -230,37 +231,34 @@ public class QueryStructure implements Serializable { @SuppressWarnings({ "unchecked" }) public void render(StringBuilder jpaqlQuery, RenderingContext renderingContext) { - jpaqlQuery.append( "select " ); - if ( isDistinct() ) { - jpaqlQuery.append( "distinct " ); - } - if ( getSelection() == null ) { - jpaqlQuery.append( locateImplicitSelection().renderProjection( renderingContext ) ); - } - else { - jpaqlQuery.append( ( (Renderable) getSelection() ).renderProjection( renderingContext ) ); - } + renderSelectClause( jpaqlQuery, renderingContext ); renderFromClause( jpaqlQuery, renderingContext ); - if ( getRestriction() != null) { - jpaqlQuery.append( " where " ) - .append( ( (Renderable) getRestriction() ).render( renderingContext ) ); + renderWhereClause( jpaqlQuery, renderingContext ); + + renderGroupByClause( jpaqlQuery, renderingContext ); + } + + protected void renderSelectClause(StringBuilder jpaqlQuery, RenderingContext renderingContext) { + renderingContext.getClauseStack().push( Clause.SELECT ); + + try { + jpaqlQuery.append( "select " ); + + if ( isDistinct() ) { + jpaqlQuery.append( "distinct " ); + } + + if ( getSelection() == null ) { + jpaqlQuery.append( locateImplicitSelection().render( renderingContext ) ); + } + else { + jpaqlQuery.append( ( (Renderable) getSelection() ).render( renderingContext ) ); + } } - - if ( ! getGroupings().isEmpty() ) { - jpaqlQuery.append( " group by " ); - String sep = ""; - for ( Expression grouping : getGroupings() ) { - jpaqlQuery.append( sep ) - .append( ( (Renderable) grouping ).renderGroupBy( renderingContext ) ); - sep = ", "; - } - - if ( getHaving() != null ) { - jpaqlQuery.append( " having " ) - .append( ( (Renderable) getHaving() ).render( renderingContext ) ); - } + finally { + renderingContext.getClauseStack().pop(); } } @@ -290,48 +288,106 @@ public class QueryStructure implements Serializable { @SuppressWarnings({ "unchecked" }) private void renderFromClause(StringBuilder jpaqlQuery, RenderingContext renderingContext) { - jpaqlQuery.append( " from " ); - String sep = ""; - for ( Root root : getRoots() ) { - ( (FromImplementor) root ).prepareAlias( renderingContext ); - jpaqlQuery.append( sep ); - jpaqlQuery.append( ( (FromImplementor) root ).renderTableExpression( renderingContext ) ); - sep = ", "; - } + renderingContext.getClauseStack().push( Clause.FROM ); - for ( Root root : getRoots() ) { - renderJoins( jpaqlQuery, renderingContext, root.getJoins() ); - if (root instanceof RootImpl) { - Set treats = ((RootImpl)root).getTreats(); - for ( TreatedRoot treat : treats ) { - renderJoins( jpaqlQuery, renderingContext, treat.getJoins() ); - } + try { + jpaqlQuery.append( " from " ); + String sep = ""; + for ( Root root : getRoots() ) { + ( (FromImplementor) root ).prepareAlias( renderingContext ); + jpaqlQuery.append( sep ); + sep = ", "; + jpaqlQuery.append( ( (FromImplementor) root ).renderTableExpression( renderingContext ) ); } - renderFetches( jpaqlQuery, renderingContext, root.getFetches() ); - } - if ( isSubQuery ) { - if ( correlationRoots != null ) { - for ( FromImplementor correlationRoot : correlationRoots ) { - final FromImplementor correlationParent = correlationRoot.getCorrelationParent(); - correlationParent.prepareAlias( renderingContext ); - final String correlationRootAlias = correlationParent.getAlias(); - for ( Join correlationJoin : correlationRoot.getJoins() ) { - final JoinImplementor correlationJoinImpl = (JoinImplementor) correlationJoin; - // IMPL NOTE: reuse the sep from above! - jpaqlQuery.append( sep ); - correlationJoinImpl.prepareAlias( renderingContext ); - jpaqlQuery.append( correlationRootAlias ) - .append( '.' ) - .append( correlationJoinImpl.getAttribute().getName() ) - .append( " as " ) - .append( correlationJoinImpl.getAlias() ); - sep = ", "; - renderJoins( jpaqlQuery, renderingContext, correlationJoinImpl.getJoins() ); + for ( Root root : getRoots() ) { + renderJoins( jpaqlQuery, renderingContext, root.getJoins() ); + if ( root instanceof RootImpl ) { + Set treats = ( (RootImpl) root ).getTreats(); + for ( TreatedRoot treat : treats ) { + renderJoins( jpaqlQuery, renderingContext, treat.getJoins() ); + } + } + renderFetches( jpaqlQuery, renderingContext, root.getFetches() ); + } + + if ( isSubQuery ) { + if ( correlationRoots != null ) { + for ( FromImplementor correlationRoot : correlationRoots ) { + final FromImplementor correlationParent = correlationRoot.getCorrelationParent(); + correlationParent.prepareAlias( renderingContext ); + final String correlationRootAlias = correlationParent.getAlias(); + for ( Join correlationJoin : correlationRoot.getJoins() ) { + final JoinImplementor correlationJoinImpl = (JoinImplementor) correlationJoin; + // IMPL NOTE: reuse the sep from above! + jpaqlQuery.append( sep ); + correlationJoinImpl.prepareAlias( renderingContext ); + jpaqlQuery.append( correlationRootAlias ) + .append( '.' ) + .append( correlationJoinImpl.getAttribute().getName() ) + .append( " as " ) + .append( correlationJoinImpl.getAlias() ); + sep = ", "; + renderJoins( jpaqlQuery, renderingContext, correlationJoinImpl.getJoins() ); + } } } } } + finally { + renderingContext.getClauseStack().pop(); + } + } + + protected void renderWhereClause(StringBuilder jpaqlQuery, RenderingContext renderingContext) { + if ( getRestriction() == null ) { + return; + } + + renderingContext.getClauseStack().push( Clause.WHERE ); + try { + jpaqlQuery.append( " where " ) + .append( ( (Renderable) getRestriction() ).render( renderingContext ) ); + } + finally { + renderingContext.getClauseStack().pop(); + } + } + + protected void renderGroupByClause(StringBuilder jpaqlQuery, RenderingContext renderingContext) { + if ( getGroupings().isEmpty() ) { + return; + } + + renderingContext.getClauseStack().push( Clause.GROUP ); + try { + jpaqlQuery.append( " group by " ); + String sep = ""; + for ( Expression grouping : getGroupings() ) { + jpaqlQuery.append( sep ) + .append( ( (Renderable) grouping ).render( renderingContext ) ); + sep = ", "; + } + + renderHavingClause( jpaqlQuery, renderingContext ); + } + finally { + renderingContext.getClauseStack().pop(); + } + } + + private void renderHavingClause(StringBuilder jpaqlQuery, RenderingContext renderingContext) { + if ( getHaving() == null ) { + return; + } + + renderingContext.getClauseStack().push( Clause.HAVING ); + try { + jpaqlQuery.append( " having " ).append( ( (Renderable) getHaving() ).render( renderingContext ) ); + } + finally { + renderingContext.getClauseStack().pop(); + } } @SuppressWarnings({ "unchecked" }) diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/Renderable.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/Renderable.java index 6a2570db9f..5eae886066 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/Renderable.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/Renderable.java @@ -10,38 +10,15 @@ package org.hibernate.query.criteria.internal; import org.hibernate.query.criteria.internal.compile.RenderingContext; /** - * TODO : javadoc + * Contract for nodes in the JPA Criteria tree that can be rendered + * as part of criteria "compilation" * * @author Steve Ebersole */ public interface Renderable { /** - * Render clause - * - * @param renderingContext context - * @return rendered expression + * Perform the rendering, returning the rendition */ String render(RenderingContext renderingContext); - - /** - * Render SELECT clause - * - * @param renderingContext context - * @return rendered expression - */ - default String renderProjection(RenderingContext renderingContext) { - return render( renderingContext ); - } - - /** - * Render GROUP BY clause - * - * @param renderingContext context - * - * @return rendered expression - */ - default String renderGroupBy(RenderingContext renderingContext) { - return render( renderingContext ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java index 74ea10f606..ebab160a14 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/CriteriaCompiler.java @@ -20,8 +20,12 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.collections.Stack; +import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.query.criteria.LiteralHandlingMode; +import org.hibernate.query.criteria.internal.expression.function.FunctionExpression; import org.hibernate.query.spi.QueryImplementor; +import org.hibernate.sql.ast.Clause; import org.hibernate.type.Type; /** @@ -63,6 +67,9 @@ public class CriteriaCompiler implements Serializable { private int aliasCount; private int explicitParameterCount; + private final Stack clauseStack = new StandardStack<>(); + private final Stack functionContextStack = new StandardStack<>(); + public String generateAlias() { return "generatedAlias" + aliasCount++; } @@ -71,6 +78,16 @@ public class CriteriaCompiler implements Serializable { return "param" + explicitParameterCount++; } + @Override + public Stack getClauseStack() { + return clauseStack; + } + + @Override + public Stack getFunctionStack() { + return functionContextStack; + } + @Override @SuppressWarnings("unchecked") public ExplicitParameterInfo registerExplicitParameter(ParameterExpression criteriaQueryParameter) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/RenderingContext.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/RenderingContext.java index a251f2ad19..68590b76ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/RenderingContext.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/compile/RenderingContext.java @@ -9,7 +9,10 @@ package org.hibernate.query.criteria.internal.compile; import javax.persistence.criteria.ParameterExpression; import org.hibernate.dialect.Dialect; +import org.hibernate.internal.util.collections.Stack; import org.hibernate.query.criteria.LiteralHandlingMode; +import org.hibernate.query.criteria.internal.expression.function.FunctionExpression; +import org.hibernate.sql.ast.Clause; /** * Used to provide a context and services to the rendering. @@ -67,4 +70,8 @@ public interface RenderingContext { default LiteralHandlingMode getCriteriaLiteralHandlingMode() { return LiteralHandlingMode.AUTO; } + + Stack getClauseStack(); + + Stack getFunctionStack(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/CompoundSelectionImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/CompoundSelectionImpl.java index a6a8e469b5..27bc0f44ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/CompoundSelectionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/CompoundSelectionImpl.java @@ -77,15 +77,17 @@ public class CompoundSelectionImpl if ( isConstructor ) { buff.append( "new " ).append( getJavaType().getName() ).append( '(' ); } + String sep = ""; for ( Selection selection : selectionItems ) { - buff.append( sep ) - .append( ( (Renderable) selection ).renderProjection( renderingContext ) ); + buff.append( sep ).append( ( (Renderable) selection ).render( renderingContext ) ); sep = ", "; } + if ( isConstructor ) { buff.append( ')' ); } + return buff.toString(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java index af42b2f009..06e7ca33b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/LiteralExpression.java @@ -47,37 +47,54 @@ public class LiteralExpression extends ExpressionImpl implements Serializa @SuppressWarnings({ "unchecked" }) public String render(RenderingContext renderingContext) { + switch ( renderingContext.getClauseStack().getCurrent() ) { + case SELECT: { + return renderProjection(); + } + case GROUP: { + // technically a literal in the group-by clause + // would be a reference to the position of a selection + // + // but this is what the code used to do... + return renderProjection(); + } + default: { + return normalRender( renderingContext ); + } + } + } + @SuppressWarnings("unchecked") + private String normalRender(RenderingContext renderingContext) { LiteralHandlingMode literalHandlingMode = renderingContext.getCriteriaLiteralHandlingMode(); switch ( literalHandlingMode ) { - case AUTO: + case AUTO: { if ( ValueHandlerFactory.isNumeric( literal ) ) { return ValueHandlerFactory.determineAppropriateHandler( (Class) literal.getClass() ).render( literal ); } else { return bindLiteral( renderingContext ); } - case BIND: + } + case BIND: { return bindLiteral( renderingContext ); - case INLINE: + } + case INLINE: { Object literalValue = literal; if ( String.class.equals( literal.getClass() ) ) { literalValue = renderingContext.getDialect().inlineLiteral( (String) literal ); } + return ValueHandlerFactory.determineAppropriateHandler( (Class) literal.getClass() ).render( literalValue ); - default: + } + default: { throw new IllegalArgumentException( "Unexpected LiteralHandlingMode: " + literalHandlingMode ); + } } } - private String bindLiteral(RenderingContext renderingContext) { - final String parameterName = renderingContext.registerLiteralParameterBinding( getLiteral(), getJavaType() ); - return ':' + parameterName; - } - - @SuppressWarnings({ "unchecked" }) - public String renderProjection(RenderingContext renderingContext) { + private String renderProjection() { // some drivers/servers do not like parameters in the select clause final ValueHandlerFactory.ValueHandler handler = ValueHandlerFactory.determineAppropriateHandler( literal.getClass() ); @@ -89,9 +106,9 @@ public class LiteralExpression extends ExpressionImpl implements Serializa } } - @Override - public String renderGroupBy(RenderingContext renderingContext) { - return renderProjection( renderingContext ); + private String bindLiteral(RenderingContext renderingContext) { + final String parameterName = renderingContext.registerLiteralParameterBinding( getLiteral(), getJavaType() ); + return ':' + parameterName; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/MapEntryExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/MapEntryExpression.java index b3424c2558..807621c3ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/MapEntryExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/MapEntryExpression.java @@ -16,6 +16,7 @@ import org.hibernate.query.criteria.internal.ParameterRegistry; import org.hibernate.query.criteria.internal.PathImplementor; import org.hibernate.query.criteria.internal.Renderable; import org.hibernate.query.criteria.internal.compile.RenderingContext; +import org.hibernate.sql.ast.Clause; /** * TODO : javadoc @@ -48,17 +49,17 @@ public class MapEntryExpression } public String render(RenderingContext renderingContext) { + if ( renderingContext.getClauseStack().getCurrent() == Clause.SELECT ) { + return "entry(" + path( renderingContext ) + ")"; + } + // don't think this is valid outside of select clause... throw new IllegalStateException( "illegal reference to map entry outside of select clause." ); } - public String renderProjection(RenderingContext renderingContext) { - return "entry(" + path( renderingContext ) + ")"; - } - private String path(RenderingContext renderingContext) { return origin.getPathIdentifier() + '.' - + ( (Renderable) getAttribute() ).renderProjection( renderingContext ); + + ( (Renderable) getAttribute() ).render( renderingContext ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/NullLiteralExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/NullLiteralExpression.java index 89c00c8d06..b6a1b1f4a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/NullLiteralExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/NullLiteralExpression.java @@ -12,6 +12,7 @@ import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; import org.hibernate.query.criteria.internal.ParameterRegistry; import org.hibernate.query.criteria.internal.compile.RenderingContext; import org.hibernate.query.criteria.internal.expression.function.CastFunction; +import org.hibernate.sql.ast.Clause; /** * Represents a NULLliteral expression. @@ -28,10 +29,13 @@ public class NullLiteralExpression extends ExpressionImpl implements Seria } public String render(RenderingContext renderingContext) { + if ( renderingContext.getClauseStack().getCurrent() == Clause.SELECT ) { + // in the select clause render the ``null` using a cast so the db analyzer/optimizer + // understands the type + return CastFunction.CAST_NAME + "( null as " + renderingContext.getCastType( getJavaType() ) + ')'; + } + + // otherwise, just render `null` return "null"; } - - public String renderProjection(RenderingContext renderingContext) { - return CastFunction.CAST_NAME + "( null as " + renderingContext.getCastType( getJavaType() ) + ')'; - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpression.java index 6ce205463c..f3e2357deb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SearchedCaseExpression.java @@ -106,41 +106,18 @@ public class SearchedCaseExpression } public String render(RenderingContext renderingContext) { - return render( - renderingContext, - (Renderable expression, RenderingContext context) -> expression.render( context ) - ); - } - - public String renderProjection(RenderingContext renderingContext) { - return render( - renderingContext, - (Renderable expression, RenderingContext context) -> expression.renderProjection( context ) - ); - } - - @Override - public String renderGroupBy(RenderingContext renderingContext) { - return render( - renderingContext, - (Renderable expression, RenderingContext context) -> expression.renderGroupBy( context ) - ); - } - - private String render( - RenderingContext renderingContext, - BiFunction formatter) { StringBuilder caseStatement = new StringBuilder( "case" ); for ( WhenClause whenClause : getWhenClauses() ) { caseStatement.append( " when " ) - .append( formatter.apply( (Renderable) whenClause.getCondition(), renderingContext ) ) + .append( ( (Renderable) whenClause.getCondition() ).render( renderingContext ) ) .append( " then " ) - .append( formatter.apply( ((Renderable) whenClause.getResult()), renderingContext ) ); + .append( ( (Renderable) whenClause.getResult() ).render( renderingContext ) ); } + caseStatement.append( " else " ) - .append( formatter.apply( (Renderable) getOtherwiseResult(), renderingContext ) ) + .append( ( (Renderable) getOtherwiseResult() ).render( renderingContext ) ) .append( " end" ); + return caseStatement.toString(); } - } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SimpleCaseExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SimpleCaseExpression.java index 8074b5fca2..d75a8bccc9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SimpleCaseExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/SimpleCaseExpression.java @@ -9,7 +9,6 @@ package org.hibernate.query.criteria.internal.expression; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -import java.util.function.BiFunction; import javax.persistence.criteria.CriteriaBuilder.SimpleCase; import javax.persistence.criteria.Expression; @@ -118,44 +117,21 @@ public class SimpleCaseExpression @Override public String render(RenderingContext renderingContext) { - return render( - renderingContext, - (Renderable expression, RenderingContext context) -> expression.render( context ) - ); - } - - @Override - public String renderProjection(RenderingContext renderingContext) { - return render( - renderingContext, - (Renderable expression, RenderingContext context) -> expression.renderProjection( context ) - ); - } - - @Override - public String renderGroupBy(RenderingContext renderingContext) { - return render( - renderingContext, - (Renderable expression, RenderingContext context) -> expression.renderGroupBy( context ) - ); - } - - private String render( - RenderingContext renderingContext, - BiFunction formatter) { StringBuilder caseExpr = new StringBuilder(); caseExpr.append( "case " ) - .append( formatter.apply( (Renderable) getExpression(), renderingContext ) ); + .append( ( (Renderable) getExpression() ).render( renderingContext ) ); + for ( WhenClause whenClause : getWhenClauses() ) { caseExpr.append( " when " ) - .append( formatter.apply( whenClause.getCondition(), renderingContext ) ) + .append( whenClause.getCondition().render( renderingContext ) ) .append( " then " ) - .append( formatter.apply( (Renderable) whenClause.getResult(), renderingContext ) ); + .append( ( (Renderable) whenClause.getResult() ).render( renderingContext ) ); } + caseExpr.append( " else " ) - .append( formatter.apply( (Renderable) getOtherwiseResult(), renderingContext ) ) + .append( ( (Renderable) getOtherwiseResult() ).render( renderingContext ) ) .append( " end" ); + return caseExpr.toString(); } - } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/CastFunction.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/CastFunction.java index 556877dba2..895fd62577 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/CastFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/CastFunction.java @@ -7,6 +7,7 @@ package org.hibernate.query.criteria.internal.expression.function; import java.io.Serializable; +import java.util.Locale; import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; import org.hibernate.query.criteria.internal.ParameterRegistry; @@ -47,10 +48,17 @@ public class CastFunction @Override public String render(RenderingContext renderingContext) { - return CAST_NAME + '(' + - castSource.render( renderingContext ) + - " as " + - renderingContext.getCastType( getJavaType() ) + - ')'; + renderingContext.getFunctionStack().push( this ); + try { + return String.format( + Locale.ROOT, + "cast(%s as %s)", + castSource.render( renderingContext ), + renderingContext.getCastType( getJavaType() ) + ); + } + finally { + renderingContext.getFunctionStack().pop(); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/LocateFunction.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/LocateFunction.java index 5cd0c89e49..5c14894d39 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/LocateFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/LocateFunction.java @@ -86,16 +86,26 @@ public class LocateFunction @Override public String render(RenderingContext renderingContext) { - StringBuilder buffer = new StringBuilder(); - buffer.append( "locate(" ) - .append( ( (Renderable) getPattern() ).render( renderingContext ) ) - .append( ',' ) - .append( ( (Renderable) getString() ).render( renderingContext ) ); - if ( getStart() != null ) { - buffer.append( ',' ) - .append( ( (Renderable) getStart() ).render( renderingContext ) ); + renderingContext.getFunctionStack().push( this ); + + try { + final StringBuilder buffer = new StringBuilder(); + buffer.append( "locate(" ) + .append( ( (Renderable) getPattern() ).render( renderingContext ) ) + .append( ',' ) + .append( ( (Renderable) getString() ).render( renderingContext ) ); + + if ( getStart() != null ) { + buffer.append( ',' ) + .append( ( (Renderable) getStart() ).render( renderingContext ) ); + } + + buffer.append( ')' ); + + return buffer.toString(); + } + finally { + renderingContext.getFunctionStack().pop(); } - buffer.append( ')' ); - return buffer.toString(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/ParameterizedFunctionExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/ParameterizedFunctionExpression.java index aa7327325c..651e127134 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/ParameterizedFunctionExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/ParameterizedFunctionExpression.java @@ -93,19 +93,26 @@ public class ParameterizedFunctionExpression @Override public String render(RenderingContext renderingContext) { - StringBuilder buffer = new StringBuilder(); - if ( isStandardJpaFunction() ) { - buffer.append( getFunctionName() ) - .append( "(" ); + renderingContext.getFunctionStack().push( this ); + + try { + final StringBuilder buffer = new StringBuilder(); + if ( isStandardJpaFunction() ) { + buffer.append( getFunctionName() ).append( "(" ); + } + else { + buffer.append( "function('" ) + .append( getFunctionName() ) + .append( "', " ); + } + + renderArguments( buffer, renderingContext ); + + return buffer.append( ')' ).toString(); } - else { - buffer.append( "function('" ) - .append( getFunctionName() ) - .append( "', " ); + finally { + renderingContext.getFunctionStack().pop(); } - renderArguments( buffer, renderingContext ); - buffer.append( ')' ); - return buffer.toString(); } protected void renderArguments(StringBuilder buffer, RenderingContext renderingContext) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/SubstringFunction.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/SubstringFunction.java index aef90a0bf1..784640548e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/SubstringFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/SubstringFunction.java @@ -92,16 +92,24 @@ public class SubstringFunction } public String render(RenderingContext renderingContext) { - StringBuilder buffer = new StringBuilder(); - buffer.append( "substring(" ) - .append( ( (Renderable) getValue() ).render( renderingContext ) ) - .append( ',' ) - .append( ( (Renderable) getStart() ).render( renderingContext ) ); - if ( getLength() != null ) { - buffer.append( ',' ) - .append( ( (Renderable) getLength() ).render( renderingContext ) ); + renderingContext.getFunctionStack().push( this ); + + try { + final StringBuilder buffer = new StringBuilder(); + buffer.append( "substring(" ) + .append( ( (Renderable) getValue() ).render( renderingContext ) ) + .append( ',' ) + .append( ( (Renderable) getStart() ).render( renderingContext ) ); + + if ( getLength() != null ) { + buffer.append( ',' ) + .append( ( (Renderable) getLength() ).render( renderingContext ) ); + } + + return buffer.append( ')' ).toString(); + } + finally { + renderingContext.getFunctionStack().pop(); } - buffer.append( ')' ); - return buffer.toString(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/TrimFunction.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/TrimFunction.java index 89d5ee7ccf..062d38a857 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/TrimFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/function/TrimFunction.java @@ -102,26 +102,31 @@ public class TrimFunction @Override public String render(RenderingContext renderingContext) { - String renderedTrimChar; - if ( trimCharacter.getClass().isAssignableFrom( - LiteralExpression.class ) ) { - // If the character is a literal, treat it as one. A few dialects - // do not support parameters as trim() arguments. - renderedTrimChar = '\'' + ( (LiteralExpression) - trimCharacter ).getLiteral().toString() + '\''; + renderingContext.getFunctionStack().push( this ); + + try { + String renderedTrimChar; + if ( trimCharacter.getClass().isAssignableFrom( LiteralExpression.class ) ) { + // If the character is a literal, treat it as one. A few dialects + // do not support parameters as trim() arguments. + renderedTrimChar = '\'' + ( (LiteralExpression) + trimCharacter ).getLiteral().toString() + '\''; + } + else { + renderedTrimChar = ( (Renderable) trimCharacter ).render( renderingContext ); + } + return new StringBuilder() + .append( "trim(" ) + .append( trimspec.name() ) + .append( ' ' ) + .append( renderedTrimChar ) + .append( " from " ) + .append( ( (Renderable) trimSource ).render( renderingContext ) ) + .append( ')' ) + .toString(); } - else { - renderedTrimChar = ( (Renderable) trimCharacter ).render( - renderingContext ); + finally { + renderingContext.getFunctionStack().pop(); } - return new StringBuilder() - .append( "trim(" ) - .append( trimspec.name() ) - .append( ' ' ) - .append( renderedTrimChar ) - .append( " from " ) - .append( ( (Renderable) trimSource ).render( renderingContext ) ) - .append( ')' ) - .toString(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/Clause.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/Clause.java new file mode 100644 index 0000000000..7f0b80a5be --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/Clause.java @@ -0,0 +1,49 @@ +/* + * 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; + +import org.hibernate.Incubating; + +/** + * Used to indicate which query clause we are currently processing + * + * @author Steve Ebersole + */ +@Incubating +public enum Clause { + /** + * The insert values clause + */ + INSERT, + + /** + * The update set clause + */ + UPDATE, + + /** + * Not used in 5.x. Intended for use in 6+ as indicator + * of processing predicates (where clause) that occur in a + * delete + */ + DELETE, + + SELECT, + FROM, + WHERE, + GROUP, + HAVING, + ORDER, + LIMIT, + CALL, + + /** + * Again, not used in 5.x. Used in 6+ + */ + IRRELEVANT + +}