HHH-13001 - NPE rendering nested criteria expressions

This commit is contained in:
Steve Ebersole 2018-10-15 21:52:17 -05:00
parent 23c6565f72
commit a2b26b81b3
23 changed files with 616 additions and 252 deletions

View File

@ -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<T> implements Stack<T> {
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<T> action) {
action.accept( instance );
}
@Override
public <X> X findCurrentFirst(Function<T, X> action) {
return action.apply( instance );
}
}

View File

@ -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 <T> The type of things stored in the stack
*
* @author Steve Ebersole
*/
public interface Stack<T> {
/**
* 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<T> 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> X findCurrentFirst(Function<T, X> action);
}

View File

@ -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 <T> The type of things stored in the stack
*
* @author Steve Ebersole
*/
public class StandardStack<T> implements Stack<T> {
private LinkedList<T> 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<T> action) {
internalStack.forEach( action );
}
@Override
public <X> X findCurrentFirst(Function<T, X> function) {
for ( T t : internalStack ) {
final X result = function.apply( t );
if ( result != null ) {
return result;
}
}
return null;
}
}

View File

@ -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.compile.RenderingContext;
import org.hibernate.query.criteria.internal.path.RootImpl; import org.hibernate.query.criteria.internal.path.RootImpl;
import org.hibernate.query.spi.QueryImplementor; import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.sql.ast.Clause;
/** /**
* Base class for commonality between {@link javax.persistence.criteria.CriteriaUpdate} and * Base class for commonality between {@link javax.persistence.criteria.CriteriaUpdate} and
@ -155,9 +156,17 @@ public abstract class AbstractManipulationCriteriaQuery<T> implements Compilable
} }
protected void renderRestrictions(StringBuilder jpaql, RenderingContext renderingContext) { protected void renderRestrictions(StringBuilder jpaql, RenderingContext renderingContext) {
if ( getRestriction() != null) { if ( getRestriction() == null ) {
return;
}
renderingContext.getClauseStack().push( Clause.WHERE );
try {
jpaql.append( " where " ) jpaql.append( " where " )
.append( ( (Renderable) getRestriction() ).render( renderingContext ) ); .append( ( (Renderable) getRestriction() ).render( renderingContext ) );
} }
finally {
renderingContext.getClauseStack().pop();
}
} }
} }

View File

@ -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.InterpretedParameterMetadata;
import org.hibernate.query.criteria.internal.compile.RenderingContext; import org.hibernate.query.criteria.internal.compile.RenderingContext;
import org.hibernate.query.spi.QueryImplementor; import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.sql.ast.Clause;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
@ -291,16 +292,7 @@ public class CriteriaQueryImpl<T> extends AbstractNode implements CriteriaQuery<
queryStructure.render( jpaqlBuffer, renderingContext ); queryStructure.render( jpaqlBuffer, renderingContext );
if ( ! getOrderList().isEmpty() ) { renderOrderByClause( renderingContext, jpaqlBuffer );
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 = ", ";
}
}
final String jpaqlString = jpaqlBuffer.toString(); final String jpaqlString = jpaqlBuffer.toString();
@ -385,4 +377,25 @@ public class CriteriaQueryImpl<T> 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();
}
}
} }

View File

@ -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.DelegatedExpressionImpl;
import org.hibernate.query.criteria.internal.expression.ExpressionImpl; import org.hibernate.query.criteria.internal.expression.ExpressionImpl;
import org.hibernate.query.criteria.internal.path.RootImpl; 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 * The Hibernate implementation of the JPA {@link Subquery} contract. Mostlty a set of delegation to its internal
@ -257,14 +258,13 @@ public class CriteriaSubqueryImpl<T> extends ExpressionImpl<T> implements Subque
@Override @Override
public String render(RenderingContext renderingContext) { public String render(RenderingContext renderingContext) {
if ( renderingContext.getClauseStack().getCurrent() == Clause.SELECT ) {
throw new IllegalStateException( "Subquery cannot occur in select clause" );
}
StringBuilder subqueryBuffer = new StringBuilder( "(" ); StringBuilder subqueryBuffer = new StringBuilder( "(" );
queryStructure.render( subqueryBuffer, renderingContext ); queryStructure.render( subqueryBuffer, renderingContext );
subqueryBuffer.append( ')' ); subqueryBuffer.append( ')' );
return subqueryBuffer.toString(); return subqueryBuffer.toString();
} }
@Override
public String renderProjection(RenderingContext renderingContext) {
throw new IllegalStateException( "Subquery cannot occur in select clause" );
}
} }

View File

@ -16,6 +16,7 @@ import javax.persistence.metamodel.SingularAttribute;
import org.hibernate.query.criteria.internal.compile.RenderingContext; import org.hibernate.query.criteria.internal.compile.RenderingContext;
import org.hibernate.query.criteria.internal.path.SingularAttributePath; import org.hibernate.query.criteria.internal.path.SingularAttributePath;
import org.hibernate.sql.ast.Clause;
/** /**
* Hibernate implementation of the JPA 2.1 {@link CriteriaUpdate} contract. * Hibernate implementation of the JPA 2.1 {@link CriteriaUpdate} contract.
@ -122,10 +123,13 @@ public class CriteriaUpdateImpl<T> extends AbstractManipulationCriteriaQuery<T>
} }
private void renderAssignments(StringBuilder jpaql, RenderingContext renderingContext) { private void renderAssignments(StringBuilder jpaql, RenderingContext renderingContext) {
renderingContext.getClauseStack().push( Clause.UPDATE );
try {
jpaql.append( " set " ); jpaql.append( " set " );
boolean first = true; boolean first = true;
for ( Assignment assignment : assignments ) { for ( Assignment assignment : assignments ) {
if ( ! first ) { if ( !first ) {
jpaql.append( ", " ); jpaql.append( ", " );
} }
jpaql.append( assignment.attributePath.render( renderingContext ) ) jpaql.append( assignment.attributePath.render( renderingContext ) )
@ -134,6 +138,10 @@ public class CriteriaUpdateImpl<T> extends AbstractManipulationCriteriaQuery<T>
first = false; first = false;
} }
} }
finally {
renderingContext.getClauseStack().pop();
}
}
private static class Assignment<A> { private static class Assignment<A> {
private final SingularAttributePath<A> attributePath; private final SingularAttributePath<A> attributePath;

View File

@ -31,6 +31,7 @@ import javax.persistence.metamodel.EntityType;
import org.hibernate.query.criteria.internal.compile.RenderingContext; import org.hibernate.query.criteria.internal.compile.RenderingContext;
import org.hibernate.query.criteria.internal.path.RootImpl; import org.hibernate.query.criteria.internal.path.RootImpl;
import org.hibernate.query.criteria.internal.path.RootImpl.TreatedRoot; 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 * Models basic query structure. Used as a delegate in implementing both
@ -230,37 +231,34 @@ public class QueryStructure<T> implements Serializable {
@SuppressWarnings({ "unchecked" }) @SuppressWarnings({ "unchecked" })
public void render(StringBuilder jpaqlQuery, RenderingContext renderingContext) { public void render(StringBuilder jpaqlQuery, RenderingContext renderingContext) {
jpaqlQuery.append( "select " ); renderSelectClause( jpaqlQuery, renderingContext );
if ( isDistinct() ) {
jpaqlQuery.append( "distinct " );
}
if ( getSelection() == null ) {
jpaqlQuery.append( locateImplicitSelection().renderProjection( renderingContext ) );
}
else {
jpaqlQuery.append( ( (Renderable) getSelection() ).renderProjection( renderingContext ) );
}
renderFromClause( jpaqlQuery, renderingContext ); renderFromClause( jpaqlQuery, renderingContext );
if ( getRestriction() != null) { renderWhereClause( jpaqlQuery, renderingContext );
jpaqlQuery.append( " where " )
.append( ( (Renderable) getRestriction() ).render( renderingContext ) ); renderGroupByClause( jpaqlQuery, renderingContext );
} }
if ( ! getGroupings().isEmpty() ) { protected void renderSelectClause(StringBuilder jpaqlQuery, RenderingContext renderingContext) {
jpaqlQuery.append( " group by " ); renderingContext.getClauseStack().push( Clause.SELECT );
String sep = "";
for ( Expression grouping : getGroupings() ) { try {
jpaqlQuery.append( sep ) jpaqlQuery.append( "select " );
.append( ( (Renderable) grouping ).renderGroupBy( renderingContext ) );
sep = ", "; if ( isDistinct() ) {
jpaqlQuery.append( "distinct " );
} }
if ( getHaving() != null ) { if ( getSelection() == null ) {
jpaqlQuery.append( " having " ) jpaqlQuery.append( locateImplicitSelection().render( renderingContext ) );
.append( ( (Renderable) getHaving() ).render( renderingContext ) );
} }
else {
jpaqlQuery.append( ( (Renderable) getSelection() ).render( renderingContext ) );
}
}
finally {
renderingContext.getClauseStack().pop();
} }
} }
@ -290,19 +288,22 @@ public class QueryStructure<T> implements Serializable {
@SuppressWarnings({ "unchecked" }) @SuppressWarnings({ "unchecked" })
private void renderFromClause(StringBuilder jpaqlQuery, RenderingContext renderingContext) { private void renderFromClause(StringBuilder jpaqlQuery, RenderingContext renderingContext) {
renderingContext.getClauseStack().push( Clause.FROM );
try {
jpaqlQuery.append( " from " ); jpaqlQuery.append( " from " );
String sep = ""; String sep = "";
for ( Root root : getRoots() ) { for ( Root root : getRoots() ) {
( (FromImplementor) root ).prepareAlias( renderingContext ); ( (FromImplementor) root ).prepareAlias( renderingContext );
jpaqlQuery.append( sep ); jpaqlQuery.append( sep );
jpaqlQuery.append( ( (FromImplementor) root ).renderTableExpression( renderingContext ) );
sep = ", "; sep = ", ";
jpaqlQuery.append( ( (FromImplementor) root ).renderTableExpression( renderingContext ) );
} }
for ( Root root : getRoots() ) { for ( Root root : getRoots() ) {
renderJoins( jpaqlQuery, renderingContext, root.getJoins() ); renderJoins( jpaqlQuery, renderingContext, root.getJoins() );
if (root instanceof RootImpl) { if ( root instanceof RootImpl ) {
Set<TreatedRoot> treats = ((RootImpl)root).getTreats(); Set<TreatedRoot> treats = ( (RootImpl) root ).getTreats();
for ( TreatedRoot treat : treats ) { for ( TreatedRoot treat : treats ) {
renderJoins( jpaqlQuery, renderingContext, treat.getJoins() ); renderJoins( jpaqlQuery, renderingContext, treat.getJoins() );
} }
@ -312,11 +313,11 @@ public class QueryStructure<T> implements Serializable {
if ( isSubQuery ) { if ( isSubQuery ) {
if ( correlationRoots != null ) { if ( correlationRoots != null ) {
for ( FromImplementor<?,?> correlationRoot : correlationRoots ) { for ( FromImplementor<?, ?> correlationRoot : correlationRoots ) {
final FromImplementor correlationParent = correlationRoot.getCorrelationParent(); final FromImplementor correlationParent = correlationRoot.getCorrelationParent();
correlationParent.prepareAlias( renderingContext ); correlationParent.prepareAlias( renderingContext );
final String correlationRootAlias = correlationParent.getAlias(); final String correlationRootAlias = correlationParent.getAlias();
for ( Join<?,?> correlationJoin : correlationRoot.getJoins() ) { for ( Join<?, ?> correlationJoin : correlationRoot.getJoins() ) {
final JoinImplementor correlationJoinImpl = (JoinImplementor) correlationJoin; final JoinImplementor correlationJoinImpl = (JoinImplementor) correlationJoin;
// IMPL NOTE: reuse the sep from above! // IMPL NOTE: reuse the sep from above!
jpaqlQuery.append( sep ); jpaqlQuery.append( sep );
@ -333,6 +334,61 @@ public class QueryStructure<T> implements Serializable {
} }
} }
} }
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" }) @SuppressWarnings({ "unchecked" })
private void renderJoins( private void renderJoins(

View File

@ -10,38 +10,15 @@ package org.hibernate.query.criteria.internal;
import org.hibernate.query.criteria.internal.compile.RenderingContext; 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 * @author Steve Ebersole
*/ */
public interface Renderable { public interface Renderable {
/** /**
* Render clause * Perform the rendering, returning the rendition
*
* @param renderingContext context
* @return rendered expression
*/ */
String render(RenderingContext renderingContext); 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 );
}
} }

View File

@ -20,8 +20,12 @@ import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.util.StringHelper; 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.LiteralHandlingMode;
import org.hibernate.query.criteria.internal.expression.function.FunctionExpression;
import org.hibernate.query.spi.QueryImplementor; import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.sql.ast.Clause;
import org.hibernate.type.Type; import org.hibernate.type.Type;
/** /**
@ -63,6 +67,9 @@ public class CriteriaCompiler implements Serializable {
private int aliasCount; private int aliasCount;
private int explicitParameterCount; private int explicitParameterCount;
private final Stack<Clause> clauseStack = new StandardStack<>();
private final Stack<FunctionExpression> functionContextStack = new StandardStack<>();
public String generateAlias() { public String generateAlias() {
return "generatedAlias" + aliasCount++; return "generatedAlias" + aliasCount++;
} }
@ -71,6 +78,16 @@ public class CriteriaCompiler implements Serializable {
return "param" + explicitParameterCount++; return "param" + explicitParameterCount++;
} }
@Override
public Stack<Clause> getClauseStack() {
return clauseStack;
}
@Override
public Stack<FunctionExpression> getFunctionStack() {
return functionContextStack;
}
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ExplicitParameterInfo registerExplicitParameter(ParameterExpression<?> criteriaQueryParameter) { public ExplicitParameterInfo registerExplicitParameter(ParameterExpression<?> criteriaQueryParameter) {

View File

@ -9,7 +9,10 @@ package org.hibernate.query.criteria.internal.compile;
import javax.persistence.criteria.ParameterExpression; import javax.persistence.criteria.ParameterExpression;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.criteria.LiteralHandlingMode; 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. * Used to provide a context and services to the rendering.
@ -67,4 +70,8 @@ public interface RenderingContext {
default LiteralHandlingMode getCriteriaLiteralHandlingMode() { default LiteralHandlingMode getCriteriaLiteralHandlingMode() {
return LiteralHandlingMode.AUTO; return LiteralHandlingMode.AUTO;
} }
Stack<Clause> getClauseStack();
Stack<FunctionExpression> getFunctionStack();
} }

View File

@ -77,15 +77,17 @@ public class CompoundSelectionImpl<X>
if ( isConstructor ) { if ( isConstructor ) {
buff.append( "new " ).append( getJavaType().getName() ).append( '(' ); buff.append( "new " ).append( getJavaType().getName() ).append( '(' );
} }
String sep = ""; String sep = "";
for ( Selection selection : selectionItems ) { for ( Selection selection : selectionItems ) {
buff.append( sep ) buff.append( sep ).append( ( (Renderable) selection ).render( renderingContext ) );
.append( ( (Renderable) selection ).renderProjection( renderingContext ) );
sep = ", "; sep = ", ";
} }
if ( isConstructor ) { if ( isConstructor ) {
buff.append( ')' ); buff.append( ')' );
} }
return buff.toString(); return buff.toString();
} }
} }

View File

@ -47,37 +47,54 @@ public class LiteralExpression<T> extends ExpressionImpl<T> implements Serializa
@SuppressWarnings({ "unchecked" }) @SuppressWarnings({ "unchecked" })
public String render(RenderingContext renderingContext) { 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(); LiteralHandlingMode literalHandlingMode = renderingContext.getCriteriaLiteralHandlingMode();
switch ( literalHandlingMode ) { switch ( literalHandlingMode ) {
case AUTO: case AUTO: {
if ( ValueHandlerFactory.isNumeric( literal ) ) { if ( ValueHandlerFactory.isNumeric( literal ) ) {
return ValueHandlerFactory.determineAppropriateHandler( (Class) literal.getClass() ).render( literal ); return ValueHandlerFactory.determineAppropriateHandler( (Class) literal.getClass() ).render( literal );
} }
else { else {
return bindLiteral( renderingContext ); return bindLiteral( renderingContext );
} }
case BIND: }
case BIND: {
return bindLiteral( renderingContext ); return bindLiteral( renderingContext );
case INLINE: }
case INLINE: {
Object literalValue = literal; Object literalValue = literal;
if ( String.class.equals( literal.getClass() ) ) { if ( String.class.equals( literal.getClass() ) ) {
literalValue = renderingContext.getDialect().inlineLiteral( (String) literal ); literalValue = renderingContext.getDialect().inlineLiteral( (String) literal );
} }
return ValueHandlerFactory.determineAppropriateHandler( (Class) literal.getClass() ).render( literalValue ); return ValueHandlerFactory.determineAppropriateHandler( (Class) literal.getClass() ).render( literalValue );
default: }
default: {
throw new IllegalArgumentException( "Unexpected LiteralHandlingMode: " + literalHandlingMode ); throw new IllegalArgumentException( "Unexpected LiteralHandlingMode: " + literalHandlingMode );
} }
} }
private String bindLiteral(RenderingContext renderingContext) {
final String parameterName = renderingContext.registerLiteralParameterBinding( getLiteral(), getJavaType() );
return ':' + parameterName;
} }
@SuppressWarnings({ "unchecked" }) private String renderProjection() {
public String renderProjection(RenderingContext renderingContext) {
// some drivers/servers do not like parameters in the select clause // some drivers/servers do not like parameters in the select clause
final ValueHandlerFactory.ValueHandler handler = final ValueHandlerFactory.ValueHandler handler =
ValueHandlerFactory.determineAppropriateHandler( literal.getClass() ); ValueHandlerFactory.determineAppropriateHandler( literal.getClass() );
@ -89,9 +106,9 @@ public class LiteralExpression<T> extends ExpressionImpl<T> implements Serializa
} }
} }
@Override private String bindLiteral(RenderingContext renderingContext) {
public String renderGroupBy(RenderingContext renderingContext) { final String parameterName = renderingContext.registerLiteralParameterBinding( getLiteral(), getJavaType() );
return renderProjection( renderingContext ); return ':' + parameterName;
} }
@Override @Override

View File

@ -16,6 +16,7 @@ import org.hibernate.query.criteria.internal.ParameterRegistry;
import org.hibernate.query.criteria.internal.PathImplementor; import org.hibernate.query.criteria.internal.PathImplementor;
import org.hibernate.query.criteria.internal.Renderable; import org.hibernate.query.criteria.internal.Renderable;
import org.hibernate.query.criteria.internal.compile.RenderingContext; import org.hibernate.query.criteria.internal.compile.RenderingContext;
import org.hibernate.sql.ast.Clause;
/** /**
* TODO : javadoc * TODO : javadoc
@ -48,17 +49,17 @@ public class MapEntryExpression<K,V>
} }
public String render(RenderingContext renderingContext) { public String render(RenderingContext renderingContext) {
// don't think this is valid outside of select clause... if ( renderingContext.getClauseStack().getCurrent() == Clause.SELECT ) {
throw new IllegalStateException( "illegal reference to map entry outside of select clause." ); return "entry(" + path( renderingContext ) + ")";
} }
public String renderProjection(RenderingContext renderingContext) { // don't think this is valid outside of select clause...
return "entry(" + path( renderingContext ) + ")"; throw new IllegalStateException( "illegal reference to map entry outside of select clause." );
} }
private String path(RenderingContext renderingContext) { private String path(RenderingContext renderingContext) {
return origin.getPathIdentifier() return origin.getPathIdentifier()
+ '.' + '.'
+ ( (Renderable) getAttribute() ).renderProjection( renderingContext ); + ( (Renderable) getAttribute() ).render( renderingContext );
} }
} }

View File

@ -12,6 +12,7 @@ import org.hibernate.query.criteria.internal.CriteriaBuilderImpl;
import org.hibernate.query.criteria.internal.ParameterRegistry; import org.hibernate.query.criteria.internal.ParameterRegistry;
import org.hibernate.query.criteria.internal.compile.RenderingContext; import org.hibernate.query.criteria.internal.compile.RenderingContext;
import org.hibernate.query.criteria.internal.expression.function.CastFunction; import org.hibernate.query.criteria.internal.expression.function.CastFunction;
import org.hibernate.sql.ast.Clause;
/** /**
* Represents a <tt>NULL</tt>literal expression. * Represents a <tt>NULL</tt>literal expression.
@ -28,10 +29,13 @@ public class NullLiteralExpression<T> extends ExpressionImpl<T> implements Seria
} }
public String render(RenderingContext renderingContext) { public String render(RenderingContext renderingContext) {
return "null"; if ( renderingContext.getClauseStack().getCurrent() == Clause.SELECT ) {
} // in the select clause render the ``null` using a cast so the db analyzer/optimizer
// understands the type
public String renderProjection(RenderingContext renderingContext) {
return CastFunction.CAST_NAME + "( null as " + renderingContext.getCastType( getJavaType() ) + ')'; return CastFunction.CAST_NAME + "( null as " + renderingContext.getCastType( getJavaType() ) + ')';
} }
// otherwise, just render `null`
return "null";
}
} }

View File

@ -106,41 +106,18 @@ public class SearchedCaseExpression<R>
} }
public String render(RenderingContext renderingContext) { 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<Renderable, RenderingContext, String> formatter) {
StringBuilder caseStatement = new StringBuilder( "case" ); StringBuilder caseStatement = new StringBuilder( "case" );
for ( WhenClause whenClause : getWhenClauses() ) { for ( WhenClause whenClause : getWhenClauses() ) {
caseStatement.append( " when " ) caseStatement.append( " when " )
.append( formatter.apply( (Renderable) whenClause.getCondition(), renderingContext ) ) .append( ( (Renderable) whenClause.getCondition() ).render( renderingContext ) )
.append( " then " ) .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( " end" );
return caseStatement.toString();
} }
caseStatement.append( " else " )
.append( ( (Renderable) getOtherwiseResult() ).render( renderingContext ) )
.append( " end" );
return caseStatement.toString();
}
} }

View File

@ -9,7 +9,6 @@ package org.hibernate.query.criteria.internal.expression;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.BiFunction;
import javax.persistence.criteria.CriteriaBuilder.SimpleCase; import javax.persistence.criteria.CriteriaBuilder.SimpleCase;
import javax.persistence.criteria.Expression; import javax.persistence.criteria.Expression;
@ -118,44 +117,21 @@ public class SimpleCaseExpression<C,R>
@Override @Override
public String render(RenderingContext renderingContext) { 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<Renderable, RenderingContext, String> formatter) {
StringBuilder caseExpr = new StringBuilder(); StringBuilder caseExpr = new StringBuilder();
caseExpr.append( "case " ) caseExpr.append( "case " )
.append( formatter.apply( (Renderable) getExpression(), renderingContext ) ); .append( ( (Renderable) getExpression() ).render( renderingContext ) );
for ( WhenClause whenClause : getWhenClauses() ) { for ( WhenClause whenClause : getWhenClauses() ) {
caseExpr.append( " when " ) caseExpr.append( " when " )
.append( formatter.apply( whenClause.getCondition(), renderingContext ) ) .append( whenClause.getCondition().render( renderingContext ) )
.append( " then " ) .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( " end" );
return caseExpr.toString();
} }
caseExpr.append( " else " )
.append( ( (Renderable) getOtherwiseResult() ).render( renderingContext ) )
.append( " end" );
return caseExpr.toString();
}
} }

View File

@ -7,6 +7,7 @@
package org.hibernate.query.criteria.internal.expression.function; package org.hibernate.query.criteria.internal.expression.function;
import java.io.Serializable; import java.io.Serializable;
import java.util.Locale;
import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; import org.hibernate.query.criteria.internal.CriteriaBuilderImpl;
import org.hibernate.query.criteria.internal.ParameterRegistry; import org.hibernate.query.criteria.internal.ParameterRegistry;
@ -47,10 +48,17 @@ public class CastFunction<T,Y>
@Override @Override
public String render(RenderingContext renderingContext) { public String render(RenderingContext renderingContext) {
return CAST_NAME + '(' + renderingContext.getFunctionStack().push( this );
castSource.render( renderingContext ) + try {
" as " + return String.format(
renderingContext.getCastType( getJavaType() ) + Locale.ROOT,
')'; "cast(%s as %s)",
castSource.render( renderingContext ),
renderingContext.getCastType( getJavaType() )
);
}
finally {
renderingContext.getFunctionStack().pop();
}
} }
} }

View File

@ -86,16 +86,26 @@ public class LocateFunction
@Override @Override
public String render(RenderingContext renderingContext) { public String render(RenderingContext renderingContext) {
StringBuilder buffer = new StringBuilder(); renderingContext.getFunctionStack().push( this );
try {
final StringBuilder buffer = new StringBuilder();
buffer.append( "locate(" ) buffer.append( "locate(" )
.append( ( (Renderable) getPattern() ).render( renderingContext ) ) .append( ( (Renderable) getPattern() ).render( renderingContext ) )
.append( ',' ) .append( ',' )
.append( ( (Renderable) getString() ).render( renderingContext ) ); .append( ( (Renderable) getString() ).render( renderingContext ) );
if ( getStart() != null ) { if ( getStart() != null ) {
buffer.append( ',' ) buffer.append( ',' )
.append( ( (Renderable) getStart() ).render( renderingContext ) ); .append( ( (Renderable) getStart() ).render( renderingContext ) );
} }
buffer.append( ')' ); buffer.append( ')' );
return buffer.toString(); return buffer.toString();
} }
finally {
renderingContext.getFunctionStack().pop();
}
}
} }

View File

@ -93,19 +93,26 @@ public class ParameterizedFunctionExpression<X>
@Override @Override
public String render(RenderingContext renderingContext) { public String render(RenderingContext renderingContext) {
StringBuilder buffer = new StringBuilder(); renderingContext.getFunctionStack().push( this );
try {
final StringBuilder buffer = new StringBuilder();
if ( isStandardJpaFunction() ) { if ( isStandardJpaFunction() ) {
buffer.append( getFunctionName() ) buffer.append( getFunctionName() ).append( "(" );
.append( "(" );
} }
else { else {
buffer.append( "function('" ) buffer.append( "function('" )
.append( getFunctionName() ) .append( getFunctionName() )
.append( "', " ); .append( "', " );
} }
renderArguments( buffer, renderingContext ); renderArguments( buffer, renderingContext );
buffer.append( ')' );
return buffer.toString(); return buffer.append( ')' ).toString();
}
finally {
renderingContext.getFunctionStack().pop();
}
} }
protected void renderArguments(StringBuilder buffer, RenderingContext renderingContext) { protected void renderArguments(StringBuilder buffer, RenderingContext renderingContext) {

View File

@ -92,16 +92,24 @@ public class SubstringFunction
} }
public String render(RenderingContext renderingContext) { public String render(RenderingContext renderingContext) {
StringBuilder buffer = new StringBuilder(); renderingContext.getFunctionStack().push( this );
try {
final StringBuilder buffer = new StringBuilder();
buffer.append( "substring(" ) buffer.append( "substring(" )
.append( ( (Renderable) getValue() ).render( renderingContext ) ) .append( ( (Renderable) getValue() ).render( renderingContext ) )
.append( ',' ) .append( ',' )
.append( ( (Renderable) getStart() ).render( renderingContext ) ); .append( ( (Renderable) getStart() ).render( renderingContext ) );
if ( getLength() != null ) { if ( getLength() != null ) {
buffer.append( ',' ) buffer.append( ',' )
.append( ( (Renderable) getLength() ).render( renderingContext ) ); .append( ( (Renderable) getLength() ).render( renderingContext ) );
} }
buffer.append( ')' );
return buffer.toString(); return buffer.append( ')' ).toString();
}
finally {
renderingContext.getFunctionStack().pop();
}
} }
} }

View File

@ -102,17 +102,18 @@ public class TrimFunction
@Override @Override
public String render(RenderingContext renderingContext) { public String render(RenderingContext renderingContext) {
renderingContext.getFunctionStack().push( this );
try {
String renderedTrimChar; String renderedTrimChar;
if ( trimCharacter.getClass().isAssignableFrom( if ( trimCharacter.getClass().isAssignableFrom( LiteralExpression.class ) ) {
LiteralExpression.class ) ) {
// If the character is a literal, treat it as one. A few dialects // If the character is a literal, treat it as one. A few dialects
// do not support parameters as trim() arguments. // do not support parameters as trim() arguments.
renderedTrimChar = '\'' + ( (LiteralExpression<Character>) renderedTrimChar = '\'' + ( (LiteralExpression<Character>)
trimCharacter ).getLiteral().toString() + '\''; trimCharacter ).getLiteral().toString() + '\'';
} }
else { else {
renderedTrimChar = ( (Renderable) trimCharacter ).render( renderedTrimChar = ( (Renderable) trimCharacter ).render( renderingContext );
renderingContext );
} }
return new StringBuilder() return new StringBuilder()
.append( "trim(" ) .append( "trim(" )
@ -124,4 +125,8 @@ public class TrimFunction
.append( ')' ) .append( ')' )
.toString(); .toString();
} }
finally {
renderingContext.getFunctionStack().pop();
}
}
} }

View File

@ -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
}