diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java index 0a4d24d02c..9fc17d7366 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java @@ -12,6 +12,7 @@ import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; import java.time.Instant; +import java.time.temporal.TemporalAccessor; import java.util.Collection; import java.util.List; import java.util.Map; @@ -894,6 +895,462 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder { @Incubating JpaSearchOrder desc(JpaCteCriteriaAttribute x, boolean nullsFirst); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Non-standard HQL functions + + /** + * Embed native {@code pattern} that will be unquoted and embedded in the generated SQL. + * Occurrences of {@code ?} in the pattern are replaced with the remaining {@code arguments} + * of the function. + * + * @param pattern native SQL pattern + * @param type type of this expression + * @param arguments optional arguments to the SQL pattern + * @param type of this expression + * + * @return native SQL expression + */ + JpaExpression sql(String pattern, Class type, Expression... arguments); + + /** + * Format a date, time, or datetime according to a pattern. + * The pattern must be written in a subset of the pattern language defined by + * Java’s {@link java.time.format.DateTimeFormatter}. + *

+ * See {@link org.hibernate.dialect.Dialect#appendDatetimeFormat Dialect#appendDatetimeFormat} + * for a full list of pattern elements. + * + * @param datetime the datetime expression to format + * @param pattern the pattern to use for formatting + * + * @return format expression + */ + JpaFunction format(Expression datetime, String pattern); + + /** + * Extracts the {@link org.hibernate.query.sqm.TemporalUnit#YEAR} of a date, time, or datetime expression. + * + * @param datetime the date, time, or datetime to extract the value from + * + * @return the extracted value + */ + JpaFunction year(Expression datetime); + + /** + * Extracts the {@link org.hibernate.query.sqm.TemporalUnit#MONTH} of a date, time, or datetime expression. + * + * @param datetime the date, time, or datetime to extract the value from + * + * @return the extracted value + */ + JpaFunction month(Expression datetime); + + /** + * Extracts the {@link org.hibernate.query.sqm.TemporalUnit#DAY} of a date, time, or datetime expression. + * + * @param datetime the date, time, or datetime to extract the value from + * + * @return the extracted value + */ + JpaFunction day(Expression datetime); + + /** + * Extracts the {@link org.hibernate.query.sqm.TemporalUnit#HOUR} of a date, time, or datetime expression. + * + * @param datetime the date, time, or datetime to extract the value from + * + * @return the extracted value + */ + JpaFunction hour(Expression datetime); + + /** + * Extracts the {@link org.hibernate.query.sqm.TemporalUnit#MINUTE} of a date, time, or datetime expression. + * + * @param datetime the date, time, or datetime to extract the value from + * + * @return the extracted value + */ + JpaFunction minute(Expression datetime); + + /** + * Extracts the {@link org.hibernate.query.sqm.TemporalUnit#SECOND} of a date, time, or datetime expression. + * + * @param datetime the date, time, or datetime to extract the value from + * + * @return the extracted value + */ + JpaFunction second(Expression datetime); + + /** + * @see #overlay(Expression, Expression, Expression, Expression) + */ + JpaFunction overlay(Expression string, String replacement, int start); + + /** + * @see #overlay(Expression, Expression, Expression, Expression) + */ + JpaFunction overlay(Expression string, Expression replacement, int start); + + /** + * @see #overlay(Expression, Expression, Expression, Expression) + */ + JpaFunction overlay(Expression string, String replacement, Expression start); + + /** + * @see #overlay(Expression, Expression, Expression, Expression) + */ + JpaFunction overlay(Expression string, Expression replacement, Expression start); + + /** + * @see #overlay(Expression, Expression, Expression, Expression) + */ + JpaFunction overlay(Expression string, String replacement, int start, int length); + + /** + * @see #overlay(Expression, Expression, Expression, Expression) + */ + JpaFunction overlay(Expression string, Expression replacement, int start, int length); + + /** + * @see #overlay(Expression, Expression, Expression, Expression) + */ + JpaFunction overlay(Expression string, String replacement, Expression start, int length); + + /** + * @see #overlay(Expression, Expression, Expression, Expression) + */ + JpaFunction overlay( + Expression string, + Expression replacement, + Expression start, + int length); + + /** + * @see #overlay(Expression, Expression, Expression, Expression) + */ + JpaFunction overlay(Expression string, String replacement, int start, Expression length); + + /** + * @see #overlay(Expression, Expression, Expression, Expression) + */ + JpaFunction overlay( + Expression string, + Expression replacement, + int start, + Expression length); + + /** + * @see #overlay(Expression, Expression, Expression, Expression) + */ + JpaFunction overlay( + Expression string, + String replacement, + Expression start, + Expression length); + + /** + * Overlay the {@code string} expression with the {@code replacement} expression, + * starting from index {@code start} and substituting a number of characters + * corresponding to the length of the {@code replacement} expression or the + * {@code length} parameter if specified. + * + * @param string string expression to be manipulated + * @param replacement string expression to replace in original + * @param start start position + * @param length optional, number of characters to substitute + * + * @return overlay expression + */ + JpaFunction overlay( + Expression string, + Expression replacement, + Expression start, + Expression length); + + /** + * @see #pad(Trimspec, Expression, Expression, Expression) + */ + JpaFunction pad(Expression x, int length); + + /** + * @see #pad(Trimspec, Expression, Expression, Expression) + */ + JpaFunction pad(Trimspec ts, Expression x, int length); + + /** + * @see #pad(Trimspec, Expression, Expression, Expression) + */ + JpaFunction pad(Expression x, Expression length); + + /** + * @see #pad(Trimspec, Expression, Expression, Expression) + */ + JpaFunction pad(Trimspec ts, Expression x, Expression length); + + /** + * @see #pad(Trimspec, Expression, Expression, Expression) + */ + JpaFunction pad(Expression x, int length, char padChar); + + /** + * @see #pad(Trimspec, Expression, Expression, Expression) + */ + JpaFunction pad(Trimspec ts, Expression x, int length, char padChar); + + /** + * @see #pad(Trimspec, Expression, Expression, Expression) + */ + JpaFunction pad(Expression x, Expression length, char padChar); + + /** + * @see #pad(Trimspec, Expression, Expression, Expression) + */ + JpaFunction pad(Trimspec ts, Expression x, Expression length, char padChar); + + /** + * @see #pad(Trimspec, Expression, Expression, Expression) + */ + JpaFunction pad(Expression x, int length, Expression padChar); + + /** + * @see #pad(Trimspec, Expression, Expression, Expression) + */ + JpaFunction pad(Trimspec ts, Expression x, int length, Expression padChar); + + /** + * @see #pad(Trimspec, Expression, Expression, Expression) + */ + JpaFunction pad(Expression x, Expression length, Expression padChar); + + /** + * Pad the specified string expression with whitespace or with the {@code padChar} character if specified. + * Optionally pass a {@link jakarta.persistence.criteria.CriteriaBuilder.Trimspec} to pad the + * string expression with {@code LEADING} or {@code TRAILING} (default) characters. + * + * @param ts optional {@link jakarta.persistence.criteria.CriteriaBuilder.Trimspec} + * @param x string expression to pad + * @param length length of the result string after padding + * @param padChar optional pad character + * + * @return pad expression + */ + JpaFunction pad( + Trimspec ts, + Expression x, + Expression length, + Expression padChar); + + /** + * @see #left(Expression, Expression) + */ + JpaFunction left(Expression x, int length); + + /** + * Extract the {@code length} leftmost characters of a string. + * + * @param x original string + * @param length number of characters + * + * @return left expression + */ + JpaFunction left(Expression x, Expression length); + + /** + * @see #right(Expression, Expression) + */ + JpaFunction right(Expression x, int length); + + /** + * Extract the {@code length} rightmost characters of a string. + * + * @param x original string + * @param length number of characters + * + * @return left expression + */ + JpaFunction right(Expression x, Expression length); + + /** + * @see #replace(Expression, Expression, Expression) + */ + JpaFunction replace(Expression x, String pattern, String replacement); + + /** + * @see #replace(Expression, Expression, Expression) + */ + JpaFunction replace(Expression x, String pattern, Expression replacement); + + /** + * @see #replace(Expression, Expression, Expression) + */ + JpaFunction replace(Expression x, Expression pattern, String replacement); + + /** + * Replace all occurrences of {@code pattern} within the original string with {@code replacement}. + * + * @param x original string + * @param pattern the string to be replaced + * @param replacement the new replacement string + * + * @return replace expression + */ + JpaFunction replace(Expression x, Expression pattern, Expression replacement); + + JpaFunction collate(Expression x, String collation); + + /** + * Create an expression that returns the base-10 logarithm + * of its argument. + * + * @param x expression + * + * @return base-10 logarithm + */ + JpaExpression log10(Expression x); + + /** + * @see #log(Expression, Expression) + */ + JpaExpression log(Number b, Expression x); + + /** + * Create an expression that returns the logarithm of {@code x} to the base {@code b}. + * + * @param b base + * @param x expression + * + * @return arbitrary-base logarithm + */ + JpaExpression log(Expression b, Expression x); + + /** + * Literal expression corresponding to the value of pi. + * + * @return pi expression + */ + JpaExpression pi(); + + /** + * Create an expression that returns the sine of its argument. + * + * @param x expression + * + * @return sine + */ + JpaExpression sin(Expression x); + + /** + * Create an expression that returns the cosine of its argument. + * + * @param x expression + * + * @return cosine + */ + JpaExpression cos(Expression x); + + /** + * Create an expression that returns the tangent of its argument. + * + * @param x expression + * + * @return tangent + */ + JpaExpression tan(Expression x); + + /** + * Create an expression that returns the inverse sine of its argument. + * + * @param x expression + * + * @return inverse sine + */ + JpaExpression asin(Expression x); + + /** + * Create an expression that returns the inverse cosine of its argument. + * + * @param x expression + * + * @return inverse cosine + */ + JpaExpression acos(Expression x); + + /** + * Create an expression that returns the inverse tangent of its argument. + * + * @param x expression + * + * @return inverse tangent + */ + JpaExpression atan(Expression x); + + /** + * @see #atan2(Expression, Expression) + */ + JpaExpression atan2(Number y, Expression x); + + /** + * @see #atan2(Expression, Expression) + */ + JpaExpression atan2(Expression y, Number x); + + /** + * Create an expression that returns the inverse tangent of {@code y} over {@code x}. + * + * @param y y coordinate + * @param x x coordinate + * + * @return 2-argument inverse tangent + */ + JpaExpression atan2(Expression y, Expression x); + + /** + * Create an expression that returns the hyperbolic sine of its argument. + * + * @param x expression + * + * @return hyperbolic sine + */ + JpaExpression sinh(Expression x); + + /** + * Create an expression that returns the hyperbolic cosine of its argument. + * + * @param x expression + * + * @return hyperbolic cosine + */ + JpaExpression cosh(Expression x); + + /** + * Create an expression that returns the hyperbolic tangent of its argument. + * + * @param x expression + * + * @return hyperbolic tangent + */ + JpaExpression tanh(Expression x); + + /** + * Create an expression that converts an angle measured in radians + * to an approximately equivalent angle measured in degrees. + * + * @param x expression + * + * @return degrees + */ + JpaExpression degrees(Expression x); + + /** + * Create an expression that converts an angle measured in degrees + * to an approximately equivalent angle measured in radians. + * + * @param x expression + * + * @return radians + */ + JpaExpression radians(Expression x); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Window functions diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java index 58b917cffe..32f6a2f993 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java @@ -15,6 +15,7 @@ import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; import java.util.Collection; import java.util.List; import java.util.Map; @@ -1257,6 +1258,339 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde return criteriaBuilder.desc( x, nullsFirst ); } + @Override + public JpaExpression sql(String pattern, Class type, Expression... arguments) { + return criteriaBuilder.sql( pattern, type, arguments ); + } + + @Override + public JpaFunction format(Expression datetime, String pattern) { + return criteriaBuilder.format( datetime, pattern ); + } + + @Override + public JpaFunction year(Expression datetime) { + return criteriaBuilder.year( datetime ); + } + + @Override + public JpaFunction month(Expression datetime) { + return criteriaBuilder.month( datetime ); + } + + @Override + public JpaFunction day(Expression datetime) { + return criteriaBuilder.day( datetime ); + } + + @Override + public JpaFunction hour(Expression datetime) { + return criteriaBuilder.hour( datetime ); + } + + @Override + public JpaFunction minute(Expression datetime) { + return criteriaBuilder.minute( datetime ); + } + + @Override + public JpaFunction second(Expression datetime) { + return criteriaBuilder.second( datetime ); + } + + @Override + public JpaFunction overlay(Expression string, String replacement, int start) { + return criteriaBuilder.overlay( string, replacement, start ); + } + + @Override + public JpaFunction overlay(Expression string, Expression replacement, int start) { + return criteriaBuilder.overlay( string, replacement, start ); + } + + @Override + public JpaFunction overlay(Expression string, String replacement, Expression start) { + return criteriaBuilder.overlay( string, replacement, start ); + } + + @Override + public JpaFunction overlay( + Expression string, + Expression replacement, + Expression start) { + return criteriaBuilder.overlay( string, replacement, start ); + } + + @Override + public JpaFunction overlay(Expression string, String replacement, int start, int length) { + return criteriaBuilder.overlay( string, replacement, start, length ); + } + + @Override + public JpaFunction overlay( + Expression string, + Expression replacement, + int start, + int length) { + return criteriaBuilder.overlay( string, replacement, start, length ); + } + + @Override + public JpaFunction overlay( + Expression string, + String replacement, + Expression start, + int length) { + return criteriaBuilder.overlay( string, replacement, start, length ); + } + + @Override + public JpaFunction overlay( + Expression string, + Expression replacement, + Expression start, + int length) { + return criteriaBuilder.overlay( string, replacement, start, length ); + } + + @Override + public JpaFunction overlay( + Expression string, + String replacement, + int start, + Expression length) { + return criteriaBuilder.overlay( string, replacement, start, length ); + } + + @Override + public JpaFunction overlay( + Expression string, + Expression replacement, + int start, + Expression length) { + return criteriaBuilder.overlay( string, replacement, start, length ); + } + + @Override + public JpaFunction overlay( + Expression string, + String replacement, + Expression start, + Expression length) { + return criteriaBuilder.overlay( string, replacement, start, length ); + } + + @Override + public JpaFunction overlay( + Expression string, + Expression replacement, + Expression start, + Expression length) { + return criteriaBuilder.overlay( string, replacement, start, length ); + } + + @Override + public JpaFunction pad(Expression x, int length) { + return criteriaBuilder.pad( x, length ); + } + + @Override + public JpaFunction pad(Trimspec ts, Expression x, int length) { + return criteriaBuilder.pad( ts, x, length ); + } + + @Override + public JpaFunction pad(Expression x, Expression length) { + return criteriaBuilder.pad( x, length ); + } + + @Override + public JpaFunction pad(Trimspec ts, Expression x, Expression length) { + return criteriaBuilder.pad( ts, x, length ); + } + + @Override + public JpaFunction pad(Expression x, int length, char padChar) { + return criteriaBuilder.pad( x, length, padChar ); + } + + @Override + public JpaFunction pad(Trimspec ts, Expression x, int length, char padChar) { + return criteriaBuilder.pad( ts, x, length, padChar ); + } + + @Override + public JpaFunction pad(Expression x, Expression length, char padChar) { + return criteriaBuilder.pad( x, length, padChar ); + } + + @Override + public JpaFunction pad(Trimspec ts, Expression x, Expression length, char padChar) { + return criteriaBuilder.pad( ts, x, length, padChar ); + } + + @Override + public JpaFunction pad(Expression x, int length, Expression padChar) { + return criteriaBuilder.pad( x, length, padChar ); + } + + @Override + public JpaFunction pad(Trimspec ts, Expression x, int length, Expression padChar) { + return criteriaBuilder.pad( ts, x, length, padChar ); + } + + @Override + public JpaFunction pad(Expression x, Expression length, Expression padChar) { + return criteriaBuilder.pad( x, length, padChar ); + } + + @Override + public JpaFunction pad( + Trimspec ts, + Expression x, + Expression length, + Expression padChar) { + return criteriaBuilder.pad( ts, x, length, padChar ); + } + + @Override + public JpaFunction left(Expression x, int length) { + return criteriaBuilder.left( x, length ); + } + + @Override + public JpaFunction left(Expression x, Expression length) { + return criteriaBuilder.left( x, length ); + } + + @Override + public JpaFunction right(Expression x, int length) { + return criteriaBuilder.right( x, length ); + } + + @Override + public JpaFunction right(Expression x, Expression length) { + return criteriaBuilder.right( x, length ); + } + + @Override + public JpaFunction replace(Expression x, String pattern, String replacement) { + return criteriaBuilder.replace( x, pattern, replacement ); + } + + @Override + public JpaFunction replace(Expression x, String pattern, Expression replacement) { + return criteriaBuilder.replace( x, pattern, replacement ); + } + + @Override + public JpaFunction replace(Expression x, Expression pattern, String replacement) { + return criteriaBuilder.replace( x, pattern, replacement ); + } + + @Override + public JpaFunction replace( + Expression x, + Expression pattern, + Expression replacement) { + return criteriaBuilder.replace( x, pattern, replacement ); + } + + @Override + public JpaFunction collate(Expression x, String collation) { + return criteriaBuilder.collate( x, collation ); + } + + @Override + public JpaExpression log10(Expression x) { + return criteriaBuilder.log10( x ); + } + + @Override + public JpaExpression log(Number b, Expression x) { + return criteriaBuilder.log( b, x ); + } + + @Override + public JpaExpression log(Expression b, Expression x) { + return criteriaBuilder.log( b, x ); + } + + @Override + public JpaExpression pi() { + return criteriaBuilder.pi(); + } + + @Override + public JpaExpression sin(Expression x) { + return criteriaBuilder.sin( x ); + } + + @Override + public JpaExpression cos(Expression x) { + return criteriaBuilder.cos( x ); + } + + @Override + public JpaExpression tan(Expression x) { + return criteriaBuilder.tan( x ); + } + + @Override + public JpaExpression asin(Expression x) { + return criteriaBuilder.asin( x ); + } + + @Override + public JpaExpression acos(Expression x) { + return criteriaBuilder.acos( x ); + } + + @Override + public JpaExpression atan(Expression x) { + return criteriaBuilder.atan( x ); + } + + @Override + public JpaExpression atan2(Number y, Expression x) { + return criteriaBuilder.atan2( y, x ); + } + + @Override + public JpaExpression atan2(Expression y, Number x) { + return criteriaBuilder.atan2( y, x ); + } + + @Override + public JpaExpression atan2(Expression y, Expression x) { + return criteriaBuilder.atan2( y, x ); + } + + @Override + public JpaExpression sinh(Expression x) { + return criteriaBuilder.sinh( x ); + } + + @Override + public JpaExpression cosh(Expression x) { + return criteriaBuilder.cosh( x ); + } + + @Override + public JpaExpression tanh(Expression x) { + return criteriaBuilder.tanh( x ); + } + + @Override + public JpaExpression degrees(Expression x) { + return criteriaBuilder.degrees( x ); + } + + @Override + public JpaExpression radians(Expression x) { + return criteriaBuilder.radians( x ); + } + @Override public JpaWindow createWindow() { return criteriaBuilder.createWindow(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java index e14bb08c53..950c73dc10 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java @@ -17,6 +17,7 @@ import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -58,7 +59,6 @@ import org.hibernate.query.criteria.JpaSearchOrder; import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.criteria.JpaSubQuery; import org.hibernate.query.criteria.JpaWindow; -import org.hibernate.query.criteria.JpaWindowFrame; import org.hibernate.query.criteria.ValueHandlingMode; import org.hibernate.query.criteria.spi.CriteriaBuilderExtension; import org.hibernate.query.spi.QueryEngine; @@ -71,6 +71,7 @@ import org.hibernate.query.sqm.SetOperator; import org.hibernate.query.sqm.SortOrder; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SqmQuerySource; +import org.hibernate.query.sqm.TemporalUnit; import org.hibernate.query.sqm.TrimSpec; import org.hibernate.query.sqm.UnaryArithmeticOperator; import org.hibernate.query.sqm.function.NamedSqmFunctionDescriptor; @@ -98,9 +99,11 @@ import org.hibernate.query.sqm.tree.expression.SqmCaseSearched; import org.hibernate.query.sqm.tree.expression.SqmCaseSimple; import org.hibernate.query.sqm.tree.expression.SqmCastTarget; import org.hibernate.query.sqm.tree.expression.SqmCoalesce; +import org.hibernate.query.sqm.tree.expression.SqmCollation; import org.hibernate.query.sqm.tree.expression.SqmCollectionSize; import org.hibernate.query.sqm.tree.expression.SqmDistinct; import org.hibernate.query.sqm.tree.expression.SqmExpression; +import org.hibernate.query.sqm.tree.expression.SqmExtractUnit; import org.hibernate.query.sqm.tree.expression.SqmFunction; import org.hibernate.query.sqm.tree.expression.SqmLiteral; import org.hibernate.query.sqm.tree.expression.SqmLiteralNull; @@ -143,7 +146,6 @@ import org.hibernate.service.ServiceRegistry; import org.hibernate.type.BasicType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType; import org.hibernate.type.spi.TypeConfiguration; import jakarta.persistence.Tuple; @@ -2569,6 +2571,498 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext, throw new InvalidObjectException( "Could not find a SessionFactory [uuid=" + uuid + ",name=" + name + "]" ); } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Non-standard HQL functions + + @Override + public SqmFunction sql(String pattern, Class type, Expression... arguments) { + List> sqmArguments = new ArrayList<>( expressionList( arguments ) ); + sqmArguments.add( 0, literal( pattern ) ); + return getFunctionDescriptor( "sql" ).generateSqmExpression( + sqmArguments, + getTypeConfiguration().getBasicTypeForJavaType( type ), + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction format(Expression datetime, String pattern) { + SqmExpression patternLiteral = value( pattern ); + return getFunctionDescriptor( "format" ).generateSqmExpression( + asList( (SqmExpression) datetime, patternLiteral ), + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + private SqmFunction extract( + Expression datetime, + TemporalUnit temporalUnit, + Class type) { + return getFunctionDescriptor( "extract" ).generateSqmExpression( + asList( + new SqmExtractUnit<>( + temporalUnit, + getTypeConfiguration().standardBasicTypeForJavaType( type ), + this + ), + (SqmTypedNode) datetime + ), + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction year(Expression datetime) { + return extract( datetime, TemporalUnit.YEAR, Integer.class ); + } + + @Override + public SqmFunction month(Expression datetime) { + return extract( datetime, TemporalUnit.MONTH, Integer.class ); + } + + @Override + public SqmFunction day(Expression datetime) { + return extract( datetime, TemporalUnit.DAY, Integer.class ); + } + + @Override + public SqmFunction hour(Expression datetime) { + return extract( datetime, TemporalUnit.HOUR, Integer.class ); + } + + @Override + public SqmFunction minute(Expression datetime) { + return extract( datetime, TemporalUnit.MINUTE, Integer.class ); + } + + @Override + public SqmFunction second(Expression datetime) { + return extract( datetime, TemporalUnit.SECOND, Float.class ); + } + + @Override + public SqmFunction overlay(Expression string, String replacement, int start) { + return overlay( string, replacement, value( start ), null ); + } + + @Override + public SqmFunction overlay(Expression string, Expression replacement, int start) { + return overlay( string, replacement, value( start ), null ); + } + + @Override + public SqmFunction overlay(Expression string, String replacement, Expression start) { + return overlay( string, value( replacement ), start, null ); + } + + @Override + public SqmFunction overlay( + Expression string, + Expression replacement, + Expression start) { + return overlay( string, replacement, start, null ); + } + + @Override + public SqmFunction overlay(Expression string, String replacement, int start, int length) { + return overlay( string, value( replacement ), value( start ), value( length ) ); + } + + @Override + public SqmFunction overlay( + Expression string, + Expression replacement, + int start, + int length) { + return overlay( string, replacement, value( start ), value( length ) ); + } + + @Override + public SqmFunction overlay( + Expression string, + String replacement, + Expression start, + int length) { + return overlay( string, value( replacement ), start, value( length ) ); + } + + @Override + public SqmFunction overlay( + Expression string, + Expression replacement, + Expression start, + int length) { + return overlay( string, replacement, start, value( length ) ); + } + + @Override + public SqmFunction overlay( + Expression string, + String replacement, + int start, + Expression length) { + return overlay( string, value( replacement ), value( start ), length ); + } + + @Override + public SqmFunction overlay( + Expression string, + Expression replacement, + int start, + Expression length) { + return overlay( string, replacement, value( start ), length ); + } + + @Override + public SqmFunction overlay( + Expression string, + String replacement, + Expression start, + Expression length) { + return overlay( string, value( replacement ), start, length ); + } + + @Override + public SqmFunction overlay( + Expression string, + Expression replacement, + Expression start, + Expression length) { + SqmExpression sqmString = (SqmExpression) string; + SqmExpression sqmReplacement = (SqmExpression) replacement; + SqmExpression sqmStart = (SqmExpression) start; + return getFunctionDescriptor( "overlay" ).generateSqmExpression( + ( length == null + ? asList( sqmString, sqmReplacement, sqmStart ) + : asList( sqmString, sqmReplacement, sqmStart, (SqmExpression) length ) ), + null, + getQueryEngine(), + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction pad(Expression x, int length) { + return pad( null, x, value( length ), null ); + } + + @Override + public SqmFunction pad(Trimspec ts, Expression x, int length) { + return pad( ts, x, value( length ), null ); + } + + @Override + public SqmFunction pad(Expression x, Expression length) { + return pad( null, x, length, null ); + } + + @Override + public SqmFunction pad(Trimspec ts, Expression x, Expression length) { + return pad( ts, x, length, null ); + } + + @Override + public SqmFunction pad(Expression x, int length, char padChar) { + return pad( null, x, value( length ), value( padChar ) ); + } + + @Override + public SqmFunction pad(Trimspec ts, Expression x, int length, char padChar) { + return pad( ts, x, value( length ), value( padChar ) ); + } + + @Override + public SqmFunction pad(Expression x, int length, Expression padChar) { + return pad( null, x, value( length ), padChar ); + } + + @Override + public SqmFunction pad(Trimspec ts, Expression x, int length, Expression padChar) { + return pad( ts, x, value( length ), padChar ); + } + + @Override + public SqmFunction pad(Expression x, Expression length, char padChar) { + return pad( null, x, length, value( padChar ) ); + } + + @Override + public SqmFunction pad(Trimspec ts, Expression x, Expression length, char padChar) { + return pad( ts, x, length, value( padChar ) ); + } + + @Override + public SqmFunction pad(Expression x, Expression length, Expression padChar) { + return pad( null, x, length, padChar ); + } + + @Override + public SqmFunction pad( + Trimspec ts, + Expression x, + Expression length, + Expression padChar) { + SqmExpression source = (SqmExpression) x; + SqmExpression sqmLength = (SqmExpression) length; + SqmTrimSpecification padSpec = new SqmTrimSpecification( + ts == null ? TrimSpec.TRAILING : convertTrimSpec( ts ), + this + ); + return getFunctionDescriptor( "pad" ).generateSqmExpression( + padChar != null + ? asList( source, sqmLength, padSpec, (SqmExpression) padChar ) + : asList( source, sqmLength, padSpec ), + null, + getQueryEngine(), + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction left(Expression x, int length) { + return left( x, value( length ) ); + } + + @Override + public SqmFunction left(Expression x, Expression length) { + return getFunctionDescriptor( "left" ).generateSqmExpression( + asList( (SqmExpression) x, (SqmExpression) length ), + null, + getQueryEngine(), + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction right(Expression x, int length) { + return right( x, value( length ) ); + } + + @Override + public SqmFunction right(Expression x, Expression length) { + return getFunctionDescriptor( "right" ).generateSqmExpression( + asList( (SqmExpression) x, (SqmExpression) length ), + null, + getQueryEngine(), + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction replace(Expression x, String pattern, String replacement) { + SqmExpression sqmPattern = value( pattern ); + return replace( x, sqmPattern, value( replacement, sqmPattern ) ); + } + + @Override + public SqmFunction replace(Expression x, String pattern, Expression replacement) { + return replace( x, value( pattern ), replacement ); + } + + @Override + public SqmFunction replace(Expression x, Expression pattern, String replacement) { + return replace( x, pattern, value( replacement ) ); + } + + @Override + public SqmFunction replace( + Expression x, + Expression pattern, + Expression replacement) { + return getFunctionDescriptor( "replace" ).generateSqmExpression( + asList( + (SqmExpression) x, + (SqmExpression) pattern, + (SqmExpression) replacement + ), + null, + getQueryEngine(), + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction collate(Expression x, String collation) { + SqmCollation sqmCollation = new SqmCollation( collation, null, this ); + return getFunctionDescriptor( "collate" ).generateSqmExpression( + asList( (SqmExpression) x, sqmCollation ), + null, + getQueryEngine(), + getJpaMetamodel().getTypeConfiguration() + ); + } + + + @Override + public SqmFunction log10(Expression x) { + return getFunctionDescriptor( "log10" ).generateSqmExpression( + (SqmTypedNode) x, + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction log(Number b, Expression x) { + return log( value( b ), x ); + } + + @Override + public SqmFunction log(Expression b, Expression x) { + return getFunctionDescriptor( "log" ).generateSqmExpression( + asList( (SqmTypedNode) b, (SqmTypedNode) x ), + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction pi() { + return getFunctionDescriptor( "pi" ).generateSqmExpression( + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction sin(Expression x) { + return getFunctionDescriptor( "sin" ).generateSqmExpression( + (SqmTypedNode) x, + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction cos(Expression x) { + return getFunctionDescriptor( "cos" ).generateSqmExpression( + (SqmTypedNode) x, + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction tan(Expression x) { + return getFunctionDescriptor( "tan" ).generateSqmExpression( + (SqmTypedNode) x, + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction asin(Expression x) { + return getFunctionDescriptor( "asin" ).generateSqmExpression( + (SqmTypedNode) x, + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction acos(Expression x) { + return getFunctionDescriptor( "acos" ).generateSqmExpression( + (SqmTypedNode) x, + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction atan(Expression x) { + return getFunctionDescriptor( "atan" ).generateSqmExpression( + (SqmTypedNode) x, + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction atan2(Number y, Expression x) { + return atan2( value( y ), x ); + } + + @Override + public SqmFunction atan2(Expression y, Number x) { + return atan2( y, value( x ) ); + } + + @Override + public SqmFunction atan2(Expression y, Expression x) { + return getFunctionDescriptor( "atan2" ).generateSqmExpression( + asList( (SqmTypedNode) y, (SqmTypedNode) x ), + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction sinh(Expression x) { + return getFunctionDescriptor( "sinh" ).generateSqmExpression( + (SqmTypedNode) x, + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction cosh(Expression x) { + return getFunctionDescriptor( "cosh" ).generateSqmExpression( + (SqmTypedNode) x, + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction tanh(Expression x) { + return getFunctionDescriptor( "tanh" ).generateSqmExpression( + (SqmTypedNode) x, + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction degrees(Expression x) { + return getFunctionDescriptor( "degrees" ).generateSqmExpression( + (SqmTypedNode) x, + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + + @Override + public SqmFunction radians(Expression x) { + return getFunctionDescriptor( "radians" ).generateSqmExpression( + (SqmTypedNode) x, + null, + queryEngine, + getJpaMetamodel().getTypeConfiguration() + ); + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Window functions diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/HibernateCriteriaBuilderFunctionsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/HibernateCriteriaBuilderFunctionsTest.java new file mode 100644 index 0000000000..dbab586026 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/HibernateCriteriaBuilderFunctionsTest.java @@ -0,0 +1,413 @@ +/* + * 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.orm.test.query.criteria; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.List; + +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.query.criteria.HibernateCriteriaBuilder; + +import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.hibernate.testing.orm.domain.gambit.EntityOfBasics; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Tuple; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Root; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests for various non JPA standard {@link HibernateCriteriaBuilder} functions. + * + * @author Marco Belladelli + */ +@DomainModel(standardModels = StandardDomainModel.GAMBIT) +@SessionFactory +public class HibernateCriteriaBuilderFunctionsTest { + + private final static String DATETIME_PATTERN = "yyyy-MM-dd"; + + @BeforeEach + public void prepareData(SessionFactoryScope scope) { + scope.inTransaction( em -> { + Date now = new Date(); + + EntityOfBasics entity1 = new EntityOfBasics(); + entity1.setId( 1 ); + entity1.setTheString( "5" ); + entity1.setTheInt( 5 ); + entity1.setTheInteger( -1 ); + entity1.setTheDouble( 1.0 ); + entity1.setTheDate( now ); + entity1.setTheLocalDateTime( LocalDateTime.now() ); + entity1.setTheBoolean( true ); + em.persist( entity1 ); + + EntityOfBasics entity2 = new EntityOfBasics(); + entity2.setId( 2 ); + entity2.setTheString( "6" ); + entity2.setTheInt( 6 ); + entity2.setTheInteger( -2 ); + entity2.setTheDouble( 6.0 ); + entity2.setTheBoolean( true ); + em.persist( entity2 ); + + EntityOfBasics entity3 = new EntityOfBasics(); + entity3.setId( 3 ); + entity3.setTheString( "7" ); + entity3.setTheInt( 7 ); + entity3.setTheInteger( 3 ); + entity3.setTheDouble( 7.0 ); + entity3.setTheBoolean( false ); + entity3.setTheDate( new Date( now.getTime() + 200000L ) ); + em.persist( entity3 ); + + EntityOfBasics entity4 = new EntityOfBasics(); + entity4.setId( 4 ); + entity4.setTheString( "thirteen" ); + entity4.setTheInt( 13 ); + entity4.setTheInteger( 4 ); + entity4.setTheDouble( 13.0 ); + entity4.setTheBoolean( false ); + entity4.setTheDate( new Date( now.getTime() + 300000L ) ); + em.persist( entity4 ); + + EntityOfBasics entity5 = new EntityOfBasics(); + entity5.setId( 5 ); + entity5.setTheString( "5" ); + entity5.setTheInt( 5 ); + entity5.setTheInteger( 5 ); + entity5.setTheDouble( 9.0 ); + entity5.setTheBoolean( false ); + em.persist( entity5 ); + } ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from EntityOfBasics" ).executeUpdate() ); + } + + @Test + public void testSql(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery( String.class ); + Root from = query.from( EntityOfBasics.class ); + + query.select( cb.sql( "'test'", String.class ) ); + + List resultList = session.createQuery( query ).getResultList(); + assertEquals( 5, resultList.size() ); + resultList.forEach( r -> assertEquals( "test", r ) ); + } ); + } + + @Test + @RequiresDialect(PostgreSQLDialect.class) + public void testSqlCustomType(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery( Integer.class ); + Root from = query.from( EntityOfBasics.class ); + + try { + query.select( from.get( "id" ) ).where( cb.equal( + cb.value( InetAddress.getByName( "127.0.0.1" ) ), + cb.sql( "?::inet", InetAddress.class, cb.literal( "127.0.0.1" ) ) + ) ); + } + catch (UnknownHostException e) { + throw new RuntimeException( e ); + } + + List resultList = session.createQuery( query ).getResultList(); + assertEquals( 5, resultList.size() ); + } ); + } + + @Test + public void testFormatWithJavaUtilDate(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery( Tuple.class ); + Root from = query.from( EntityOfBasics.class ); + + Expression theDate = from.get( "theDate" ).as( LocalDate.class ); + query.multiselect( from.get( "id" ), cb.format( theDate, DATETIME_PATTERN ) ) + .where( cb.isNotNull( theDate ) ) + .orderBy( cb.asc( theDate ) ); + + List resultList = session.createQuery( query ).getResultList(); + assertEquals( 3, resultList.size() ); + + EntityOfBasics eob = session.find( EntityOfBasics.class, resultList.get( 0 ).get( 0 ) ); + String formattedDate = new SimpleDateFormat( DATETIME_PATTERN ).format( eob.getTheDate() ); + assertEquals( formattedDate, resultList.get( 0 ).get( 1 ) ); + } ); + } + + @Test + public void testFormatWithJavaTimeLocalDateTime(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createTupleQuery(); + Root from = query.from( EntityOfBasics.class ); + + Path theLocalDateTime = from.get( "theLocalDateTime" ); + query.multiselect( from.get( "id" ), cb.format( theLocalDateTime, DATETIME_PATTERN ) ) + .where( cb.isNotNull( theLocalDateTime ) ); + + List resultList = session.createQuery( query ).getResultList(); + assertEquals( 1, resultList.size() ); + + EntityOfBasics eob = session.find( EntityOfBasics.class, resultList.get( 0 ).get( 0 ) ); + String formattedDate = DateTimeFormatter.ofPattern( DATETIME_PATTERN ).format( eob.getTheLocalDateTime() ); + assertEquals( formattedDate, resultList.get( 0 ).get( 1 ) ); + } ); + } + + @Test + public void testExtractFunctions(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createTupleQuery(); + Root from = query.from( EntityOfBasics.class ); + + Expression theLocalDateTime = from.get( "theLocalDateTime" ); + query.multiselect( + from.get( "id" ), + cb.year( theLocalDateTime ), + cb.month( theLocalDateTime ), + cb.day( theLocalDateTime ), + cb.hour( theLocalDateTime ), + cb.minute( theLocalDateTime ) + ).where( cb.isNotNull( theLocalDateTime ) ); + + Tuple result = session.createQuery( query ).getSingleResult(); + EntityOfBasics eob = session.find( EntityOfBasics.class, result.get( 0 ) ); + assertEquals( eob.getTheLocalDateTime().getYear(), result.get( 1 ) ); + assertEquals( eob.getTheLocalDateTime().getMonth().getValue(), result.get( 2 ) ); + assertEquals( eob.getTheLocalDateTime().getDayOfMonth(), result.get( 3 ) ); + assertEquals( eob.getTheLocalDateTime().getHour(), result.get( 4 ) ); + assertEquals( eob.getTheLocalDateTime().getMinute(), result.get( 5 ) ); + } ); + } + + @Test + public void testOverlay(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createTupleQuery(); + Root from = query.from( EntityOfBasics.class ); + + Expression theString = from.get( "theString" ); + query.multiselect( + cb.overlay( theString, "33", 6 ), + cb.overlay( theString, from.get( "theInt" ).as( String.class ), 6 ), + cb.overlay( theString, "1234", from.get( "theInteger" ), 2 ) + ).where( cb.equal( from.get( "id" ), 4 ) ); + + Tuple result = session.createQuery( query ).getSingleResult(); + assertEquals( "thirt33n", result.get( 0 ) ); + assertEquals( "thirt13n", result.get( 1 ) ); + assertEquals( "thi1234een", result.get( 2 ) ); + } ); + } + + @Test + public void testPad(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createTupleQuery(); + Root from = query.from( EntityOfBasics.class ); + + Expression theString = from.get( "theString" ); + query.multiselect( + cb.pad( theString, 5 ), + cb.pad( CriteriaBuilder.Trimspec.TRAILING, theString, from.get( "theInt" ) ), + cb.pad( CriteriaBuilder.Trimspec.LEADING, theString, 3, '#' ) + ).where( cb.equal( from.get( "id" ), 1 ) ); + + Tuple result = session.createQuery( query ).getSingleResult(); + assertEquals( "5 ", result.get( 0 ) ); + assertEquals( "5 ", result.get( 1 ) ); + assertEquals( "##5", result.get( 2 ) ); + } ); + } + + @Test + public void testLeftRight(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createTupleQuery(); + Root from = query.from( EntityOfBasics.class ); + + Expression theString = from.get( "theString" ); + query.multiselect( + cb.left( theString, 3 ), + cb.right( theString, from.get( "theInteger" ) ) + ).where( cb.equal( from.get( "id" ), 4 ) ); + + Tuple result = session.createQuery( query ).getSingleResult(); + assertEquals( "thi", result.get( 0 ) ); + assertEquals( "teen", result.get( 1 ) ); + } ); + } + + @Test + public void testReplace(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createTupleQuery(); + Root from = query.from( EntityOfBasics.class ); + + Expression theString = from.get( "theString" ); + query.multiselect( + cb.replace( theString, "thi", "12345" ), + cb.replace( theString, "t", from.get( "theInteger" ).as( String.class ) ) + ).where( cb.equal( from.get( "id" ), 4 ) ); + + Tuple result = session.createQuery( query ).getSingleResult(); + assertEquals( "12345rteen", result.get( 0 ) ); + assertEquals( "4hir4een", result.get( 1 ) ); + } ); + } + + @Test + @RequiresDialect(PostgreSQLDialect.class) + public void testCollatePostgreSQL(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + + CriteriaQuery query = cb.createQuery( String.class ); + Root from = query.from( EntityOfBasics.class ); + Expression theString = from.get( "theString" ); + query.select( theString ) + .where( cb.isNotNull( theString ) ) + .orderBy( cb.asc( cb.collate( theString, "ucs_basic" ) ) ); + assertNotNull( session.createQuery( query ).getResultList() ); + + CriteriaQuery query2 = cb.createQuery( Boolean.class ); + query2.select( cb.lessThan( cb.collate( + cb.literal( "bar" ), + "ucs_basic" + ), cb.literal( "foo" ) ) ); + assertTrue( session.createQuery( query2 ).getSingleResult() ); + } ); + } + + @Test + public void testLog(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createTupleQuery(); + Root from = query.from( EntityOfBasics.class ); + + query.multiselect( + cb.log10( from.get( "theInt" ) ), + cb.log( 10, from.get( "theInt" ) ) + ).where( cb.equal( from.get( "id" ), 1 ) ); + + Tuple result = session.createQuery( query ).getSingleResult(); + assertEquals( 0.698970, result.get( 0, Double.class ), 1e-6 ); + assertEquals( 0.698970, result.get( 1, Double.class ), 1e-6 ); + } ); + } + + @Test + public void testPi(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery( Double.class ).select( cb.pi() ); + assertEquals( 3.141592, session.createQuery( query ).getSingleResult(), 1e-6 ); + } ); + } + + @Test + public void testTrigonometricFunctions(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createTupleQuery(); + Root from = query.from( EntityOfBasics.class ); + + Path theDouble = from.get( "theDouble" ); + query.multiselect( + cb.sin( theDouble ), + cb.cos( theDouble ), + cb.tan( theDouble ), + cb.asin( theDouble ), + cb.acos( theDouble ), + cb.atan( theDouble ), + cb.atan2( cb.sin( theDouble ), 0 ), + cb.atan2( cb.sin( theDouble ), cb.cos( theDouble ) ) + ).where( cb.equal( from.get( "id" ), 1 ) ); + + Tuple result = session.createQuery( query ).getSingleResult(); + assertEquals( Math.sin( 1.0 ), result.get( 0, Double.class ), 1e-6 ); + assertEquals( Math.cos( 1.0 ), result.get( 1, Double.class ), 1e-6 ); + assertEquals( Math.tan( 1.0 ), result.get( 2, Double.class ), 1e-6 ); + assertEquals( Math.asin( 1.0 ), result.get( 3, Double.class ), 1e-6 ); + assertEquals( Math.acos( 1.0 ), result.get( 4, Double.class ), 1e-6 ); + assertEquals( Math.atan( 1.0 ), result.get( 5, Double.class ), 1e-6 ); + assertEquals( Math.atan2( Math.sin( 1.0 ), 0 ), result.get( 6, Double.class ), 1e-6 ); + assertEquals( Math.atan2( Math.sin( 1.0 ), Math.cos( 1.0 ) ), result.get( 7, Double.class ), 1e-6 ); + } ); + } + + @Test + public void testHyperbolic(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createTupleQuery(); + Root from = query.from( EntityOfBasics.class ); + + Path theDouble = from.get( "theDouble" ); + query.multiselect( + cb.sinh( theDouble ), + cb.cosh( theDouble ), + cb.tanh( theDouble ) + ).where( cb.equal( from.get( "id" ), 1 ) ); + + Tuple result = session.createQuery( query ).getSingleResult(); + assertEquals( Math.sinh( 1.0 ), result.get( 0, Double.class ), 1e-6 ); + assertEquals( Math.cosh( 1.0 ), result.get( 1, Double.class ), 1e-6 ); + assertEquals( Math.tanh( 1.0 ), result.get( 2, Double.class ), 1e-6 ); + } ); + } + + @Test + public void testDegrees(SessionFactoryScope scope) { + scope.inTransaction( session -> { + HibernateCriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createTupleQuery(); + query.multiselect( cb.degrees( cb.pi() ), cb.radians( cb.literal( 180.0 ) ) ); + Tuple result = session.createQuery( query ).getSingleResult(); + assertEquals( 180.0, result.get( 0, Double.class ), 1e-9 ); + assertEquals( Math.PI, result.get( 1, Double.class ), 1e-9 ); + } ); + } +}