HHH-16048 add 'hibernate.query.hql.portable_integer_division'

consistent interpretation of division on MySQL and Oracle
This commit is contained in:
Gavin King 2024-02-15 15:20:34 +01:00
parent a97b7ba611
commit d7fe31643a
22 changed files with 299 additions and 247 deletions

View File

@ -129,6 +129,7 @@ import static org.hibernate.cfg.AvailableSettings.USE_SQL_COMMENTS;
import static org.hibernate.cfg.AvailableSettings.USE_STRUCTURED_CACHE;
import static org.hibernate.cfg.AvailableSettings.USE_SUBSELECT_FETCH;
import static org.hibernate.cfg.CacheSettings.QUERY_CACHE_LAYOUT;
import static org.hibernate.cfg.QuerySettings.PORTABLE_INTEGER_DIVISION;
import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN;
import static org.hibernate.internal.CoreLogging.messageLogger;
import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER;
@ -270,6 +271,8 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
private final boolean failOnPaginationOverCollectionFetchEnabled;
private final boolean inClauseParameterPaddingEnabled;
private final boolean portableIntegerDivisionEnabled;
private final int queryStatisticsMaxSize;
@ -596,6 +599,11 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
configurationSettings
);
this.portableIntegerDivisionEnabled = getBoolean(
PORTABLE_INTEGER_DIVISION,
configurationSettings
);
this.queryStatisticsMaxSize = getInt(
QUERY_STATISTICS_MAX_SIZE,
configurationSettings,
@ -1213,6 +1221,11 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
return this.inClauseParameterPaddingEnabled;
}
@Override
public boolean isPortableIntegerDivisionEnabled() {
return portableIntegerDivisionEnabled;
}
@Override
public JpaCompliance getJpaCompliance() {
return jpaCompliance;

View File

@ -428,6 +428,11 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
return delegate.inClauseParameterPaddingEnabled();
}
@Override
public boolean isPortableIntegerDivisionEnabled() {
return delegate.isPortableIntegerDivisionEnabled();
}
@Override
public int getQueryStatisticsMaxSize() {
return delegate.getQueryStatisticsMaxSize();

View File

@ -272,10 +272,21 @@ public interface SessionFactoryOptions extends QueryEngineOptions {
return null;
}
/**
* @see org.hibernate.cfg.AvailableSettings#IN_CLAUSE_PARAMETER_PADDING
*/
default boolean inClauseParameterPaddingEnabled() {
return false;
}
/**
* @see org.hibernate.cfg.AvailableSettings#PORTABLE_INTEGER_DIVISION
*/
@Override
default boolean isPortableIntegerDivisionEnabled() {
return false;
}
default int getQueryStatisticsMaxSize() {
return Statistics.DEFAULT_QUERY_STATISTICS_MAX_SIZE;
}

View File

@ -18,6 +18,14 @@ import jakarta.persistence.criteria.CriteriaUpdate;
* @author Steve Ebersole
*/
public interface QuerySettings {
/**
* Specifies that division of two integers should produce an integer on all
* databases. By default, integer division in HQL can produce a non-integer
* on Oracle, MySQL, or MariaDB.
*
* @since 6.5
*/
String PORTABLE_INTEGER_DIVISION = "hibernate.query.hql.portable_integer_division";
/**
* Specifies a {@link org.hibernate.query.hql.HqlTranslator} to use for HQL query
* translation.

View File

@ -17,6 +17,7 @@ import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.tree.MutationStatement;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
@ -51,6 +52,20 @@ public class MariaDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSq
this.dialect = (MariaDBDialect) DialectDelegateWrapper.extractRealDialect( super.getDialect() );
}
@Override
public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) {
if ( isIntegerDivisionEmulationRequired( arithmeticExpression ) ) {
appendSql( OPEN_PARENTHESIS );
arithmeticExpression.getLeftHandOperand().accept( this );
appendSql( " div " );
arithmeticExpression.getRightHandOperand().accept( this );
appendSql( CLOSE_PARENTHESIS );
}
else {
super.visitBinaryArithmeticExpression(arithmeticExpression);
}
}
@Override
protected void visitInsertSource(InsertSelectStatement statement) {
if ( statement.getSourceSelectStatement() != null ) {

View File

@ -19,6 +19,7 @@ import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.tree.MutationStatement;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
@ -101,6 +102,20 @@ public class MySQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
return sqlType;
}
@Override
public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) {
if ( isIntegerDivisionEmulationRequired( arithmeticExpression ) ) {
appendSql( OPEN_PARENTHESIS );
arithmeticExpression.getLeftHandOperand().accept( this );
appendSql( " div " );
arithmeticExpression.getRightHandOperand().accept( this );
appendSql( CLOSE_PARENTHESIS );
}
else {
super.visitBinaryArithmeticExpression(arithmeticExpression);
}
}
@Override
protected void visitInsertSource(InsertSelectStatement statement) {
if ( statement.getSourceSelectStatement() != null ) {

View File

@ -23,6 +23,7 @@ import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.FunctionExpression;
@ -587,6 +588,14 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends SqlAstTrans
}
}
@Override
public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) {
if ( isIntegerDivisionEmulationRequired( arithmeticExpression ) ) {
appendSql( "floor" );
}
super.visitBinaryArithmeticExpression(arithmeticExpression);
}
@Override
protected boolean supportsDuplicateSelectItemsInQueryGroup() {
return false;

View File

@ -17,6 +17,12 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
import org.hibernate.query.sqm.CastType;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.model.MutationOperation;
import org.hibernate.sql.model.internal.OptionalTableUpdate;
import org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation;
@ -114,4 +120,23 @@ public class PostgresPlusDialect extends PostgreSQLDialect {
// https://www.enterprisedb.com/docs/migrating/oracle/oracle_epas_comparison/notable_differences/
return new OptionalTableUpdateOperation( mutationTarget, optionalTableUpdate, factory );
}
@Override
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
return new StandardSqlAstTranslatorFactory() {
@Override
protected <T extends JdbcOperation> SqlAstTranslator<T> buildTranslator(
SessionFactoryImplementor sessionFactory, Statement statement) {
return new PostgreSQLSqlAstTranslator<>( sessionFactory, statement ) {
@Override
public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) {
if ( isIntegerDivisionEmulationRequired( arithmeticExpression ) ) {
appendSql( "floor" );
}
super.visitBinaryArithmeticExpression(arithmeticExpression);
}
};
}
};
}
}

View File

@ -29,7 +29,6 @@ import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.ExtractUnit;
import org.hibernate.type.BasicType;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.usertype.internal.AbstractTimeZoneStorageCompositeUserType;
import java.time.ZoneOffset;
import java.util.Collections;
@ -40,6 +39,7 @@ import static org.hibernate.query.sqm.BinaryArithmeticOperator.*;
import static org.hibernate.query.sqm.TemporalUnit.*;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL_UNIT;
import static org.hibernate.usertype.internal.AbstractTimeZoneStorageCompositeUserType.ZONE_OFFSET_NAME;
/**
* ANSI SQL-inspired {@code extract()} function, where the date/time fields
@ -96,7 +96,7 @@ public class ExtractFunction extends AbstractSqmFunctionDescriptor implements Fu
case OFFSET:
if ( compositeTemporal ) {
final SqmPath<Object> offsetPath = ( (SqmPath<?>) originalExpression ).get(
AbstractTimeZoneStorageCompositeUserType.ZONE_OFFSET_NAME
ZONE_OFFSET_NAME
);
return new SelfRenderingSqmFunction<>(
this,

View File

@ -15,8 +15,6 @@ import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.query.sqm.function.AbstractSqmFunctionDescriptor;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
@ -29,9 +27,6 @@ import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver;
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.sql.ast.SqlAstTranslator;
@ -40,7 +35,6 @@ import org.hibernate.sql.ast.spi.StringBuilderSqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.DurationUnit;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Format;
@ -54,8 +48,16 @@ import org.hibernate.type.SqlTypes;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.query.sqm.BinaryArithmeticOperator.DIVIDE_PORTABLE;
import static org.hibernate.query.sqm.BinaryArithmeticOperator.MODULO;
import static org.hibernate.query.sqm.ComparisonOperator.GREATER_THAN_OR_EQUAL;
import static org.hibernate.query.sqm.ComparisonOperator.LESS_THAN;
import static org.hibernate.query.sqm.ComparisonOperator.LESS_THAN_OR_EQUAL;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL;
import static org.hibernate.query.sqm.produce.function.StandardArgumentsValidators.exactly;
import static org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers.invariant;
import static org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers.invariant;
/**
* A format function with support for composite temporal expressions.
@ -89,10 +91,9 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
TypeConfiguration typeConfiguration) {
super(
"format",
new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 2 ), TEMPORAL, STRING ),
StandardFunctionReturnTypeResolvers.invariant( typeConfiguration.getBasicTypeRegistry().resolve(
StandardBasicTypes.STRING ) ),
StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, TEMPORAL, STRING )
new ArgumentTypesValidator( exactly( 2 ), TEMPORAL, STRING ),
invariant( typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.STRING ) ),
invariant( typeConfiguration, TEMPORAL, STRING )
);
this.nativeFunctionName = nativeFunctionName;
this.reversedArguments = reversedArguments;
@ -260,8 +261,6 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
"substring",
3
);
final AbstractSqmSelfRenderingFunctionDescriptor floorFunction = getFunction( walker, "floor" );
final AbstractSqmSelfRenderingFunctionDescriptor castFunction = getFunction( walker, "cast" );
final BasicType<String> stringType = typeConfiguration.getBasicTypeRegistry()
.resolve( StandardBasicTypes.STRING );
final Dialect dialect = walker.getCreationContext()
@ -344,8 +343,6 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
createSmallOffset(
concatFunction,
substringFunction,
floorFunction,
castFunction,
stringType,
integerType,
offsetExpression
@ -365,8 +362,6 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
createMediumOffset(
concatFunction,
substringFunction,
floorFunction,
castFunction,
stringType,
integerType,
offsetExpression
@ -382,8 +377,6 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
formatExpression,
createFullOffset(
concatFunction,
floorFunction,
castFunction,
stringType,
integerType,
offsetExpression
@ -491,7 +484,8 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
.getSqmFunctionRegistry()
.findFunctionDescriptor( name );
if ( functionDescriptor instanceof MultipatternSqmFunctionDescriptor ) {
return (AbstractSqmSelfRenderingFunctionDescriptor) ( (MultipatternSqmFunctionDescriptor) functionDescriptor )
return (AbstractSqmSelfRenderingFunctionDescriptor)
( (MultipatternSqmFunctionDescriptor) functionDescriptor )
.getFunction( argumentCount );
}
return (AbstractSqmSelfRenderingFunctionDescriptor) functionDescriptor;
@ -519,8 +513,6 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
private Expression createFullOffset(
AbstractSqmSelfRenderingFunctionDescriptor concatFunction,
AbstractSqmSelfRenderingFunctionDescriptor floorFunction,
AbstractSqmSelfRenderingFunctionDescriptor castFunction,
BasicType<String> stringType,
BasicType<Integer> integerType,
Expression offsetExpression) {
@ -529,63 +521,18 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
}
else {
// ZoneOffset as seconds
final CaseSearchedExpression caseSearchedExpression = new CaseSearchedExpression( stringType );
caseSearchedExpression.getWhenFragments().add(
new CaseSearchedExpression.WhenFragment(
new ComparisonPredicate(
offsetExpression,
ComparisonOperator.LESS_THAN_OR_EQUAL,
new QueryLiteral<>(
-36000,
integerType
)
),
new QueryLiteral<>( "-", stringType )
)
);
caseSearchedExpression.getWhenFragments().add(
new CaseSearchedExpression.WhenFragment(
new ComparisonPredicate(
offsetExpression,
ComparisonOperator.LESS_THAN,
new QueryLiteral<>(
0,
integerType
)
),
new QueryLiteral<>( "-0", stringType )
)
);
caseSearchedExpression.getWhenFragments().add(
new CaseSearchedExpression.WhenFragment(
new ComparisonPredicate(
offsetExpression,
ComparisonOperator.GREATER_THAN_OR_EQUAL,
new QueryLiteral<>(
36000,
integerType
)
),
new QueryLiteral<>( "+", stringType )
)
);
caseSearchedExpression.otherwise( new QueryLiteral<>( "+0", stringType ) );
final Expression hours = getHours( floorFunction, castFunction, integerType, offsetExpression );
final Expression minutes = getMinutes( floorFunction, castFunction, integerType, offsetExpression );
final CaseSearchedExpression caseSearchedExpression =
zoneOffsetSeconds( stringType, integerType, offsetExpression );
final Expression hours = getHours( integerType, offsetExpression );
final Expression minutes = getMinutes( integerType, offsetExpression );
final CaseSearchedExpression minuteStart = new CaseSearchedExpression( stringType );
minuteStart.getWhenFragments().add(
new CaseSearchedExpression.WhenFragment(
new BetweenPredicate(
minutes,
new QueryLiteral<>(
-9,
integerType
),
new QueryLiteral<>(
9,
integerType
),
new QueryLiteral<>( -9, integerType ),
new QueryLiteral<>( 9, integerType ),
false,
null
),
@ -610,8 +557,6 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
private Expression createMediumOffset(
AbstractSqmSelfRenderingFunctionDescriptor concatFunction,
AbstractSqmSelfRenderingFunctionDescriptor substringFunction,
AbstractSqmSelfRenderingFunctionDescriptor floorFunction,
AbstractSqmSelfRenderingFunctionDescriptor castFunction,
BasicType<String> stringType,
BasicType<Integer> integerType,
Expression offsetExpression) {
@ -622,8 +567,6 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
createSmallOffset(
concatFunction,
substringFunction,
floorFunction,
castFunction,
stringType,
integerType,
offsetExpression
@ -643,64 +586,19 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
}
else {
// ZoneOffset as seconds
final CaseSearchedExpression caseSearchedExpression = new CaseSearchedExpression( stringType );
caseSearchedExpression.getWhenFragments().add(
new CaseSearchedExpression.WhenFragment(
new ComparisonPredicate(
offsetExpression,
ComparisonOperator.LESS_THAN_OR_EQUAL,
new QueryLiteral<>(
-36000,
integerType
)
),
new QueryLiteral<>( "-", stringType )
)
);
caseSearchedExpression.getWhenFragments().add(
new CaseSearchedExpression.WhenFragment(
new ComparisonPredicate(
offsetExpression,
ComparisonOperator.LESS_THAN,
new QueryLiteral<>(
0,
integerType
)
),
new QueryLiteral<>( "-0", stringType )
)
);
caseSearchedExpression.getWhenFragments().add(
new CaseSearchedExpression.WhenFragment(
new ComparisonPredicate(
offsetExpression,
ComparisonOperator.GREATER_THAN_OR_EQUAL,
new QueryLiteral<>(
36000,
integerType
)
),
new QueryLiteral<>( "+", stringType )
)
);
caseSearchedExpression.otherwise( new QueryLiteral<>( "+0", stringType ) );
final CaseSearchedExpression caseSearchedExpression =
zoneOffsetSeconds( stringType, integerType, offsetExpression );
final Expression hours = getHours( floorFunction, castFunction, integerType, offsetExpression );
final Expression minutes = getMinutes( floorFunction, castFunction, integerType, offsetExpression );
final Expression hours = getHours( integerType, offsetExpression );
final Expression minutes = getMinutes( integerType, offsetExpression );
final CaseSearchedExpression minuteStart = new CaseSearchedExpression( stringType );
minuteStart.getWhenFragments().add(
new CaseSearchedExpression.WhenFragment(
new BetweenPredicate(
minutes,
new QueryLiteral<>(
-9,
integerType
),
new QueryLiteral<>(
9,
integerType
),
new QueryLiteral<>( -9, integerType ),
new QueryLiteral<>( 9, integerType ),
false,
null
),
@ -725,8 +623,6 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
private Expression createSmallOffset(
AbstractSqmSelfRenderingFunctionDescriptor concatFunction,
AbstractSqmSelfRenderingFunctionDescriptor substringFunction,
AbstractSqmSelfRenderingFunctionDescriptor floorFunction,
AbstractSqmSelfRenderingFunctionDescriptor castFunction,
BasicType<String> stringType,
BasicType<Integer> integerType,
Expression offsetExpression) {
@ -745,48 +641,9 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
}
else {
// ZoneOffset as seconds
final CaseSearchedExpression caseSearchedExpression = new CaseSearchedExpression( stringType );
caseSearchedExpression.getWhenFragments().add(
new CaseSearchedExpression.WhenFragment(
new ComparisonPredicate(
offsetExpression,
ComparisonOperator.LESS_THAN_OR_EQUAL,
new QueryLiteral<>(
-36000,
integerType
)
),
new QueryLiteral<>( "-", stringType )
)
);
caseSearchedExpression.getWhenFragments().add(
new CaseSearchedExpression.WhenFragment(
new ComparisonPredicate(
offsetExpression,
ComparisonOperator.LESS_THAN,
new QueryLiteral<>(
0,
integerType
)
),
new QueryLiteral<>( "-0", stringType )
)
);
caseSearchedExpression.getWhenFragments().add(
new CaseSearchedExpression.WhenFragment(
new ComparisonPredicate(
offsetExpression,
ComparisonOperator.GREATER_THAN_OR_EQUAL,
new QueryLiteral<>(
36000,
integerType
)
),
new QueryLiteral<>( "+", stringType )
)
);
caseSearchedExpression.otherwise( new QueryLiteral<>( "+0", stringType ) );
final Expression hours = getHours( floorFunction, castFunction, integerType, offsetExpression );
final CaseSearchedExpression caseSearchedExpression =
zoneOffsetSeconds( stringType, integerType, offsetExpression );
final Expression hours = getHours( integerType, offsetExpression );
return concat( concatFunction, stringType, caseSearchedExpression, hours );
}
}
@ -844,7 +701,8 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
}
else if ( expression2 instanceof SelfRenderingFunctionSqlAstExpression
&& "concat".equals( ( (SelfRenderingFunctionSqlAstExpression) expression2 ).getFunctionName() ) ) {
List<SqlAstNode> list = (List<SqlAstNode>) ( (SelfRenderingFunctionSqlAstExpression) expression2 ).getArguments();
final List<SqlAstNode> list = (List<SqlAstNode>)
( (SelfRenderingFunctionSqlAstExpression) expression2 ).getArguments();
final SqlAstNode firstOperand = list.get( 0 );
if ( expression instanceof QueryLiteral<?> && firstOperand instanceof QueryLiteral<?> ) {
list.set(
@ -883,68 +741,84 @@ public class FormatFunction extends AbstractSqmFunctionDescriptor implements Fun
}
private Expression getHours(
AbstractSqmSelfRenderingFunctionDescriptor floorFunction,
AbstractSqmSelfRenderingFunctionDescriptor castFunction,
BasicType<Integer> integerType,
Expression offsetExpression) {
return new SelfRenderingFunctionSqlAstExpression(
return /*new SelfRenderingFunctionSqlAstExpression(
"cast",
castFunction,
List.of(
new SelfRenderingFunctionSqlAstExpression(
"floor",
floorFunction,
List.of(
List.of(*/
new BinaryArithmeticExpression(
offsetExpression,
BinaryArithmeticOperator.DIVIDE,
DIVIDE_PORTABLE,
new QueryLiteral<>( 3600, integerType ),
integerType
)
),
integerType,
integerType
),
)/*,
new CastTarget( integerType )
),
integerType,
integerType
);
)*/;
}
private Expression getMinutes(
AbstractSqmSelfRenderingFunctionDescriptor floorFunction,
AbstractSqmSelfRenderingFunctionDescriptor castFunction,
BasicType<Integer> integerType,
Expression offsetExpression){
return new SelfRenderingFunctionSqlAstExpression(
return /*new SelfRenderingFunctionSqlAstExpression(
"cast",
castFunction,
List.of(
new SelfRenderingFunctionSqlAstExpression(
"floor",
floorFunction,
List.of(
List.of(*/
new BinaryArithmeticExpression(
new BinaryArithmeticExpression(
offsetExpression,
BinaryArithmeticOperator.MODULO,
MODULO,
new QueryLiteral<>( 3600, integerType ),
integerType
),
BinaryArithmeticOperator.DIVIDE,
DIVIDE_PORTABLE,
new QueryLiteral<>( 60, integerType ),
integerType
)
),
integerType,
integerType
),
)/*,
new CastTarget( integerType )
),
integerType,
integerType
)*/;
}
}
private static CaseSearchedExpression zoneOffsetSeconds(BasicType<String> stringType, BasicType<Integer> integerType, Expression offsetExpression) {
final CaseSearchedExpression caseSearchedExpression = new CaseSearchedExpression(stringType);
caseSearchedExpression.getWhenFragments().add(
new CaseSearchedExpression.WhenFragment(
new ComparisonPredicate(
offsetExpression,
LESS_THAN_OR_EQUAL,
new QueryLiteral<>( -36000, integerType)
),
new QueryLiteral<>( "-", stringType)
)
);
}
caseSearchedExpression.getWhenFragments().add(
new CaseSearchedExpression.WhenFragment(
new ComparisonPredicate(
offsetExpression,
LESS_THAN,
new QueryLiteral<>( 0, integerType)
),
new QueryLiteral<>( "-0", stringType)
)
);
caseSearchedExpression.getWhenFragments().add(
new CaseSearchedExpression.WhenFragment(
new ComparisonPredicate(
offsetExpression,
GREATER_THAN_OR_EQUAL,
new QueryLiteral<>( 36000, integerType)
),
new QueryLiteral<>( "+", stringType)
)
);
caseSearchedExpression.otherwise( new QueryLiteral<>( "+0", stringType) );
return caseSearchedExpression;
}
}

View File

@ -9,7 +9,6 @@ package org.hibernate.dialect.function;
import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
import org.hibernate.sql.ast.SqlAstTranslator;
@ -28,6 +27,9 @@ import org.hibernate.type.spi.TypeConfiguration;
import java.util.Arrays;
import java.util.List;
import static org.hibernate.query.sqm.BinaryArithmeticOperator.DIVIDE;
import static org.hibernate.query.sqm.BinaryArithmeticOperator.MULTIPLY;
/**
* Used in place of {@link TimestampaddFunction} for databases which don't
* support fractional seconds in the {@code timestampadd()} function.
@ -110,11 +112,10 @@ public class IntegralTimestampaddFunction
? magnitude
: new BinaryArithmeticExpression(
magnitude,
conversionFactor.charAt(0) == '*'
? BinaryArithmeticOperator.MULTIPLY
: BinaryArithmeticOperator.DIVIDE,
conversionFactor.charAt(0) == '*' ? MULTIPLY : DIVIDE,
new QueryLiteral<>(
expressionType.getExpressibleJavaType().fromString( conversionFactor.substring(1) ),
expressionType.getExpressibleJavaType()
.fromString( conversionFactor.substring(1) ),
expressionType
),
expressionType

View File

@ -888,7 +888,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
private void applyCycleClause(JpaCteCriteria<?> cteDefinition, HqlParser.CycleClauseContext ctx) {
final HqlParser.CteAttributesContext attributesContext = (HqlParser.CteAttributesContext) ctx.getChild( 1 );
final HqlParser.CteAttributesContext attributesContext = ctx.cteAttributes();
final String cycleMarkAttributeName = visitIdentifier( (HqlParser.IdentifierContext) ctx.getChild( 3 ) );
final List<JpaCteCriteriaAttribute> cycleAttributes = new ArrayList<>( ( attributesContext.getChildCount() + 1 ) >> 1 );
final List<ParseTree> children = attributesContext.children;
@ -948,14 +948,14 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
else {
kind = CteSearchClauseKind.DEPTH_FIRST;
}
final String searchAttributeName = visitIdentifier( (HqlParser.IdentifierContext) ctx.getChild( ctx.getChildCount() - 1 ) );
final HqlParser.SearchSpecificationsContext searchCtx = (HqlParser.SearchSpecificationsContext) ctx.getChild( 4 );
final String searchAttributeName = visitIdentifier( ctx.identifier() );
final HqlParser.SearchSpecificationsContext searchCtx = ctx.searchSpecifications();
final List<JpaSearchOrder> searchOrders = new ArrayList<>( ( searchCtx.getChildCount() + 1 ) >> 1 );
final List<ParseTree> children = searchCtx.children;
final JpaCteCriteriaType<?> type = cteDefinition.getType();
for ( int i = 0; i < children.size(); i += 2 ) {
final HqlParser.SearchSpecificationContext specCtx = (HqlParser.SearchSpecificationContext) children.get( i );
final String attributeName = visitIdentifier( (HqlParser.IdentifierContext) specCtx.getChild( 0 ) );
final String attributeName = visitIdentifier( specCtx.identifier() );
final JpaCteCriteriaAttribute attribute = type.getAttribute( attributeName );
if ( attribute == null ) {
throw new SemanticException(
@ -972,7 +972,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
int index = 1;
if ( index < specCtx.getChildCount() ) {
if ( specCtx.getChild( index ) instanceof HqlParser.SortDirectionContext ) {
final HqlParser.SortDirectionContext sortCtx = (HqlParser.SortDirectionContext) specCtx.getChild( index );
final HqlParser.SortDirectionContext sortCtx = specCtx.sortDirection();
switch ( ( (TerminalNode) sortCtx.getChild( 0 ) ).getSymbol().getType() ) {
case HqlParser.ASC:
sortOrder = SortDirection.ASCENDING;
@ -986,7 +986,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
index++;
}
if ( index < specCtx.getChildCount() ) {
final HqlParser.NullsPrecedenceContext nullsPrecedenceContext = (HqlParser.NullsPrecedenceContext) specCtx.getChild( index );
final HqlParser.NullsPrecedenceContext nullsPrecedenceContext = specCtx.nullsPrecedence();
switch ( ( (TerminalNode) nullsPrecedenceContext.getChild( 1 ) ).getSymbol().getType() ) {
case HqlParser.FIRST:
nullPrecedence = NullPrecedence.FIRST;
@ -999,13 +999,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
}
}
searchOrders.add(
creationContext.getNodeBuilder().search(
attribute,
sortOrder,
nullPrecedence
)
);
searchOrders.add( creationContext.getNodeBuilder().search( attribute, sortOrder, nullPrecedence ) );
}
cteDefinition.search( kind, searchAttributeName, searchOrders );
}
@ -3050,7 +3044,9 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
case HqlParser.ASTERISK:
return BinaryArithmeticOperator.MULTIPLY;
case HqlParser.SLASH:
return BinaryArithmeticOperator.DIVIDE;
return this.creationOptions.isPortableIntegerDivisionEnabled()
? BinaryArithmeticOperator.DIVIDE_PORTABLE
: BinaryArithmeticOperator.DIVIDE;
case HqlParser.PERCENT_OP:
return BinaryArithmeticOperator.MODULO;
default:

View File

@ -22,5 +22,14 @@ public interface SqmCreationOptions {
*
* @see StrictJpaComplianceViolation
*/
boolean useStrictJpaCompliance();
default boolean useStrictJpaCompliance() {
return false;
}
/**
* @see org.hibernate.cfg.AvailableSettings#PORTABLE_INTEGER_DIVISION
*/
default boolean isPortableIntegerDivisionEnabled() {
return false;
}
}

