HHH-15578 Add min/max emulation for uuid on PostgreSQL
This commit is contained in:
parent
b392f663c3
commit
5f2d5e3938
|
@ -39,6 +39,7 @@ import org.hibernate.dialect.RowLockStrategy;
|
|||
import org.hibernate.dialect.SelectItemReferenceStrategy;
|
||||
import org.hibernate.dialect.TimeZoneSupport;
|
||||
import org.hibernate.dialect.function.CommonFunctionFactory;
|
||||
import org.hibernate.dialect.function.PostgreSQLMinMaxFunction;
|
||||
import org.hibernate.dialect.identity.IdentityColumnSupport;
|
||||
import org.hibernate.dialect.identity.PostgreSQLIdentityColumnSupport;
|
||||
import org.hibernate.dialect.pagination.LimitHandler;
|
||||
|
@ -574,6 +575,60 @@ public class PostgreSQLLegacyDialect extends Dialect {
|
|||
functionFactory.inverseDistributionOrderedSetAggregates();
|
||||
functionFactory.hypotheticalOrderedSetAggregates();
|
||||
}
|
||||
|
||||
if ( !supportsMinMaxOnUuid() ) {
|
||||
queryEngine.getSqmFunctionRegistry().register( "min", new PostgreSQLMinMaxFunction( "min" ) );
|
||||
queryEngine.getSqmFunctionRegistry().register( "max", new PostgreSQLMinMaxFunction( "max" ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether PostgreSQL supports `min(uuid)`/`max(uuid)` which it doesn't by default.
|
||||
* Since the emulation is not very performant, this can be overridden by users which
|
||||
* make sure that an aggregate function for uuid exists on their database.
|
||||
*
|
||||
* The following definitions can be used for this purpose:
|
||||
*
|
||||
* <code>
|
||||
* create or replace function min(uuid, uuid)
|
||||
* returns uuid
|
||||
* immutable parallel safe
|
||||
* language plpgsql as
|
||||
* $$
|
||||
* begin
|
||||
* return least($1, $2);
|
||||
* end
|
||||
* $$;
|
||||
*
|
||||
* create aggregate min(uuid) (
|
||||
* sfunc = min,
|
||||
* stype = uuid,
|
||||
* combinefunc = min,
|
||||
* parallel = safe,
|
||||
* sortop = operator (<)
|
||||
* );
|
||||
*
|
||||
* create or replace function max(uuid, uuid)
|
||||
* returns uuid
|
||||
* immutable parallel safe
|
||||
* language plpgsql as
|
||||
* $$
|
||||
* begin
|
||||
* return greatest($1, $2);
|
||||
* end
|
||||
* $$;
|
||||
*
|
||||
* create aggregate max(uuid) (
|
||||
* sfunc = max,
|
||||
* stype = uuid,
|
||||
* combinefunc = max,
|
||||
* parallel = safe,
|
||||
* sortop = operator (>)
|
||||
* );
|
||||
* </code>
|
||||
*/
|
||||
protected boolean supportsMinMaxOnUuid() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.hibernate.PessimisticLockException;
|
|||
import org.hibernate.QueryTimeoutException;
|
||||
import org.hibernate.boot.model.TypeContributions;
|
||||
import org.hibernate.dialect.function.CommonFunctionFactory;
|
||||
import org.hibernate.dialect.function.PostgreSQLMinMaxFunction;
|
||||
import org.hibernate.dialect.identity.IdentityColumnSupport;
|
||||
import org.hibernate.dialect.identity.PostgreSQLIdentityColumnSupport;
|
||||
import org.hibernate.dialect.pagination.LimitHandler;
|
||||
|
@ -556,6 +557,60 @@ public class PostgreSQLDialect extends Dialect {
|
|||
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions
|
||||
functionFactory.inverseDistributionOrderedSetAggregates();
|
||||
functionFactory.hypotheticalOrderedSetAggregates();
|
||||
|
||||
if ( !supportsMinMaxOnUuid() ) {
|
||||
queryEngine.getSqmFunctionRegistry().register( "min", new PostgreSQLMinMaxFunction( "min" ) );
|
||||
queryEngine.getSqmFunctionRegistry().register( "max", new PostgreSQLMinMaxFunction( "max" ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether PostgreSQL supports `min(uuid)`/`max(uuid)` which it doesn't by default.
|
||||
* Since the emulation is not very performant, this can be overridden by users which
|
||||
* make sure that an aggregate function for uuid exists on their database.
|
||||
*
|
||||
* The following definitions can be used for this purpose:
|
||||
*
|
||||
* <code>
|
||||
* create or replace function min(uuid, uuid)
|
||||
* returns uuid
|
||||
* immutable parallel safe
|
||||
* language plpgsql as
|
||||
* $$
|
||||
* begin
|
||||
* return least($1, $2);
|
||||
* end
|
||||
* $$;
|
||||
*
|
||||
* create aggregate min(uuid) (
|
||||
* sfunc = min,
|
||||
* stype = uuid,
|
||||
* combinefunc = min,
|
||||
* parallel = safe,
|
||||
* sortop = operator (<)
|
||||
* );
|
||||
*
|
||||
* create or replace function max(uuid, uuid)
|
||||
* returns uuid
|
||||
* immutable parallel safe
|
||||
* language plpgsql as
|
||||
* $$
|
||||
* begin
|
||||
* return greatest($1, $2);
|
||||
* end
|
||||
* $$;
|
||||
*
|
||||
* create aggregate max(uuid) (
|
||||
* sfunc = max,
|
||||
* stype = uuid,
|
||||
* combinefunc = max,
|
||||
* parallel = safe,
|
||||
* sortop = operator (>)
|
||||
* );
|
||||
* </code>
|
||||
*/
|
||||
protected boolean supportsMinMaxOnUuid() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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.dialect.function;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.function.FunctionKind;
|
||||
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
|
||||
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.sql.ast.Clause;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.COMPARABLE;
|
||||
|
||||
/**
|
||||
* PostgreSQL doesn't support min/max for uuid yet,
|
||||
* but since that type is comparable we want to support this operation.
|
||||
* The workaround is to cast uuid to text and aggregate that, which preserves the ordering,
|
||||
* and finally cast the result back to uuid.
|
||||
*
|
||||
* @author Christian Beikov
|
||||
*/
|
||||
public class PostgreSQLMinMaxFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||
|
||||
public PostgreSQLMinMaxFunction(String name) {
|
||||
super(
|
||||
name,
|
||||
FunctionKind.AGGREGATE,
|
||||
new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 1 ), COMPARABLE ),
|
||||
StandardFunctionReturnTypeResolvers.useFirstNonNull(),
|
||||
StandardFunctionArgumentTypeResolvers.IMPLIED_RESULT_TYPE
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(SqlAppender sqlAppender, List<? extends SqlAstNode> sqlAstArguments, SqlAstTranslator<?> walker) {
|
||||
render( sqlAppender, sqlAstArguments, null, walker );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
Predicate filter,
|
||||
SqlAstTranslator<?> translator) {
|
||||
final boolean caseWrapper = filter != null && !translator.supportsFilterClause();
|
||||
sqlAppender.appendSql( getName() );
|
||||
sqlAppender.appendSql( '(' );
|
||||
final Expression arg = (Expression) sqlAstArguments.get( 0 );
|
||||
final String castTarget;
|
||||
if ( caseWrapper ) {
|
||||
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||
sqlAppender.appendSql( "case when " );
|
||||
filter.accept( translator );
|
||||
translator.getCurrentClauseStack().pop();
|
||||
sqlAppender.appendSql( " then " );
|
||||
castTarget = renderArgument( sqlAppender, translator, arg );
|
||||
sqlAppender.appendSql( " else null end)" );
|
||||
}
|
||||
else {
|
||||
castTarget = renderArgument( sqlAppender, translator, arg );
|
||||
sqlAppender.appendSql( ')' );
|
||||
if ( filter != null ) {
|
||||
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||
sqlAppender.appendSql( " filter (where " );
|
||||
filter.accept( translator );
|
||||
sqlAppender.appendSql( ')' );
|
||||
translator.getCurrentClauseStack().pop();
|
||||
}
|
||||
}
|
||||
if ( castTarget != null ) {
|
||||
sqlAppender.appendSql( "::" );
|
||||
sqlAppender.appendSql( castTarget );
|
||||
}
|
||||
}
|
||||
|
||||
private String renderArgument(SqlAppender sqlAppender, SqlAstTranslator<?> translator, Expression arg) {
|
||||
final JdbcMapping sourceMapping = arg.getExpressionType().getJdbcMappings().get( 0 );
|
||||
// Cast uuid expressions to "text" first, aggregate that, and finally cast to uuid again
|
||||
if ( sourceMapping.getJdbcType().getDefaultSqlTypeCode() == SqlTypes.UUID ) {
|
||||
sqlAppender.appendSql( "cast(" );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql( " as text)" );
|
||||
return "uuid";
|
||||
}
|
||||
else {
|
||||
arg.accept( translator );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArgumentListSignature() {
|
||||
return "(COMPARABLE arg)";
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue