Implement tuple count emulation

This commit is contained in:
Christian Beikov 2021-09-23 00:20:57 +02:00
parent aa7b5529e9
commit 3ecc602852
15 changed files with 187 additions and 30 deletions

View File

@ -215,6 +215,11 @@ public class CockroachDialect extends Dialect {
return NullOrdering.SMALLEST;
}
@Override
public boolean supportsTupleCounts() {
return true;
}
@Override
public boolean requiresParensForTupleDistinctCounts() {
return true;

View File

@ -21,7 +21,6 @@ import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.pagination.AbstractLimitHandler;
import org.hibernate.dialect.pagination.DerbyLimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.sequence.DB2SequenceSupport;
import org.hibernate.dialect.sequence.DerbySequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.engine.jdbc.Size;
@ -53,7 +52,6 @@ import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorDerbyDatabaseImpl;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.jdbc.DecimalTypeDescriptor;
import org.hibernate.type.descriptor.jdbc.JdbcTypeDescriptor;
import org.hibernate.type.descriptor.jdbc.SmallIntTypeDescriptor;
@ -185,7 +183,7 @@ public class DerbyDialect extends Dialect {
super.initializeFunctionRegistry( queryEngine );
// Derby needs an actual argument type for aggregates like SUM, AVG, MIN, MAX to determine the result type
CommonFunctionFactory.aggregates( queryEngine, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER );
CommonFunctionFactory.aggregates( this, queryEngine, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER );
CommonFunctionFactory.concat_pipeOperator( queryEngine );
CommonFunctionFactory.cot( queryEngine );

View File

@ -476,7 +476,7 @@ public abstract class Dialect implements ConversionContext {
//aggregate functions, supported on every database
CommonFunctionFactory.aggregates( queryEngine, SqlAstNodeRenderingMode.DEFAULT );
CommonFunctionFactory.aggregates( this, queryEngine, SqlAstNodeRenderingMode.DEFAULT );
//the ANSI SQL-defined aggregate functions any() and every() are only
//supported on one database, but can be emulated using sum() and case,

View File

@ -46,7 +46,6 @@ 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.SqlTupleContainer;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorH2DatabaseImpl;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl;
@ -157,7 +156,7 @@ public class H2Dialect extends Dialect {
super.initializeFunctionRegistry( queryEngine );
// H2 needs an actual argument type for aggregates like SUM, AVG, MIN, MAX to determine the result type
CommonFunctionFactory.aggregates( queryEngine, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER );
CommonFunctionFactory.aggregates( this, queryEngine, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER );
CommonFunctionFactory.pi( queryEngine );
CommonFunctionFactory.cot( queryEngine );
@ -416,6 +415,11 @@ public class H2Dialect extends Dialect {
return false;
}
@Override
public boolean supportsTupleCounts() {
return true;
}
@Override
public boolean requiresParensForTupleDistinctCounts() {
return true;

View File

@ -650,6 +650,11 @@ public class HSQLDialect extends Dialect {
return String.valueOf( bool );
}
@Override
public boolean supportsTupleCounts() {
return true;
}
@Override
public boolean supportsTupleDistinctCounts() {
// from v. 2.2.9 is added support for COUNT(DISTINCT ...) with multiple arguments

View File

@ -494,6 +494,11 @@ public class MySQLDialect extends Dialect {
return getMySQLVersion() >= 570;
}
@Override
public boolean supportsTupleCounts() {
return true;
}
@Override
public boolean supportsUnionAll() {
return getMySQLVersion() >= 500;

View File

@ -531,6 +531,11 @@ public class PostgreSQLDialect extends Dialect {
return "select now()";
}
@Override
public boolean supportsTupleCounts() {
return true;
}
@Override
public boolean requiresParensForTupleDistinctCounts() {
return true;

View File

@ -13,6 +13,7 @@ import java.util.List;
import java.util.Arrays;
import java.util.function.Supplier;
import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType;
@ -1483,7 +1484,10 @@ public class CommonFunctionFactory {
.register();
}
public static void aggregates(QueryEngine queryEngine, SqlAstNodeRenderingMode inferenceArgumentRenderingMode) {
public static void aggregates(
Dialect dialect,
QueryEngine queryEngine,
SqlAstNodeRenderingMode inferenceArgumentRenderingMode) {
queryEngine.getSqmFunctionRegistry().namedAggregateDescriptorBuilder( "max" )
.setArgumentRenderingMode( inferenceArgumentRenderingMode )
.setExactArgumentCount( 1 )
@ -1603,11 +1607,7 @@ public class CommonFunctionFactory {
.setExactArgumentCount( 1 )
.register();
queryEngine.getSqmFunctionRegistry().namedAggregateDescriptorBuilder( "count" )
.setInvariantType( StandardBasicTypes.LONG )
.setExactArgumentCount( 1 )
.setArgumentListSignature( "([distinct ]{arg|*})" )
.register();
queryEngine.getSqmFunctionRegistry().register( CountFunction.FUNCTION_NAME, new CountFunction( dialect ) );
}
public static void math(QueryEngine queryEngine) {

View File

@ -0,0 +1,140 @@
/*
* 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.ArrayList;
import java.util.List;
import org.hibernate.dialect.Dialect;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.function.FunctionKind;
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
import org.hibernate.query.sqm.sql.internal.AbstractSqmPathInterpretation;
import org.hibernate.query.sqm.sql.internal.EntityValuedPathInterpretation;
import org.hibernate.query.sqm.sql.internal.NonAggregatedCompositeValuedPathInterpretation;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
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.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.Distinct;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.NullnessLiteral;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
import org.hibernate.sql.ast.tree.expression.Star;
import org.hibernate.sql.ast.tree.predicate.Junction;
import org.hibernate.sql.ast.tree.predicate.NullnessPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.type.StandardBasicTypes;
/**
* @author Christian Beikov
*/
public class CountFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
public static final String FUNCTION_NAME = "count";
private final Dialect dialect;
public CountFunction(Dialect dialect) {
super(
FUNCTION_NAME,
FunctionKind.AGGREGATE,
StandardArgumentsValidators.exactly( 1 ),
StandardFunctionReturnTypeResolvers.invariant( StandardBasicTypes.LONG )
);
this.dialect = dialect;
}
@Override
public void render(SqlAppender sqlAppender, List<SqlAstNode> sqlAstArguments, SqlAstTranslator<?> walker) {
render( sqlAppender, sqlAstArguments, null, walker );
}
@Override
public void render(
SqlAppender sqlAppender,
List<SqlAstNode> sqlAstArguments,
Predicate filter,
SqlAstTranslator<?> translator) {
final boolean caseWrapper = filter != null && !translator.supportsFilterClause();
final SqlAstNode arg = sqlAstArguments.get( 0 );
final SqlAstNode realArg;
sqlAppender.appendSql( "count(" );
if ( arg instanceof Distinct ) {
sqlAppender.appendSql( "distinct " );
final Expression distinctArg = ( (Distinct) arg ).getExpression();
// todo (6.0): emulate tuple count distinct if necessary
realArg = distinctArg;
}
else {
// If the table group supports inner joins, this means that it is non-optional,
// which means we can omit the tuples and instead use count(*)
final SqlTuple tuple;
if ( ( arg instanceof EntityValuedPathInterpretation<?> || arg instanceof NonAggregatedCompositeValuedPathInterpretation<?> )
&& ( (AbstractSqmPathInterpretation<?>) arg ).getTableGroup().canUseInnerJoins() ) {
realArg = Star.INSTANCE;
}
else if ( !dialect.supportsTupleCounts() && ( tuple = SqlTupleContainer.getSqlTuple( arg ) ) != null ) {
final List<? extends Expression> expressions = tuple.getExpressions();
if ( expressions.size() == 1 ) {
realArg = expressions.get( 0 );
}
else {
final List<CaseSearchedExpression.WhenFragment> whenFragments = new ArrayList<>( 1 );
final Junction junction = new Junction( Junction.Nature.DISJUNCTION );
for ( Expression expression : expressions ) {
junction.add( new NullnessPredicate( expression ) );
}
whenFragments.add(
new CaseSearchedExpression.WhenFragment(
junction,
new NullnessLiteral( StandardBasicTypes.INTEGER )
)
);
realArg = new CaseSearchedExpression(
StandardBasicTypes.INTEGER,
whenFragments,
new QueryLiteral<>( 1, StandardBasicTypes.INTEGER )
);
}
}
else {
realArg = arg;
}
}
if ( caseWrapper ) {
sqlAppender.appendSql( "case when " );
filter.accept( translator );
sqlAppender.appendSql( " then " );
if ( realArg instanceof Star ) {
sqlAppender.appendSql( "1" );
}
else {
translator.render( realArg, SqlAstNodeRenderingMode.DEFAULT );
}
sqlAppender.appendSql( " else null end" );
}
else {
translator.render( realArg, SqlAstNodeRenderingMode.DEFAULT );
}
sqlAppender.appendSql( ')' );
if ( filter != null && !caseWrapper ) {
sqlAppender.appendSql( " filter (where " );
filter.accept( translator );
sqlAppender.appendSql( ')' );
}
}
@Override
public String getArgumentListSignature() {
return "([distinct ]{arg|*})";
}
}

View File

@ -935,10 +935,6 @@ public class ToOneAttributeMapping
if ( !canUseParentTableGroup ) {
return false;
}
// // Special case for resolving the table group for entity valued paths
// if ( np == navigablePath ) {
// return true;
// }
NavigablePath path = np.getParent();
// Fast path
if ( path != null && navigablePath.equals( path ) ) {

View File

@ -1835,7 +1835,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.LESS_THAN_OR_EQUAL,
(SqmExpression<?>) y,
value( y, (SqmExpression<?>) x ),
this
);
}

View File

@ -10,6 +10,7 @@ import java.util.Set;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
@ -28,6 +29,11 @@ public interface SqlAstTranslator<T extends JdbcOperation> extends SqlAstWalker
*/
boolean supportsFilterClause();
/**
* Returns the current query part that is translated.
*/
QueryPart getCurrentQueryPart();
/**
* Not the best spot for this. Its the table names collected while walking the SQL AST.
* Its ok here because the translator is consider a one-time-use. It just needs to be called

View File

@ -530,6 +530,11 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
return queryPartStack;
}
@Override
public QueryPart getCurrentQueryPart() {
return queryPartStack.getCurrent();
}
@Override
public T translate(JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) {
try {

View File

@ -55,18 +55,6 @@ public class CompositeTableGroup implements VirtualTableGroup {
@Override
public boolean isFetched() {
// if ( fetched ) {
// return true;
// }
// // We also consider it "fetched" if it contains fetched joins
// if ( tableGroupJoins != null ) {
// for ( TableGroupJoin tableGroupJoin : tableGroupJoins ) {
// if ( tableGroupJoin.getJoinedGroup().isFetched() ) {
// return true;
// }
// }
// }
// return false;
return fetched;
}

View File

@ -109,7 +109,7 @@ public class EntityGraphAttributeResolutionTest extends BaseEntityManagerFunctio
attributeNodes = {
@NamedAttributeNode("permissions")
})
@Table(name = "groups") // Name 'group' not accepted by H2
@Table( name = "t_group") // Name 'group' not accepted by H2
public static class Group {
public static final String ENTITY_GRAPH = "group-with-permissions";