View File

@ -172,7 +172,7 @@ public class QueryEngineImpl implements QueryEngine {
private static List<FunctionContributor> sortedFunctionContributors(ServiceRegistry serviceRegistry) {
Collection<FunctionContributor> functionContributors =
serviceRegistry.getService(ClassLoaderService.class)
serviceRegistry.requireService(ClassLoaderService.class)
.loadJavaServices(FunctionContributor.class);
List<FunctionContributor> contributors = new ArrayList<>( functionContributors );
contributors.sort(

View File

@ -72,4 +72,10 @@ public interface QueryEngineOptions {
JpaCompliance getJpaCompliance();
ValueHandlingMode getCriteriaValueHandlingMode();
/**
* @see org.hibernate.cfg.AvailableSettings#PORTABLE_INTEGER_DIVISION
*/
boolean isPortableIntegerDivisionEnabled();
}

View File

@ -85,6 +85,25 @@ public enum BinaryArithmeticOperator {
}
},
/**
* "Portable" division, that is, true integer division when the
* operands are integers.
*
* @see org.hibernate.cfg.AvailableSettings#PORTABLE_INTEGER_DIVISION
* @see org.hibernate.query.spi.QueryEngineOptions#isPortableIntegerDivisionEnabled()
*/
DIVIDE_PORTABLE {
@Override
public String toLoggableText(String lhs, String rhs) {
return standardToLoggableText( lhs, this, rhs );
}
@Override
public char getOperatorSqlText() {
return '/';
}
},
;
public abstract String toLoggableText(String lhs, String rhs);

View File

@ -23,4 +23,9 @@ public class SqmCreationOptionsStandard implements SqmCreationOptions {
public boolean useStrictJpaCompliance() {
return queryEngineOptions.getJpaCompliance().isJpaQueryComplianceEnabled();
}
@Override
public boolean isPortableIntegerDivisionEnabled() {
return queryEngineOptions.isPortableIntegerDivisionEnabled();
}
}

View File

@ -50,7 +50,6 @@ import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
@ -64,7 +63,6 @@ import org.hibernate.persister.internal.SqlFragmentPredicate;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.SortDirection;
import org.hibernate.query.results.TableGroupImpl;
import org.hibernate.query.spi.Limit;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
@ -81,7 +79,6 @@ import org.hibernate.query.sqm.function.MultipatternSqmFunctionDescriptor;
import org.hibernate.query.sqm.function.SelfRenderingAggregateFunctionSqlAstExpression;
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.query.sqm.sql.internal.EntityValuedPathInterpretation;
import org.hibernate.query.sqm.sql.internal.SqmParameterInterpretation;
import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation;
import org.hibernate.query.sqm.tree.expression.Conversion;
@ -227,6 +224,7 @@ import org.hibernate.type.descriptor.sql.DdlType;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.query.sqm.BinaryArithmeticOperator.DIVIDE_PORTABLE;
import static org.hibernate.query.sqm.TemporalUnit.NANOSECOND;
import static org.hibernate.sql.ast.SqlTreePrinter.logSqlAst;
import static org.hibernate.sql.results.graph.DomainResultGraphPrinter.logDomainResultGraph;
@ -1318,6 +1316,16 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
visitReturningColumns( statement.getReturningColumns() );
}
protected boolean isIntegerDivisionEmulationRequired(BinaryArithmeticExpression expression) {
return expression.getOperator() == DIVIDE_PORTABLE
&& jdbcType( expression.getLeftHandOperand() ).isInteger()
&& jdbcType( expression.getRightHandOperand() ).isInteger();
}
private JdbcType jdbcType(Expression expression) {
return expression.getExpressionType().getSingleJdbcMapping().getJdbcType();
}
protected void visitInsertSource(InsertSelectStatement statement) {
if ( statement.getSourceSelectStatement() != null ) {
statement.getSourceSelectStatement().accept( this );
@ -7149,8 +7157,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
@Override
public void visitEntityTypeLiteral(EntityTypeLiteral expression) {
final EntityMappingType entityMapping = expression.getEntityTypeDescriptor();
appendSql( entityMapping.getDiscriminatorSQLValue() );
appendSql( expression.getEntityTypeDescriptor().getDiscriminatorSQLValue() );
}
@Override

View File

@ -61,9 +61,11 @@ import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.isOneOf;
import static org.hibernate.cfg.QuerySettings.PORTABLE_INTEGER_DIVISION;
import static org.hibernate.testing.orm.domain.gambit.EntityOfBasics.Gender.FEMALE;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertThrows;

View File

@ -0,0 +1,27 @@
package org.hibernate.orm.test.query.hql;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test;
import static org.hibernate.cfg.QuerySettings.PORTABLE_INTEGER_DIVISION;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@DomainModel
@SessionFactory
@ServiceRegistry(settings = @Setting(name = PORTABLE_INTEGER_DIVISION, value = "true"))
public class IntegerDivisionTest {
@Test
public void testIntegerDivision(SessionFactoryScope scope) {
scope.inTransaction(s -> {
assertFalse( s.createQuery("select 1 where 1/2 = 0 and 4/3 = 1", Integer.class)
.getResultList().isEmpty() );
assertEquals( 1, s.createQuery("select 4/3", Integer.class)
.getSingleResult() );
});
}
}

View File

@ -86,6 +86,7 @@ import org.hibernate.proxy.EntityNotFoundDelegate;
import org.hibernate.query.criteria.ValueHandlingMode;
import org.hibernate.query.hql.HqlTranslator;
import org.hibernate.query.hql.internal.StandardHqlTranslator;
import org.hibernate.query.hql.spi.SqmCreationOptions;
import org.hibernate.query.internal.NamedObjectRepositoryImpl;
import org.hibernate.query.internal.QueryInterpretationCacheDisabledImpl;
import org.hibernate.query.named.NamedObjectRepository;
@ -478,7 +479,7 @@ public abstract class MockSessionFactory
@Override
public HqlTranslator getHqlTranslator() {
return new StandardHqlTranslator(MockSessionFactory.this, () -> false);
return new StandardHqlTranslator(MockSessionFactory.this, new SqmCreationOptions() {});
}
@Override

View File

@ -19,6 +19,7 @@ import org.hibernate.grammars.hql.HqlLexer;
import org.hibernate.grammars.hql.HqlParser;
import org.hibernate.query.hql.internal.HqlParseTreeBuilder;
import org.hibernate.query.hql.internal.SemanticQueryBuilder;
import org.hibernate.query.hql.spi.SqmCreationOptions;
import org.hibernate.query.sqm.EntityTypeException;
import org.hibernate.query.sqm.PathElementException;
import org.hibernate.query.sqm.TerminalPathException;
@ -99,6 +100,9 @@ public class Validation {
return null;
}
private static final SqmCreationOptions CREATION_OPTIONS = new SqmCreationOptions() {
};
private static SemanticQueryBuilder<?> createSemanticQueryBuilder(
@Nullable TypeMirror returnType, String hql, SessionFactoryImplementor factory) {
if ( returnType != null && returnType.getKind() == TypeKind.DECLARED ) {
@ -107,11 +111,11 @@ public class Validation {
final String typeName = typeElement.getQualifiedName().toString();
final String shortName = typeElement.getSimpleName().toString();
return isEntity( typeElement )
? new SemanticQueryBuilder<>( typeName, shortName, getEntityName(typeElement), () -> false, factory, hql )
: new SemanticQueryBuilder<>( typeName, shortName, Object[].class, () -> false, factory, hql );
? new SemanticQueryBuilder<>( typeName, shortName, getEntityName(typeElement), CREATION_OPTIONS, factory, hql )
: new SemanticQueryBuilder<>( typeName, shortName, Object[].class, CREATION_OPTIONS, factory, hql );
}
else {
return new SemanticQueryBuilder<>( Object[].class, () -> false, factory, hql );
return new SemanticQueryBuilder<>( Object[].class, CREATION_OPTIONS, factory, hql );
}
}