From cbcec73d4fa4ca0845d0f8ca620dcdc02cd91b17 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 23 Nov 2021 18:16:48 +0100 Subject: [PATCH] * Handle quoted identifiers in HQL and the Ordering parser * Switch from the "expression" to "expressionOrPredicate" rule in the HQL grammar where it makes sense as required by some HQL tests * Cleanup parser rule ordering to allow more keywords in the identifier rule * Implement literal support for Ordering parser * Add special AvgFunction as needed by H2, HSQL, DB2, SQL Server and Sybase that casts arguments to double if necessary * Fix wrong deduplication of order by fragments in case a plural attribute is fetched multiple times * Implement support for de-referencing any-valued mappings in HQL * Avoid unnecessary entity subtypes in polymorphic splitted queries if a base type also matches the requested type * Implement pagination support for polymorphic splitted queries * Cleanup path part resolving by removing lots of duplicate code * Aligh HQL parsing expectations to the expected behavior of 5.x * Add method to `JavaType` that allows determining if a type is can be widened to another which is used for arithmetic type resolving * Implement validations for fetch owner checking * Fix issues with the id table creation due to lacking column lengths in the column DDL type * Fix issues and add some optimizations related to multi-table delete handling * Add the notion of a special "implicit" alias to avoid generating a unique alias for unaliased or implicit HQL joins * Properly implement multiple bag fetch validation * Make sure filter predicates are applied for all plural attribute joins * Fix some issues with undecidable parameter type inference * Fix some issues with negated SQM predicates not being converted to the proper SQL AST predicates * Fix issues with qualifying DML target referencing columns * Fix `is null` semantics for tuples referring to embeddable types * Capture necessary details from JdbcValuesMetadata in the cached data to avoid executing a query on cache hit when types should be inferred * Get rid of special CollectionPropertyNames and writeup a migration guide section for the replacements --- docker_db.sh | 2 + .../community/dialect/FirebirdDialect.java | 2 +- .../community/dialect/InformixDialect.java | 2 +- .../community/dialect/IngresDialect.java | 2 +- .../community/dialect/TeradataDialect.java | 2 +- .../community/dialect/TimesTenDialect.java | 8 +- .../org/hibernate/grammars/hql/HqlLexer.g4 | 9 +- .../org/hibernate/grammars/hql/HqlParser.g4 | 125 +++-- .../grammars/ordering/OrderingLexer.g4 | 37 +- .../grammars/ordering/OrderingParser.g4 | 27 +- .../boot/model/naming/Identifier.java | 104 +++- .../dialect/AbstractTransactSQLDialect.java | 2 +- .../org/hibernate/dialect/DB2Dialect.java | 4 + .../org/hibernate/dialect/DerbyDialect.java | 7 +- .../java/org/hibernate/dialect/Dialect.java | 4 +- .../java/org/hibernate/dialect/H2Dialect.java | 6 +- .../dialect/HANAColumnStoreDialect.java | 2 +- .../dialect/HANARowStoreDialect.java | 2 +- .../org/hibernate/dialect/HSQLDialect.java | 8 +- .../org/hibernate/dialect/MySQLDialect.java | 2 +- .../org/hibernate/dialect/OracleDialect.java | 3 +- .../hibernate/dialect/SQLServerDialect.java | 3 + .../dialect/SybaseASESqlAstTranslator.java | 7 +- .../org/hibernate/dialect/SybaseDialect.java | 3 + .../dialect/function/AvgFunction.java | 115 +++++ .../function/CommonFunctionFactory.java | 31 +- .../dialect/function/CountFunction.java | 27 +- .../dialect/function/CurrentFunction.java | 4 + .../NormalizingIdentifierHelperImpl.java | 2 +- .../internal/util/QuotingHelper.java | 141 ++++++ .../util/collections/ArrayHelper.java | 2 +- .../ast/internal/LoaderSelectBuilder.java | 57 ++- .../hibernate/mapping/PersistentClass.java | 16 + .../metamodel/mapping/CollectionPart.java | 3 +- .../metamodel/mapping/MappingModelHelper.java | 4 +- .../mapping/internal/AbstractDomainPath.java | 4 +- ...criminatedAssociationAttributeMapping.java | 83 ++- .../internal/DiscriminatedCollectionPart.java | 5 + .../internal/EmbeddedCollectionPart.java | 19 +- .../internal/EntityCollectionPart.java | 5 + .../internal/MappingModelCreationProcess.java | 1 - .../internal/PluralAttributeMappingImpl.java | 40 +- .../mapping/ordering/TranslationContext.java | 8 +- .../ordering/ast/CollectionPartPath.java | 1 + .../mapping/ordering/ast/ColumnReference.java | 21 +- .../ordering/ast/DomainPathContinuation.java | 1 + .../ast/FkDomainPathContinuation.java | 6 +- .../ordering/ast/OrderingSpecification.java | 13 +- .../ordering/ast/ParseTreeVisitor.java | 191 ++++--- .../mapping/ordering/ast/PathConsumer.java | 21 +- .../ordering/ast/PluralAttributePath.java | 8 +- .../ordering/ast/RootSequencePart.java | 16 +- .../ast/SelfRenderingOrderingExpression.java | 86 ++++ .../mapping/ordering/ast/SequencePart.java | 1 + .../internal/AnyMappingSqmPathSource.java | 13 +- .../domain/internal/DiscriminatorSqmPath.java | 2 +- .../domain/internal/JpaMetamodelImpl.java | 29 +- .../collection/CollectionPropertyNames.java | 26 - .../entity/AbstractEntityPersister.java | 13 +- .../entity/SingleTableEntityPersister.java | 4 +- .../internal/BasicDotIdentifierConsumer.java | 26 +- .../query/hql/internal/DomainPathPart.java | 95 +--- .../internal/QualifiedJoinPathConsumer.java | 45 +- .../query/hql/internal/QuerySplitter.java | 251 ++++++---- .../hql/internal/SemanticQueryBuilder.java | 378 +++++++------- .../hql/internal/SqmPathRegistryImpl.java | 38 -- .../query/hql/spi/SqmPathRegistry.java | 16 - .../hibernate/query/internal/QueryHelper.java | 6 +- .../internal/QueryParameterBindingsImpl.java | 26 +- .../query/results/ResultSetMappingImpl.java | 2 +- ...pleteResultBuilderBasicValuedStandard.java | 6 +- .../results/dynamic/DynamicFetchBuilder.java | 3 + .../dynamic/DynamicFetchBuilderLegacy.java | 5 + .../dynamic/DynamicFetchBuilderStandard.java | 5 + .../DynamicResultBuilderBasicConverted.java | 8 +- .../DynamicResultBuilderBasicStandard.java | 6 +- .../DynamicResultBuilderEntityStandard.java | 25 +- .../ImplicitFetchBuilderEmbeddable.java | 1 - .../hibernate/query/spi/AbstractQuery.java | 2 +- .../sqm/StrictJpaComplianceViolation.java | 1 + .../function/SelfRenderingSqmFunction.java | 2 +- .../AggregatedNonSelectQueryPlanImpl.java | 30 ++ .../AggregatedSelectQueryPlanImpl.java | 33 +- .../internal/ConcreteSqmSelectQueryPlan.java | 18 +- .../query/sqm/internal/QuerySqmImpl.java | 38 +- .../internal/MatchingIdSelectionHelper.java | 1 + .../MultiTableSqmMutationConverter.java | 23 +- .../internal/SqmMutationStrategyHelper.java | 2 +- .../cte/AbstractCteMutationHandler.java | 6 +- .../idtable/ExecuteWithIdTableHelper.java | 6 +- .../mutation/internal/idtable/IdTable.java | 89 ++-- .../RestrictedDeleteExecutionDelegate.java | 263 ++++++---- .../idtable/TableBasedUpdateHandler.java | 2 + .../internal/idtable/TempIdTableExporter.java | 2 +- .../internal/inline/InlineDeleteHandler.java | 2 +- .../query/sqm/spi/SqmCreationHelper.java | 31 +- .../sqm/sql/BaseSqmToSqlAstConverter.java | 471 +++++++++--------- .../EntityValuedPathInterpretation.java | 28 +- .../tree/domain/AbstractSqmAttributeJoin.java | 2 +- .../sqm/tree/domain/AbstractSqmFrom.java | 88 +++- .../NonAggregatedCompositeSimplePath.java | 11 +- .../tree/domain/SqmAnyValuedSimplePath.java | 12 +- .../tree/domain/SqmBasicValuedSimplePath.java | 2 +- .../sqm/tree/domain/SqmCorrelatedRoot.java | 17 + .../domain/SqmEmbeddedValuedSimplePath.java | 11 +- .../domain/SqmEntityValuedSimplePath.java | 14 +- .../SqmIndexedCollectionAccessPath.java | 14 +- .../sqm/tree/domain/SqmMaxElementPath.java | 17 +- .../sqm/tree/domain/SqmMaxIndexPath.java | 6 +- .../sqm/tree/domain/SqmMinElementPath.java | 10 +- .../sqm/tree/domain/SqmMinIndexPath.java | 6 +- .../query/sqm/tree/domain/SqmPath.java | 5 + .../domain/SqmPluralValuedSimplePath.java | 27 +- .../query/sqm/tree/domain/SqmTreatedRoot.java | 24 +- .../query/sqm/tree/from/SqmFrom.java | 3 - .../sqm/tree/jpa/ParameterCollector.java | 2 +- .../tree/select/SqmDynamicInstantiation.java | 10 - .../query/sqm/tree/select/SqmQueryGroup.java | 9 +- .../query/sqm/tree/select/SqmQueryPart.java | 2 +- .../query/sqm/tree/select/SqmQuerySpec.java | 86 +++- .../result/internal/OutputsImpl.java | 1 + .../sql/ast/spi/AbstractSqlAstTranslator.java | 96 +++- .../sql/ast/tree/delete/DeleteStatement.java | 1 + .../ast/tree/predicate/ExistsPredicate.java | 8 +- .../JdbcSelectExecutorStandardImpl.java | 182 ++++++- .../QueryCachePutManagerEnabledImpl.java | 9 +- .../internal/DynamicInstantiation.java | 17 +- .../jdbc/internal/JdbcValuesCacheHit.java | 24 +- .../internal/JdbcValuesResultSetImpl.java | 24 +- .../jdbc/internal/ResultSetAccess.java | 5 +- .../results/jdbc/spi/JdbcValuesMetadata.java | 6 +- .../java/BigDecimalJavaTypeDescriptor.java | 18 + .../java/BigIntegerJavaTypeDescriptor.java | 17 + .../java/DoubleJavaTypeDescriptor.java | 21 + .../descriptor/java/FloatTypeDescriptor.java | 19 + .../java/IntegerJavaTypeDescriptor.java | 13 + .../type/descriptor/java/JavaType.java | 8 + .../java/LongJavaTypeDescriptor.java | 15 + .../java/ShortJavaTypeDescriptor.java | 11 + .../java/StringJavaTypeDescriptor.java | 13 + .../orm/test/hql/QuotedIdentifierTest.java | 121 +++++ .../temporals/ProposedGeneratedTests.java | 5 +- .../ManyToManyHqlMemberOfQueryTest.java | 3 +- .../onetomany/OneToManyBidirectionalTest.java | 1 - .../OneToManyHqlMemberOfQueryTest.java | 3 +- .../orm/test/query/hql/InsertUpdateTests.java | 2 +- .../annotations/query/QueryAndSQLTest.java | 18 +- .../test/hql/ASTParserLoadingTest.java | 124 ++--- .../test/hql/BulkManipulationTest.java | 34 +- .../test/hql/FunctionNameAsColumnTest.java | 49 +- .../hql/size/WhereClauseOrderBySizeTest.java | 20 +- .../WhereAnnotatedOneToManySizeTest.java | 25 +- migration-guide.adoc | 38 ++ 153 files changed, 3141 insertions(+), 1572 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/AvgFunction.java create mode 100644 hibernate-core/src/main/java/org/hibernate/internal/util/QuotingHelper.java create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/SelfRenderingOrderingExpression.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPropertyNames.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AggregatedNonSelectQueryPlanImpl.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/hql/QuotedIdentifierTest.java diff --git a/docker_db.sh b/docker_db.sh index 068cbe870e..4412f70fe4 100755 --- a/docker_db.sh +++ b/docker_db.sh @@ -270,6 +270,8 @@ exec sp_dboption $SYBASE_DB, 'full logging for alter table', true go sp_dboption $SYBASE_DB, \"select into\", true go +sp_dboption tempdb, 'ddl in tran', true +go EOSQL /opt/sybase/OCS-16_0/bin/isql -Usa -P myPassword -S MYSYBASE -i ./init1.sql diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/FirebirdDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/FirebirdDialect.java index 902d0d2be4..73608d0e0e 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/FirebirdDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/FirebirdDialect.java @@ -870,7 +870,7 @@ public class FirebirdDialect extends Dialect { return getVersion() < 210 ? super.getFallbackSqmMutationStrategy( entityDescriptor, runtimeModelCreationContext ) : new GlobalTemporaryTableStrategy( - new IdTable( entityDescriptor, name -> "HT_" + name, this ), + new IdTable( entityDescriptor, name -> "HT_" + name, this, runtimeModelCreationContext ), () -> new TempIdTableExporter( false, this::getTypeName ) { @Override protected String getCreateOptions() { diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java index 4b2a38d740..c5a005804a 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java @@ -388,7 +388,7 @@ public class InformixDialect extends Dialect { EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { return new LocalTemporaryTableStrategy( - new IdTable( rootEntityDescriptor, basename -> "HT_" + basename, this ), + new IdTable( rootEntityDescriptor, basename -> "HT_" + basename, this, runtimeModelCreationContext ), () -> new TempIdTableExporter( true, this::getTypeName ) { @Override protected String getCreateCommand() { diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/IngresDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/IngresDialect.java index ebfd0de804..0d993e7d29 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/IngresDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/IngresDialect.java @@ -413,7 +413,7 @@ public class IngresDialect extends Dialect { EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { return new GlobalTemporaryTableStrategy( - new IdTable( rootEntityDescriptor, name -> "session." + name, this ), + new IdTable( rootEntityDescriptor, name -> "session." + name, this, runtimeModelCreationContext ), () -> new TempIdTableExporter( false, this::getTypeName ) { @Override protected String getCreateOptions() { diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TeradataDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TeradataDialect.java index 670b3bcc97..37f9c8537e 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TeradataDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TeradataDialect.java @@ -295,7 +295,7 @@ public class TeradataDialect extends Dialect { EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { return new GlobalTemporaryTableStrategy( - new IdTable( rootEntityDescriptor, basename -> "HT_" + basename, this ), + new IdTable( rootEntityDescriptor, basename -> "HT_" + basename, this, runtimeModelCreationContext ), () -> new TempIdTableExporter( false, this::getTypeName ) { @Override public String getCreateOptions() { diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenDialect.java index 3754f4f0ff..d56758789e 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenDialect.java @@ -314,8 +314,12 @@ public class TimesTenDialect extends Dialect { EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { return new GlobalTemporaryTableStrategy( - new IdTable( rootEntityDescriptor, - name -> name.length() > 30 ? name.substring( 0, 30 ) : name, this ), + new IdTable( + rootEntityDescriptor, + name -> name.length() > 30 ? name.substring( 0, 30 ) : name, + this, + runtimeModelCreationContext + ), () -> new TempIdTableExporter( false, this::getTypeName ) { @Override protected String getCreateOptions() { diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 index 834e83471c..d5e3afa2f6 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 @@ -42,6 +42,9 @@ BIG_DECIMAL_SUFFIX : [bB] [dD]; fragment BIG_INTEGER_SUFFIX : [bB] [iI]; +// Although this is not 100% correct because this accepts leading zeros, +// we stick to this because temporal literals use this rule for simplicity. +// Since we don't support octal literals, this shouldn't really be a big issue fragment INTEGER_NUMBER : DIGIT+ @@ -73,8 +76,8 @@ fragment SINGLE_QUOTE : '\''; fragment DOUBLE_QUOTE : '"'; STRING_LITERAL - : DOUBLE_QUOTE ( ~('"') | ESCAPE_SEQUENCE | DOUBLE_QUOTE DOUBLE_QUOTE )* DOUBLE_QUOTE - | SINGLE_QUOTE ( ~('\'') | ESCAPE_SEQUENCE | SINGLE_QUOTE SINGLE_QUOTE )* SINGLE_QUOTE + : DOUBLE_QUOTE ( ESCAPE_SEQUENCE | DOUBLE_QUOTE DOUBLE_QUOTE | ~('"') )* DOUBLE_QUOTE + | SINGLE_QUOTE ( ESCAPE_SEQUENCE | SINGLE_QUOTE SINGLE_QUOTE | ~('\'') )* SINGLE_QUOTE ; fragment BACKSLASH : '\\'; @@ -318,5 +321,5 @@ fragment BACKTICK : '`'; QUOTED_IDENTIFIER - : BACKTICK ( ~([\\`]) | ESCAPE_SEQUENCE )* BACKTICK + : BACKTICK ( ESCAPE_SEQUENCE | '\\' BACKTICK | ~([`]) )* BACKTICK ; diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 index 6598168db0..36e959d159 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 @@ -52,7 +52,7 @@ setClause ; assignment - : dotIdentifierSequence EQUAL expression + : dotIdentifierSequence EQUAL expressionOrPredicate ; insertStatement @@ -68,7 +68,7 @@ valuesList ; values - : LEFT_PAREN expression (COMMA expression)* RIGHT_PAREN + : LEFT_PAREN expressionOrPredicate (COMMA expressionOrPredicate)* RIGHT_PAREN ; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -116,20 +116,14 @@ pathRoot : entityName identificationVariableDef? ; -/** - * Specialized dotIdentifierSequence for cases where we expect an entity-name. We handle it specially - * for the sake of performance. Specifically we concatenate together the entity name as we walk the - * parse tree. Relying on the `EntiytNameContext#getText` or `DotIdentifierSequenceContext#getText` - * performs walk to determine the name. - */ entityName - returns [String fullNameText] - : (i=identifier { $fullNameText = _localctx.i.getText(); }) (DOT c=identifier { $fullNameText += ("." + _localctx.c.getText() ); })* + : identifier (DOT identifier)* ; identificationVariableDef : (AS identifier) | IDENTIFIER + | QUOTED_IDENTIFIER ; crossJoin @@ -171,19 +165,14 @@ selectionList ; selection - : selectExpression resultIdentifier? + : selectExpression identificationVariableDef? ; selectExpression : dynamicInstantiation | jpaSelectObjectSyntax | mapEntrySelection - | expression - ; - -resultIdentifier - : (AS identifier) - | IDENTIFIER + | expressionOrPredicate ; @@ -206,11 +195,11 @@ dynamicInstantiationArgs ; dynamicInstantiationArg - : dynamicInstantiationArgExpression (AS? identifier)? + : dynamicInstantiationArgExpression identificationVariableDef? ; dynamicInstantiationArgExpression - : expression + : expressionOrPredicate | dynamicInstantiation ; @@ -428,10 +417,10 @@ comparisonOperator ; inList - : (ELEMENTS|INDICES) LEFT_PAREN dotIdentifierSequence RIGHT_PAREN # PersistentCollectionReferenceInList - | LEFT_PAREN expression (COMMA expression)* RIGHT_PAREN # ExplicitTupleInList - | LEFT_PAREN subQuery RIGHT_PAREN # SubQueryInList - | parameter # ParamInList + : (ELEMENTS|INDICES) LEFT_PAREN dotIdentifierSequence RIGHT_PAREN # PersistentCollectionReferenceInList + | LEFT_PAREN (expressionOrPredicate (COMMA expressionOrPredicate)*)? RIGHT_PAREN# ExplicitTupleInList + | LEFT_PAREN subQuery RIGHT_PAREN # SubQueryInList + | parameter # ParamInList ; likeEscape @@ -444,16 +433,17 @@ likeEscape expression //highest to lowest precedence - : LEFT_PAREN expression RIGHT_PAREN # GroupedExpression - | LEFT_PAREN expression (COMMA expression)* RIGHT_PAREN # TupleExpression - | LEFT_PAREN subQuery RIGHT_PAREN # SubQueryExpression - | primaryExpression collationSpecification? # CollateExpression - | signOperator expression # UnaryExpression - | expression datetimeField # ToDurationExpression - | expression BY datetimeField # FromDurationExpression - | expression multiplicativeOperator expression # MultiplicationExpression - | expression additiveOperator expression # AdditionExpression - | expression DOUBLE_PIPE expression # ConcatenationExpression + : LEFT_PAREN expression RIGHT_PAREN # GroupedExpression + | LEFT_PAREN expressionOrPredicate (COMMA expressionOrPredicate)+ RIGHT_PAREN # TupleExpression + | LEFT_PAREN subQuery RIGHT_PAREN # SubQueryExpression + | primaryExpression collationSpecification? # CollateExpression + | signOperator numericLiteral # UnaryNumericLiteralExpression + | signOperator expression # UnaryExpression + | expression datetimeField # ToDurationExpression + | expression BY datetimeField # FromDurationExpression + | expression multiplicativeOperator expression # MultiplicationExpression + | expression additiveOperator expression # AdditionExpression + | expression DOUBLE_PIPE expression # ConcatenationExpression ; primaryExpression @@ -464,8 +454,14 @@ primaryExpression | entityIdReference # EntityIdExpression | entityVersionReference # EntityVersionExpression | entityNaturalIdReference # EntityNaturalIdExpression - | path # PathExpression + | syntacticDomainPath (pathContinuation)? # SyntacticPathExpression | function # FunctionExpression + | generalPathFragment # GeneralPathExpression + ; + +expressionOrPredicate + : expression + | predicate ; multiplicativeOperator @@ -506,15 +502,15 @@ caseList ; simpleCaseList - : CASE expression (simpleCaseWhen)+ (caseOtherwise)? END + : CASE expressionOrPredicate (simpleCaseWhen)+ (caseOtherwise)? END ; simpleCaseWhen - : WHEN expression THEN expression + : WHEN expression THEN expressionOrPredicate ; caseOtherwise - : ELSE expression + : ELSE expressionOrPredicate ; searchedCaseList @@ -522,24 +518,28 @@ searchedCaseList ; searchedCaseWhen - : WHEN predicate THEN expression + : WHEN predicate THEN expressionOrPredicate ; literal : STRING_LITERAL - | INTEGER_LITERAL + | NULL + | TRUE + | FALSE + | numericLiteral + | binaryLiteral + | temporalLiteral + | generalizedLiteral + ; + +numericLiteral + : INTEGER_LITERAL | LONG_LITERAL | BIG_INTEGER_LITERAL | FLOAT_LITERAL | DOUBLE_LITERAL | BIG_DECIMAL_LITERAL | HEX_LITERAL - | NULL - | TRUE - | FALSE - | binaryLiteral - | temporalLiteral - | generalizedLiteral ; binaryLiteral @@ -652,7 +652,7 @@ genericFunctionName ; nonStandardFunctionArguments - : (DISTINCT | datetimeField COMMA)? expression (COMMA expression)* + : (DISTINCT | datetimeField COMMA)? expressionOrPredicate (COMMA expressionOrPredicate)* ; jpaCollectionFunction @@ -897,11 +897,11 @@ positionFunctionStringArgument ; cube - : CUBE LEFT_PAREN expression (COMMA expression)* RIGHT_PAREN + : CUBE LEFT_PAREN expressionOrPredicate (COMMA expressionOrPredicate)* RIGHT_PAREN ; rollup - : ROLLUP LEFT_PAREN expression (COMMA expression)* RIGHT_PAREN + : ROLLUP LEFT_PAREN expressionOrPredicate (COMMA expressionOrPredicate)* RIGHT_PAREN ; /** @@ -941,13 +941,15 @@ identifier | COS | COUNT | CROSS + | CUBE + | CURRENT | CURRENT_DATE | CURRENT_INSTANT | CURRENT_TIME | CURRENT_TIMESTAMP | DATE | DAY - | DAY + | DATETIME | DELETE | DESC | DISTINCT @@ -958,11 +960,13 @@ identifier | ENTRY | ESCAPE | EVERY + | EXCEPT | EXISTS | EXP | EXTRACT | FETCH | FILTER + | FIRST | FLOOR | FOR | FORMAT @@ -971,6 +975,7 @@ identifier | FUNCTION | GREATEST | GROUP + | HAVING | HOUR | ID | IFNULL @@ -981,10 +986,12 @@ identifier | INNER | INSERT | INSTANT + | INTERSECT | INTO | IS | JOIN | KEY + | LAST | LEADING | LEAST | LEFT @@ -993,6 +1000,10 @@ identifier | LIMIT | LIST | LN + | LOCAL + | LOCAL_DATE + | LOCAL_DATETIME + | LOCAL_TIME | LOCATE | LOWER | MAP @@ -1000,7 +1011,6 @@ identifier | MAXELEMENT | MAXINDEX | MEMBER - | MEMBER | MICROSECOND | MILLISECOND | MIN @@ -1012,22 +1022,32 @@ identifier | NANOSECOND | NATURALID | NEW + | NEXT | NOT | NULLIF + | NULLS | OBJECT | OF + | OFFSET + | OFFSET_DATETIME | ON + | ONLY | OR | ORDER | OUTER + | OVERLAY | PAD + | PERCENT + | PLACING | POSITION | POWER | QUARTER | REPLACE | RIGHT - | RIGHT + | ROLLUP | ROUND + | ROW + | ROWS | SECOND | SELECT | SET @@ -1041,6 +1061,7 @@ identifier | SUM | TAN | THEN + | TIES | TIME | TIMESTAMP | TIMEZONE_HOUR @@ -1049,6 +1070,7 @@ identifier | TREAT | TRIM | TYPE + | UNION | UPDATE | UPPER | VALUE @@ -1056,6 +1078,7 @@ identifier | VERSION | VERSIONED | WEEK + | WHEN | WHERE | WITH | YEAR) { diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/ordering/OrderingLexer.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/ordering/OrderingLexer.g4 index e99e996d3f..b288be9db6 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/ordering/OrderingLexer.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/ordering/OrderingLexer.g4 @@ -22,10 +22,13 @@ WS : ( ' ' | '\t' | '\f' | EOL ) -> skip; fragment EOL : [\r\n]+; +fragment +DIGIT : [0-9]; + INTEGER_LITERAL : INTEGER_NUMBER ; fragment -INTEGER_NUMBER : ('0' | '1'..'9' '0'..'9'*) ; +INTEGER_NUMBER : ('0' | '1'..'9' DIGIT*) ; LONG_LITERAL : INTEGER_NUMBER ('l'|'L'); @@ -34,7 +37,7 @@ BIG_INTEGER_LITERAL : INTEGER_NUMBER ('bi'|'BI') ; HEX_LITERAL : '0' ('x'|'X') HEX_DIGIT+ ('l'|'L')? ; fragment -HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ; +HEX_DIGIT : (DIGIT|'a'..'f'|'A'..'F') ; OCTAL_LITERAL : '0' ('0'..'7')+ ('l'|'L')? ; @@ -42,10 +45,10 @@ FLOAT_LITERAL : FLOATING_POINT_NUMBER ('f'|'F')? ; fragment FLOATING_POINT_NUMBER - : ('0'..'9')+ '.' ('0'..'9')* EXPONENT? - | '.' ('0'..'9')+ EXPONENT? - | ('0'..'9')+ EXPONENT - | ('0'..'9')+ + : DIGIT+ '.' DIGIT* EXPONENT? + | '.' DIGIT+ EXPONENT? + | DIGIT+ EXPONENT + | DIGIT+ ; DOUBLE_LITERAL : FLOATING_POINT_NUMBER ('d'|'D') ; @@ -53,15 +56,18 @@ DOUBLE_LITERAL : FLOATING_POINT_NUMBER ('d'|'D') ; BIG_DECIMAL_LITERAL : FLOATING_POINT_NUMBER ('bd'|'BD') ; fragment -EXPONENT : ('e'|'E') ('+'|'-')? ('0'..'9')+ ; +EXPONENT : ('e'|'E') ('+'|'-')? DIGIT+ ; + +fragment SINGLE_QUOTE : '\''; +fragment DOUBLE_QUOTE : '"'; CHARACTER_LITERAL - : '\'' ( ESCAPE_SEQUENCE | ~('\''|'\\') ) '\'' {setText(getText().substring(1, getText().length()-1));} + : SINGLE_QUOTE ( ESCAPE_SEQUENCE | SINGLE_QUOTE SINGLE_QUOTE | ~('\'') ) SINGLE_QUOTE ; STRING_LITERAL - : '"' ( ESCAPE_SEQUENCE | ~('\\'|'"') )* '"' {setText(getText().substring(1, getText().length()-1));} - | ('\'' ( ESCAPE_SEQUENCE | ~('\\'|'\'') )* '\'')+ {setText(getText().substring(1, getText().length()-1).replace("''", "'"));} + : DOUBLE_QUOTE ( ESCAPE_SEQUENCE | DOUBLE_QUOTE DOUBLE_QUOTE | ~('"') )* DOUBLE_QUOTE + | SINGLE_QUOTE ( ESCAPE_SEQUENCE | SINGLE_QUOTE SINGLE_QUOTE | ~('\'') )* SINGLE_QUOTE ; fragment @@ -107,12 +113,19 @@ DESC : [dD] [eE] [sS] [cC] ( [eE] [nN] [dD] [iI] [nN] [gG] )?; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Identifiers +fragment +LETTER : [a-zA-Z\u0080-\ufffe_$]; + +// Identifiers IDENTIFIER - : ('a'..'z'|'A'..'Z'|'_'|'$'|'\u0080'..'\ufffe')('a'..'z'|'A'..'Z'|'_'|'$'|'0'..'9'|'\u0080'..'\ufffe')* + : LETTER (LETTER | DIGIT)* ; +fragment +BACKTICK : '`'; + QUOTED_IDENTIFIER - : '`' ( ESCAPE_SEQUENCE | ~('\\'|'`') )* '`' + : BACKTICK ( ESCAPE_SEQUENCE | '\\' BACKTICK | ~([`]) )* BACKTICK ; diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/ordering/OrderingParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/ordering/OrderingParser.g4 index d1d0622b6c..d5f3e7fe74 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/ordering/OrderingParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/ordering/OrderingParser.g4 @@ -33,9 +33,9 @@ sortSpecification ; expression - : function - | identifier - | dotIdentifier + : function # FunctionExpression + | identifier # IdentifierExpression + | dotIdentifier # DotIdentifierExpression ; function @@ -52,7 +52,23 @@ packagedFunction ; functionArguments - : OPEN_PAREN expression* CLOSE_PAREN + : OPEN_PAREN (functionArgument ( COMMA functionArgument )* )? CLOSE_PAREN + ; + +functionArgument + : expression + | literal + ; + +literal + : STRING_LITERAL + | INTEGER_LITERAL + | LONG_LITERAL + | BIG_INTEGER_LITERAL + | FLOAT_LITERAL + | DOUBLE_LITERAL + | BIG_DECIMAL_LITERAL + | HEX_LITERAL ; collationSpecification @@ -69,6 +85,7 @@ nullsPrecedence identifier : IDENTIFIER + | QUOTED_IDENTIFIER // keyword-as-identifier | FIRST | LAST @@ -78,5 +95,5 @@ identifier ; dotIdentifier - : IDENTIFIER (DOT IDENTIFIER)+ + : identifier (DOT identifier)+ ; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java index c0de513f07..293f58fb08 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/Identifier.java @@ -26,25 +26,18 @@ public class Identifier implements Comparable { * If passed text is {@code null}, {@code null} is returned. *

* If passed text is surrounded in quote markers, the generated Identifier - * is considered quoted. Quote markers include back-ticks (`), and - * double-quotes ("). + * is considered quoted. Quote markers include back-ticks (`), + * double-quotes (") and brackets ([ and ]). + * + * If the text, after trimming, contains a character that is not a valid identifier character, + * the identifier is treated as quoted. * * @param text The text form * * @return The identifier form, or {@code null} if text was {@code null} */ public static Identifier toIdentifier(String text) { - if ( StringHelper.isEmpty( text ) ) { - return null; - } - final String trimmedText = text.trim(); - if ( isQuoted( trimmedText ) ) { - final String bareName = trimmedText.substring( 1, trimmedText.length() - 1 ); - return new Identifier( bareName, true ); - } - else { - return new Identifier( trimmedText, false ); - } + return toIdentifier( text, false ); } /** @@ -53,8 +46,11 @@ public class Identifier implements Comparable { * If passed text is {@code null}, {@code null} is returned. *

* If passed text is surrounded in quote markers, the generated Identifier - * is considered quoted. Quote markers include back-ticks (`), and - * double-quotes ("). + * is considered quoted. Quote markers include back-ticks (`), + * double-quotes (") and brackets ([ and ]). + * + * If the text, after trimming, contains a character that is not a valid identifier character, + * the identifier is treated as quoted. * * @param text The text form * @param quote Whether to quote unquoted text forms @@ -62,17 +58,65 @@ public class Identifier implements Comparable { * @return The identifier form, or {@code null} if text was {@code null} */ public static Identifier toIdentifier(String text, boolean quote) { + return toIdentifier( text, quote, true ); + } + + /** + * Means to generate an {@link Identifier} instance from its simple text form. + *

+ * If passed text is {@code null}, {@code null} is returned. + *

+ * If passed text is surrounded in quote markers, the generated Identifier + * is considered quoted. Quote markers include back-ticks (`), + * double-quotes (") and brackets ([ and ]). + * + * @param text The text form + * @param quote Whether to quote unquoted text forms + * @param quoteOnNonIdentifierChar Controls whether to treat the result as quoted if text contains characters that are invalid for identifiers + * + * @return The identifier form, or {@code null} if text was {@code null} + */ + public static Identifier toIdentifier(String text, boolean quote, boolean quoteOnNonIdentifierChar) { if ( StringHelper.isEmpty( text ) ) { return null; } - final String trimmedText = text.trim(); - if ( isQuoted( trimmedText ) ) { - final String bareName = trimmedText.substring( 1, trimmedText.length() - 1 ); - return new Identifier( bareName, true ); + int start = 0; + int end = text.length(); + while ( start < end ) { + if ( !Character.isWhitespace( text.charAt( start ) ) ) { + break; + } + start++; } - else { - return new Identifier( trimmedText, quote ); + while ( start < end ) { + if ( !Character.isWhitespace( text.charAt( end - 1 ) ) ) { + break; + } + end--; } + if ( isQuoted( text, start, end ) ) { + start++; + end--; + quote = true; + } + else if ( quoteOnNonIdentifierChar && !quote ) { + // Check the letters to determine if we must quote the text + char c = text.charAt( start ); + if ( !Character.isLetter( c ) && c != '_' ) { + // SQL identifiers must begin with a letter or underscore + quote = true; + } + else { + for ( int i = start + 1; i < end; i++ ) { + c = text.charAt( i ); + if ( !Character.isLetterOrDigit( c ) && c != '_' ) { + quote = true; + break; + } + } + } + } + return new Identifier( text.substring( start, end ), quote ); } /** @@ -91,9 +135,21 @@ public class Identifier implements Comparable { * @return {@code true} if the given identifier text is considered quoted; {@code false} otherwise. */ public static boolean isQuoted(String name) { - return ( name.startsWith( "`" ) && name.endsWith( "`" ) ) - || ( name.startsWith( "[" ) && name.endsWith( "]" ) ) - || ( name.startsWith( "\"" ) && name.endsWith( "\"" ) ); + return isQuoted( name, 0, name.length() ); + } + + public static boolean isQuoted(String name, int start, int end) { + if ( start + 2 < end ) { + switch ( name.charAt( start ) ) { + case '`': + return name.charAt( end - 1 ) == '`'; + case '[': + return name.charAt( end - 1 ) == ']'; + case '"': + return name.charAt( end - 1 ) == '"'; + } + } + return false; } public static String unQuote(String name) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java index c985404267..bd834a0039 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java @@ -260,7 +260,7 @@ public abstract class AbstractTransactSQLDialect extends Dialect { EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { return new LocalTemporaryTableStrategy( - new IdTable( entityDescriptor, basename -> "#" + basename, this ), + new IdTable( entityDescriptor, basename -> "#" + basename, this, runtimeModelCreationContext ), () -> new TempIdTableExporter( true, this::getTypeName ) { @Override protected String getCreateCommand() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java index 4749bce586..364a1a3516 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -34,6 +34,7 @@ import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.sqm.mutation.internal.cte.CteStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.service.ServiceRegistry; +import org.hibernate.sql.ast.SqlAstNodeRenderingMode; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.spi.SqlAppender; @@ -155,6 +156,9 @@ public class DB2Dialect extends Dialect { public void initializeFunctionRegistry(QueryEngine queryEngine) { super.initializeFunctionRegistry( queryEngine ); + // AVG by default uses the input type, so we possibly need to cast the argument type, hence a special function + CommonFunctionFactory.avg_castingNonDoubleArguments( this, queryEngine, SqlAstNodeRenderingMode.DEFAULT ); + CommonFunctionFactory.cot( queryEngine ); CommonFunctionFactory.degrees( queryEngine ); CommonFunctionFactory.log( queryEngine ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java index 75b77144c1..6c1d080c05 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java @@ -782,7 +782,12 @@ public class DerbyDialect extends Dialect { EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { return new LocalTemporaryTableStrategy( - new IdTable( rootEntityDescriptor, basename -> "session.HT_" + basename, this ), + new IdTable( + rootEntityDescriptor, + basename -> "session.HT_" + basename, + this, + runtimeModelCreationContext + ), () -> new TempIdTableExporter( true, this::getTypeName ) { @Override protected String getCreateCommand() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index 506e33b6b0..8ebe46ff17 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -513,7 +513,7 @@ public abstract class Dialect implements ConversionContext { //aggregate functions, supported on every database - CommonFunctionFactory.aggregates( this, queryEngine, SqlAstNodeRenderingMode.DEFAULT ); + CommonFunctionFactory.aggregates( this, queryEngine, SqlAstNodeRenderingMode.DEFAULT, "||", null ); //the ANSI SQL-defined aggregate functions any() and every() are only //supported on one database, but can be emulated using sum() and case, @@ -1834,7 +1834,7 @@ public abstract class Dialect implements ConversionContext { EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { return new PersistentTableStrategy( - new IdTable( entityDescriptor, name -> name, this ), + new IdTable( entityDescriptor, name -> name, this, runtimeModelCreationContext ), AfterUseAction.CLEAN, PhysicalIdTableExporter::new, runtimeModelCreationContext.getSessionFactory() diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java index c406a36aa5..6fef3a5d97 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -192,7 +192,9 @@ 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( this, queryEngine, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER ); + CommonFunctionFactory.aggregates( this, queryEngine, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER, "||", null ); + // AVG by default uses the input type, so we possibly need to cast the argument type, hence a special function + CommonFunctionFactory.avg_castingNonDoubleArguments( this, queryEngine, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER ); CommonFunctionFactory.pi( queryEngine ); CommonFunctionFactory.cot( queryEngine ); @@ -374,7 +376,7 @@ public class H2Dialect extends Dialect { EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { return new LocalTemporaryTableStrategy( - new IdTable( entityDescriptor, basename -> "HT_" + basename, this ), + new IdTable( entityDescriptor, basename -> "HT_" + basename, this, runtimeModelCreationContext ), this::getTypeName, AfterUseAction.CLEAN, TempTableDdlTransactionHandling.NONE, diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java index ac14d9de5b..f79c6309bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HANAColumnStoreDialect.java @@ -128,7 +128,7 @@ public class HANAColumnStoreDialect extends AbstractHANADialect { EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { return new GlobalTemporaryTableStrategy( - new IdTable( entityDescriptor, basename -> "HT_" + basename, this ), + new IdTable( entityDescriptor, basename -> "HT_" + basename, this, runtimeModelCreationContext ), () -> new PhysicalIdTableExporter() { @Override protected String getCreateCommand() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java index e2f3295b7a..0b0ec02181 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HANARowStoreDialect.java @@ -50,7 +50,7 @@ public class HANARowStoreDialect extends AbstractHANADialect { EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { return new GlobalTemporaryTableStrategy( - new IdTable( entityDescriptor, basename -> "HT_" + basename, this ), + new IdTable( entityDescriptor, basename -> "HT_" + basename, this, runtimeModelCreationContext ), () -> new PhysicalIdTableExporter() { @Override protected String getCreateCommand() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java index f75077fb2a..e6be5de46e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -55,6 +55,7 @@ import org.hibernate.query.sqm.mutation.internal.idtable.IdTable; import org.hibernate.query.sqm.mutation.internal.idtable.LocalTemporaryTableStrategy; import org.hibernate.query.sqm.mutation.internal.idtable.TempIdTableExporter; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.sql.ast.SqlAstNodeRenderingMode; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.spi.SqlAppender; @@ -155,6 +156,9 @@ public class HSQLDialect extends Dialect { public void initializeFunctionRegistry(QueryEngine queryEngine) { super.initializeFunctionRegistry( queryEngine ); + // AVG by default uses the input type, so we possibly need to cast the argument type, hence a special function + CommonFunctionFactory.avg_castingNonDoubleArguments( this, queryEngine, SqlAstNodeRenderingMode.DEFAULT ); + CommonFunctionFactory.cot( queryEngine ); CommonFunctionFactory.radians( queryEngine ); CommonFunctionFactory.degrees( queryEngine ); @@ -511,7 +515,7 @@ public class HSQLDialect extends Dialect { if ( version < 200 ) { return new GlobalTemporaryTableStrategy( - new IdTable( rootEntityDescriptor, name -> "HT_" + name, this ), + new IdTable( rootEntityDescriptor, name -> "HT_" + name, this, runtimeModelCreationContext ), () -> new TempIdTableExporter( false, this::getTypeName ), // Version 1.8 GLOBAL TEMPORARY table definitions persist beyond the end // of the session (by default, data is cleared at commit). @@ -523,7 +527,7 @@ public class HSQLDialect extends Dialect { return new LocalTemporaryTableStrategy( // With HSQLDB 2.0, the table name is qualified with MODULE to assist the drop // statement (in-case there is a global name beginning with HT_) - new IdTable( rootEntityDescriptor, name -> "MODULE.HT_" + name, this ), + new IdTable( rootEntityDescriptor, name -> "MODULE.HT_" + name, this, runtimeModelCreationContext ), () -> new TempIdTableExporter( true, this::getTypeName ) { @Override protected String getCreateCommand() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java index 60752eaca9..53a5252713 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -697,7 +697,7 @@ public class MySQLDialect extends Dialect { RuntimeModelCreationContext runtimeModelCreationContext) { return new LocalTemporaryTableStrategy( - new IdTable( rootEntityDescriptor, basename -> "HT_" + basename, this ), + new IdTable( rootEntityDescriptor, basename -> "HT_" + basename, this, runtimeModelCreationContext ), () -> new TempIdTableExporter( true, this::getTypeName ) { @Override protected String getCreateCommand() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 892576b7ac..b091ec5685 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -906,7 +906,8 @@ public class OracleDialect extends Dialect { new IdTable( rootEntityDescriptor, name -> "HT_" + ( name.length() > 27 ? name.substring( 0, 27 ) : name ), - this + this, + runtimeModelCreationContext ), () -> new TempIdTableExporter( false, this::getTypeName ) { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java index 9ed3879fc2..4f85e08b04 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -185,6 +185,9 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { // For SQL-Server we need to cast certain arguments to varchar(max) to be able to concat them CommonFunctionFactory.aggregates( this, queryEngine, SqlAstNodeRenderingMode.DEFAULT, "+", "varchar(max)" ); + // AVG by default uses the input type, so we possibly need to cast the argument type, hence a special function + CommonFunctionFactory.avg_castingNonDoubleArguments( this, queryEngine, SqlAstNodeRenderingMode.DEFAULT ); + CommonFunctionFactory.truncate_round( queryEngine ); CommonFunctionFactory.everyAny_sumIif( queryEngine ); CommonFunctionFactory.bitLength_pattern( queryEngine, "datalength(?1) * 8" ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java index 74a609b38e..98d51d00f2 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java @@ -301,7 +301,8 @@ public class SybaseASESqlAstTranslator extends Abstract @Override public void visitColumnReference(ColumnReference columnReference) { - if ( getDmlTargetTableAlias() != null && getDmlTargetTableAlias().equals( columnReference.getQualifier() ) ) { + final String dmlTargetTableAlias = getDmlTargetTableAlias(); + if ( dmlTargetTableAlias != null && dmlTargetTableAlias.equals( columnReference.getQualifier() ) ) { // Sybase needs a table name prefix // but not if this is a restricted union table reference subquery final QuerySpec currentQuerySpec = (QuerySpec) getQueryPartStack().getCurrent(); @@ -317,11 +318,11 @@ public class SybaseASESqlAstTranslator extends Abstract // This is fine for now as this is only temporary anyway until we render aliases for table references appendSql( columnReference.getColumnExpression() - .replaceAll( "(\\b)(" + getDmlTargetTableAlias() + "\\.)(\\b)", "$1$3" ) + .replaceAll( "(\\b)(" + dmlTargetTableAlias + "\\.)(\\b)", "$1$3" ) ); } else { - appendSql( ( (MutationStatement) getStatement() ).getTargetTable().getTableExpression() ); + appendSql( getCurrentDmlStatement().getTargetTable().getTableExpression() ); appendSql( '.' ); appendSql( columnReference.getColumnExpression() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java index de741dfed9..2aeacbbfa5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java @@ -211,6 +211,9 @@ public class SybaseDialect extends AbstractTransactSQLDialect { // For SQL-Server we need to cast certain arguments to varchar(16384) to be able to concat them CommonFunctionFactory.aggregates( this, queryEngine, SqlAstNodeRenderingMode.DEFAULT, "+", "varchar(16384)" ); + // AVG by default uses the input type, so we possibly need to cast the argument type, hence a special function + CommonFunctionFactory.avg_castingNonDoubleArguments( this, queryEngine, SqlAstNodeRenderingMode.DEFAULT ); + queryEngine.getSqmFunctionRegistry().register( "concat", new SybaseConcatFunction( this, queryEngine.getTypeConfiguration() ) ); //this doesn't work 100% on earlier versions of Sybase diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/AvgFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/AvgFunction.java new file mode 100644 index 0000000000..c4de5e3d2c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/AvgFunction.java @@ -0,0 +1,115 @@ +/* + * 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.Collections; +import java.util.List; + +import org.hibernate.dialect.Dialect; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.query.CastType; +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.produce.function.internal.PatternRenderer; +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.Distinct; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * @author Christian Beikov + */ +public class AvgFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + + public static final String FUNCTION_NAME = "avg"; + private final Dialect dialect; + private final SqlAstNodeRenderingMode defaultArgumentRenderingMode; + private final String doubleCastType; + + public AvgFunction( + Dialect dialect, + TypeConfiguration typeConfiguration, + SqlAstNodeRenderingMode defaultArgumentRenderingMode, + String doubleCastType) { + super( + FUNCTION_NAME, + FunctionKind.AGGREGATE, + StandardArgumentsValidators.exactly( 1 ), + StandardFunctionReturnTypeResolvers.invariant( + typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.DOUBLE ) + ) + ); + this.dialect = dialect; + this.defaultArgumentRenderingMode = defaultArgumentRenderingMode; + this.doubleCastType = doubleCastType; + } + + @Override + public void render(SqlAppender sqlAppender, List sqlAstArguments, SqlAstTranslator walker) { + render( sqlAppender, sqlAstArguments, null, walker ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + Predicate filter, + SqlAstTranslator translator) { + final boolean caseWrapper = filter != null && !translator.supportsFilterClause(); + sqlAppender.appendSql( "avg(" ); + final Expression arg; + if ( sqlAstArguments.get( 0 ) instanceof Distinct ) { + sqlAppender.appendSql( "distinct " ); + arg = ( (Distinct) sqlAstArguments.get( 0 ) ).getExpression(); + } + else { + arg = (Expression) sqlAstArguments.get( 0 ); + } + if ( caseWrapper ) { + sqlAppender.appendSql( "case when " ); + filter.accept( translator ); + sqlAppender.appendSql( " then " ); + renderArgument( sqlAppender, translator, arg ); + sqlAppender.appendSql( " else null end)" ); + } + else { + renderArgument( sqlAppender, translator, arg ); + sqlAppender.appendSql( ')' ); + if ( filter != null ) { + sqlAppender.appendSql( " filter (where " ); + filter.accept( translator ); + sqlAppender.appendSql( ')' ); + } + } + } + + private void renderArgument(SqlAppender sqlAppender, SqlAstTranslator translator, Expression realArg) { + final JdbcMapping sourceMapping = realArg.getExpressionType().getJdbcMappings().get( 0 ); + // Only cast to float/double if this is an integer + if ( sourceMapping.getJdbcTypeDescriptor().isInteger() ) { + final String cast = dialect.castPattern( sourceMapping.getCastType(), CastType.DOUBLE ); + new PatternRenderer( cast.replace( "?2", doubleCastType ) ) + .render( sqlAppender, Collections.singletonList( realArg ), translator ); + } + else { + translator.render( realArg, defaultArgumentRenderingMode ); + } + } + + @Override + public String getArgumentListSignature() { + return "(arg)"; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java index 4edb83fa8f..7ae35f6c84 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java @@ -27,6 +27,7 @@ import org.hibernate.sql.ast.SqlAstNodeRenderingMode; import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.type.BasicType; import org.hibernate.type.BasicTypeRegistry; +import org.hibernate.type.SqlTypes; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.spi.TypeConfiguration; @@ -1701,13 +1702,6 @@ public class CommonFunctionFactory { .register(); } - public static void aggregates( - Dialect dialect, - QueryEngine queryEngine, - SqlAstNodeRenderingMode inferenceArgumentRenderingMode) { - aggregates( dialect, queryEngine, inferenceArgumentRenderingMode, "||", null ); - } - public static void aggregates( Dialect dialect, QueryEngine queryEngine, @@ -1837,7 +1831,28 @@ public class CommonFunctionFactory { queryEngine.getSqmFunctionRegistry().register( CountFunction.FUNCTION_NAME, - new CountFunction( dialect, queryEngine.getTypeConfiguration(), concatOperator, concatArgumentCastType ) + new CountFunction( + dialect, + queryEngine.getTypeConfiguration(), + inferenceArgumentRenderingMode, + concatOperator, + concatArgumentCastType + ) + ); + } + + public static void avg_castingNonDoubleArguments( + Dialect dialect, + QueryEngine queryEngine, + SqlAstNodeRenderingMode inferenceArgumentRenderingMode) { + queryEngine.getSqmFunctionRegistry().register( + AvgFunction.FUNCTION_NAME, + new AvgFunction( + dialect, + queryEngine.getTypeConfiguration(), + inferenceArgumentRenderingMode, + dialect.getTypeName( SqlTypes.DOUBLE ) + ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CountFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CountFunction.java index 7ff3af3cc4..2f57272713 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CountFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CountFunction.java @@ -43,10 +43,16 @@ public class CountFunction extends AbstractSqmSelfRenderingFunctionDescriptor { public static final String FUNCTION_NAME = "count"; private final Dialect dialect; + private final SqlAstNodeRenderingMode defaultArgumentRenderingMode; private final String concatOperator; private final String concatArgumentCastType; - public CountFunction(Dialect dialect, TypeConfiguration typeConfiguration, String concatOperator, String concatArgumentCastType) { + public CountFunction( + Dialect dialect, + TypeConfiguration typeConfiguration, + SqlAstNodeRenderingMode defaultArgumentRenderingMode, + String concatOperator, + String concatArgumentCastType) { super( FUNCTION_NAME, FunctionKind.AGGREGATE, @@ -56,6 +62,7 @@ public class CountFunction extends AbstractSqmSelfRenderingFunctionDescriptor { ) ); this.dialect = dialect; + this.defaultArgumentRenderingMode = defaultArgumentRenderingMode; this.concatOperator = concatOperator; this.concatArgumentCastType = concatArgumentCastType; } @@ -159,11 +166,11 @@ public class CountFunction extends AbstractSqmSelfRenderingFunctionDescriptor { filter.accept( translator ); sqlAppender.appendSql( " and " ); } - translator.render( expressions.get( 0 ), SqlAstNodeRenderingMode.DEFAULT ); + translator.render( expressions.get( 0 ), defaultArgumentRenderingMode ); sqlAppender.appendSql( " is not null" ); for ( int i = 1; i < expressions.size(); i++ ) { sqlAppender.appendSql( " and " ); - translator.render( expressions.get( i ), SqlAstNodeRenderingMode.DEFAULT ); + translator.render( expressions.get( i ), defaultArgumentRenderingMode ); sqlAppender.appendSql( " is not null" ); } sqlAppender.appendSql( " then 1 else null end" ); @@ -219,7 +226,7 @@ public class CountFunction extends AbstractSqmSelfRenderingFunctionDescriptor { } // Rendering the tuple will add parenthesis around else if ( requiresParenthesis ) { - translator.render( tuple, SqlAstNodeRenderingMode.DEFAULT ); + translator.render( tuple, defaultArgumentRenderingMode ); } else { renderCommaSeparatedList( sqlAppender, translator, expressions ); @@ -230,10 +237,10 @@ public class CountFunction extends AbstractSqmSelfRenderingFunctionDescriptor { SqlAppender sqlAppender, SqlAstTranslator translator, List expressions) { - translator.render( expressions.get( 0 ), SqlAstNodeRenderingMode.DEFAULT ); + translator.render( expressions.get( 0 ), defaultArgumentRenderingMode ); for ( int i = 1; i < expressions.size(); i++ ) { sqlAppender.appendSql( ',' ); - translator.render( expressions.get( i ), SqlAstNodeRenderingMode.DEFAULT ); + translator.render( expressions.get( i ), defaultArgumentRenderingMode ); } } @@ -251,24 +258,24 @@ public class CountFunction extends AbstractSqmSelfRenderingFunctionDescriptor { sqlAppender.appendSql( "1" ); } else { - translator.render( realArg, SqlAstNodeRenderingMode.DEFAULT ); + translator.render( realArg, defaultArgumentRenderingMode ); } sqlAppender.appendSql( " else null end" ); } else { - translator.render( realArg, SqlAstNodeRenderingMode.DEFAULT ); + translator.render( realArg, defaultArgumentRenderingMode ); } } private void renderCastedArgument(SqlAppender sqlAppender, SqlAstTranslator translator, Expression realArg) { if ( concatArgumentCastType == null ) { - translator.render( realArg, SqlAstNodeRenderingMode.DEFAULT ); + translator.render( realArg, defaultArgumentRenderingMode ); } else { final JdbcMapping sourceMapping = realArg.getExpressionType().getJdbcMappings().get( 0 ); // No need to cast if we already have a string if ( sourceMapping.getCastType() == CastType.STRING ) { - translator.render( realArg, SqlAstNodeRenderingMode.DEFAULT ); + translator.render( realArg, defaultArgumentRenderingMode ); } else { final String cast = dialect.castPattern( sourceMapping.getCastType(), CastType.STRING ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CurrentFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CurrentFunction.java index 516810bf5d..d8d375041e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CurrentFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CurrentFunction.java @@ -46,4 +46,8 @@ public class CurrentFunction return ""; } + @Override + public boolean alwaysIncludesParentheses() { + return sql.indexOf( '(' ) != -1; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/NormalizingIdentifierHelperImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/NormalizingIdentifierHelperImpl.java index e7f8072f5b..2c95d6b8b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/NormalizingIdentifierHelperImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/NormalizingIdentifierHelperImpl.java @@ -91,7 +91,7 @@ public class NormalizingIdentifierHelperImpl implements IdentifierHelper { @Override public Identifier applyGlobalQuoting(String text) { - return Identifier.toIdentifier( text, globallyQuoteIdentifiers && !globallyQuoteIdentifiersSkipColumnDefinitions ); + return Identifier.toIdentifier( text, globallyQuoteIdentifiers && !globallyQuoteIdentifiersSkipColumnDefinitions, false ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/QuotingHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/QuotingHelper.java new file mode 100644 index 0000000000..55a57298ec --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/QuotingHelper.java @@ -0,0 +1,141 @@ +/* + * 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 . + */ +package org.hibernate.internal.util; + +public final class QuotingHelper { + + private QuotingHelper() { /* static methods only - hide constructor */ + } + + public static String unquoteIdentifier(String text) { + final int end = text.length() - 1; + assert text.charAt( 0 ) == '`' && text.charAt( end ) == '`'; + // Unquote a parsed quoted identifier and handle escape sequences + final StringBuilder sb = new StringBuilder( text.length() - 2 ); + for ( int i = 1; i < end; i++ ) { + char c = text.charAt( i ); + switch ( c ) { + case '\\': + if ( ( i + 1 ) < end ) { + char nextChar = text.charAt( ++i ); + switch ( nextChar ) { + case 'b': + c = '\b'; + break; + case 't': + c = '\t'; + break; + case 'n': + c = '\n'; + break; + case 'f': + c = '\f'; + break; + case 'r': + c = '\r'; + break; + case '\\': + c = '\\'; + break; + case '\'': + c = '\''; + break; + case '"': + c = '"'; + break; + case '`': + c = '`'; + break; + case 'u': + c = (char) Integer.parseInt( text.substring( i + 1, i + 5 ), 16 ); + i += 4; + break; + default: + sb.append( '\\' ); + c = nextChar; + break; + } + } + break; + default: + break; + } + sb.append( c ); + } + return sb.toString(); + } + + public static String unquoteStringLiteral(String text) { + assert text.length() > 1; + final int end = text.length() - 1; + final char delimiter = text.charAt( 0 ); + assert delimiter == text.charAt( end ); + // Unescape the parsed literal and handle escape sequences + final StringBuilder sb = new StringBuilder( text.length() - 2 ); + for ( int i = 1; i < end; i++ ) { + char c = text.charAt( i ); + switch ( c ) { + case '\'': + if ( delimiter == '\'' ) { + i++; + } + break; + case '"': + if ( delimiter == '"' ) { + i++; + } + break; + case '\\': + if ( ( i + 1 ) < end ) { + char nextChar = text.charAt( ++i ); + switch ( nextChar ) { + case 'b': + c = '\b'; + break; + case 't': + c = '\t'; + break; + case 'n': + c = '\n'; + break; + case 'f': + c = '\f'; + break; + case 'r': + c = '\r'; + break; + case '\\': + c = '\\'; + break; + case '\'': + c = '\''; + break; + case '"': + c = '"'; + break; + case '`': + c = '`'; + break; + case 'u': + c = (char) Integer.parseInt( text.substring( i + 1, i + 5 ), 16 ); + i += 4; + break; + default: + sb.append( '\\' ); + c = nextChar; + break; + } + } + break; + default: + break; + } + sb.append( c ); + } + return sb.toString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java index 2bd13df5d2..662c8a1bae 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/ArrayHelper.java @@ -29,7 +29,7 @@ public final class ArrayHelper { public static int indexOf(Object[] array, Object object) { for ( int i = 0; i < array.length; i++ ) { - if ( array[i].equals( object ) ) { + if ( object.equals( array[i] ) ) { return i; } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java index 2011028123..831abed172 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java @@ -6,6 +6,7 @@ */ package org.hibernate.loader.ast.internal; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -251,7 +252,7 @@ public class LoaderSelectBuilder { private final EntityGraphTraversalState entityGraphTraversalState; private int fetchDepth; - private Map orderByFragments; + private List> orderByFragments; private boolean hasCollectionJoinFetches; private String currentBagRole; @@ -483,9 +484,9 @@ public class LoaderSelectBuilder { if ( orderByFragments != null ) { orderByFragments.forEach( - (orderByFragment, tableGroup) -> orderByFragment.apply( + entry -> entry.getKey().apply( rootQuerySpec, - tableGroup, + entry.getValue(), sqlAstCreationState ) ); @@ -652,9 +653,9 @@ public class LoaderSelectBuilder { private void applyOrdering(TableGroup tableGroup, OrderByFragment orderByFragment) { if ( orderByFragments == null ) { - orderByFragments = new LinkedHashMap<>(); + orderByFragments = new ArrayList<>(); } - orderByFragments.put( orderByFragment, tableGroup ); + orderByFragments.add( new AbstractMap.SimpleEntry<>( orderByFragment, tableGroup ) ); } private List visitFetches( @@ -1010,13 +1011,14 @@ public class LoaderSelectBuilder { if ( jdbcTypeCount == 1 ) { assert fkDescriptor instanceof SimpleForeignKeyDescriptor; final SimpleForeignKeyDescriptor simpleFkDescriptor = (SimpleForeignKeyDescriptor) fkDescriptor; + final TableReference tableReference = rootTableGroup.resolveTableReference( + navigablePath, + simpleFkDescriptor.getContainingTableExpression() + ); fkExpression = sqlAstCreationState.getSqlExpressionResolver().resolveSqlExpression( - createColumnReferenceKey( - simpleFkDescriptor.getContainingTableExpression(), - simpleFkDescriptor.getSelectionExpression() - ), + createColumnReferenceKey( tableReference, simpleFkDescriptor.getSelectionExpression() ), sqlAstProcessingState -> new ColumnReference( - rootTableGroup.resolveTableReference( navigablePath, simpleFkDescriptor.getContainingTableExpression() ), + tableReference, simpleFkDescriptor.getSelectionExpression(), false, null, @@ -1029,21 +1031,26 @@ public class LoaderSelectBuilder { else { final List columnReferences = new ArrayList<>( jdbcTypeCount ); fkDescriptor.forEachSelectable( - (columnIndex, selection) -> - columnReferences.add( - (ColumnReference) sqlAstCreationState.getSqlExpressionResolver() - .resolveSqlExpression( - createColumnReferenceKey( - selection.getContainingTableExpression(), - selection.getSelectionExpression() - ), - sqlAstProcessingState -> new ColumnReference( - rootTableGroup.resolveTableReference( navigablePath, selection.getContainingTableExpression() ), - selection, - this.creationContext.getSessionFactory() - ) - ) - ) + (columnIndex, selection) -> { + final TableReference tableReference = rootTableGroup.resolveTableReference( + navigablePath, + selection.getContainingTableExpression() + ); + columnReferences.add( + (ColumnReference) sqlAstCreationState.getSqlExpressionResolver() + .resolveSqlExpression( + createColumnReferenceKey( + tableReference, + selection.getSelectionExpression() + ), + sqlAstProcessingState -> new ColumnReference( + tableReference, + selection, + this.creationContext.getSessionFactory() + ) + ) + ); + } ); fkExpression = new SqlTuple( columnReferences, fkDescriptor ); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java index 9adbb551c9..eba0d74664 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java @@ -542,6 +542,22 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl } } + public Property getSubclassProperty(String propertyName) throws MappingException { + Property identifierProperty = getIdentifierProperty(); + if ( identifierProperty != null + && identifierProperty.getName().equals( StringHelper.root( propertyName ) ) ) { + return identifierProperty; + } + else { + Iterator iter = getSubclassPropertyClosureIterator(); + Component identifierMapper = getIdentifierMapper(); + if ( identifierMapper != null ) { + iter = new JoinedIterator<>( identifierMapper.getPropertyIterator(), iter ); + } + return getProperty( propertyName, iter ); + } + } + /** * Check to see if this PersistentClass defines a property with the given name. * diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionPart.java index e6e7240c04..a75c049739 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionPart.java @@ -7,11 +7,12 @@ package org.hibernate.metamodel.mapping; import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.type.descriptor.java.JavaTypedExpressable; /** * @author Steve Ebersole */ -public interface CollectionPart extends ModelPart, Fetchable { +public interface CollectionPart extends ModelPart, Fetchable, JavaTypedExpressable { enum Nature { ELEMENT( "{element}" ), INDEX( "{index}" ), diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/MappingModelHelper.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/MappingModelHelper.java index c7bf1633f2..6a90ab0a10 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/MappingModelHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/MappingModelHelper.java @@ -57,7 +57,7 @@ public class MappingModelHelper { } else { colRef = (ColumnReference) sqlExpressionResolver.resolveSqlExpression( - createColumnReferenceKey( selection.getContainingTableExpression(), selection.getSelectionExpression() ), + createColumnReferenceKey( qualifier, selection.getSelectionExpression() ), sqlAstProcessingState -> new ColumnReference( qualifier, selection, @@ -89,7 +89,7 @@ public class MappingModelHelper { } else { return sqlExpressionResolver.resolveSqlExpression( - createColumnReferenceKey( basicPart.getContainingTableExpression(), basicPart.getSelectionExpression() ), + createColumnReferenceKey( qualifier, basicPart.getSelectionExpression() ), sqlAstProcessingState -> new ColumnReference( qualifier, basicPart, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDomainPath.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDomainPath.java index 33e68fd1bf..13fef1699f 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDomainPath.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDomainPath.java @@ -61,7 +61,7 @@ public abstract class AbstractDomainPath implements DomainPath { final TableReference tableReference = tableGroup.resolveTableReference( getNavigablePath(), selection.getContainingTableExpression() ); return creationState.getSqlExpressionResolver().resolveSqlExpression( SqlExpressionResolver.createColumnReferenceKey( - selection.getContainingTableExpression(), + tableReference, selection.getSelectionExpression() ), sqlAstProcessingState -> new ColumnReference( @@ -247,7 +247,7 @@ public abstract class AbstractDomainPath implements DomainPath { final TableReference tableReference = tableGroup.resolveTableReference( getNavigablePath(), selection.getContainingTableExpression() ); final Expression expression = creationState.getSqlExpressionResolver().resolveSqlExpression( SqlExpressionResolver.createColumnReferenceKey( - selection.getContainingTableExpression(), + tableReference, selection.getSelectionExpression() ), sqlAstProcessingState -> new ColumnReference( diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java index 28c9d2b7db..2d590ef7c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java @@ -10,7 +10,6 @@ import java.io.Serializable; import java.util.function.BiConsumer; import java.util.function.Consumer; -import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.SharedSessionContract; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; @@ -33,8 +32,17 @@ import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.query.NavigablePath; import org.hibernate.sql.ast.Clause; +import org.hibernate.sql.ast.SqlAstJoinType; +import org.hibernate.sql.ast.spi.FromClauseAccess; +import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; +import org.hibernate.sql.ast.spi.SqlAstCreationContext; +import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.ast.tree.from.StandardVirtualTableGroup; import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.from.TableGroupJoin; +import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer; +import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; @@ -53,7 +61,7 @@ import org.hibernate.type.descriptor.java.MutabilityPlan; */ public class DiscriminatedAssociationAttributeMapping extends AbstractSingularAttributeMapping - implements DiscriminatedAssociationModelPart { + implements DiscriminatedAssociationModelPart, TableGroupJoinProducer { private final NavigableRole navigableRole; private final DiscriminatedAssociationMapping discriminatorMapping; private final SessionFactoryImplementor sessionFactory; @@ -141,17 +149,24 @@ public class DiscriminatedAssociationAttributeMapping TableGroup tableGroup, String resultVariable, DomainResultCreationState creationState) { - throw new NotYetImplementedFor6Exception( getClass() ); + return discriminatorMapping.createDomainResult( + navigablePath, + tableGroup, + resultVariable, + creationState + ); } @Override public void applySqlSelections(NavigablePath navigablePath, TableGroup tableGroup, DomainResultCreationState creationState) { - throw new NotYetImplementedFor6Exception( getClass() ); + discriminatorMapping.getDiscriminatorPart().applySqlSelections( navigablePath, tableGroup, creationState ); + discriminatorMapping.getKeyPart().applySqlSelections( navigablePath, tableGroup, creationState ); } @Override public void applySqlSelections(NavigablePath navigablePath, TableGroup tableGroup, DomainResultCreationState creationState, BiConsumer selectionConsumer) { - throw new NotYetImplementedFor6Exception( getClass() ); + discriminatorMapping.getDiscriminatorPart().applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer ); + discriminatorMapping.getKeyPart().applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer ); } @Override @@ -361,4 +376,62 @@ public class DiscriminatedAssociationAttributeMapping return anyType.assemble( cached, persistenceContext, null ); } } + + @Override + public TableGroupJoin createTableGroupJoin( + NavigablePath navigablePath, + TableGroup lhs, + String explicitSourceAlias, + SqlAstJoinType sqlAstJoinType, + boolean fetched, + boolean addsPredicate, + SqlAliasBaseGenerator aliasBaseGenerator, + SqlExpressionResolver sqlExpressionResolver, + FromClauseAccess fromClauseAccess, + SqlAstCreationContext creationContext) { + final TableGroup tableGroup = createRootTableGroupJoin( + navigablePath, + lhs, + explicitSourceAlias, + sqlAstJoinType, + fetched, + null, + aliasBaseGenerator, + sqlExpressionResolver, + fromClauseAccess, + creationContext + ); + + return new TableGroupJoin( navigablePath, sqlAstJoinType, tableGroup ); + } + + @Override + public TableGroup createRootTableGroupJoin( + NavigablePath navigablePath, + TableGroup lhs, + String explicitSourceAlias, + SqlAstJoinType sqlAstJoinType, + boolean fetched, + Consumer predicateConsumer, + SqlAliasBaseGenerator aliasBaseGenerator, + SqlExpressionResolver sqlExpressionResolver, + FromClauseAccess fromClauseAccess, + SqlAstCreationContext creationContext) { + return new StandardVirtualTableGroup( + navigablePath, + this, + lhs, + fetched + ); + } + + @Override + public SqlAstJoinType getDefaultSqlAstJoinType(TableGroup parentTableGroup) { + return SqlAstJoinType.LEFT; + } + + @Override + public String getSqlAliasStem() { + return getAttributeName(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java index fc655ba2d1..7a12f12d0a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java @@ -144,6 +144,11 @@ public class DiscriminatedCollectionPart implements DiscriminatedAssociationMode return discriminatorMapping.getJavaTypeDescriptor(); } + @Override + public JavaType getExpressableJavaTypeDescriptor() { + return getJavaTypeDescriptor(); + } + @Override public NavigableRole getNavigableRole() { return partRole; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java index 9f405cdf27..67c706f681 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java @@ -42,6 +42,7 @@ import org.hibernate.sql.ast.tree.from.PluralTableGroup; import org.hibernate.sql.ast.tree.from.StandardVirtualTableGroup; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroupJoin; +import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; @@ -199,15 +200,16 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF getEmbeddableTypeDescriptor().forEachSelectable( (columnIndex, selection) -> { assert containingTableExpression.equals( selection.getContainingTableExpression() ); + final TableReference tableReference = tableGroup.resolveTableReference( + tableGroup.getNavigablePath() + .append( getNavigableRole().getNavigableName() ), + selection.getContainingTableExpression() + ); expressions.add( sqlExpressionResolver.resolveSqlExpression( - SqlExpressionResolver.createColumnReferenceKey( selection.getContainingTableExpression(), selection.getSelectionExpression() ), + SqlExpressionResolver.createColumnReferenceKey( tableReference, selection.getSelectionExpression() ), sqlAstProcessingState -> new ColumnReference( - tableGroup.resolveTableReference( - tableGroup.getNavigablePath() - .append( getNavigableRole().getNavigableName() ), - selection.getContainingTableExpression() - ), + tableReference, selection, sqlAstCreationState.getCreationContext().getSessionFactory() ) @@ -300,6 +302,11 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF return getEmbeddableTypeDescriptor().getJavaTypeDescriptor(); } + @Override + public JavaType getExpressableJavaTypeDescriptor() { + return getJavaTypeDescriptor(); + } + @Override public NavigableRole getNavigableRole() { return navigableRole; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java index 73d07f94d1..375317d3dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java @@ -359,6 +359,11 @@ public class EntityCollectionPart return getEntityMappingType().getJavaTypeDescriptor(); } + @Override + public JavaType getExpressableJavaTypeDescriptor() { + return getJavaTypeDescriptor(); + } + @Override public NavigableRole getNavigableRole() { return navigableRole; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationProcess.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationProcess.java index 9e4d739696..a0c32cb2f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationProcess.java @@ -10,7 +10,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.metamodel.mapping.MappingModelCreationLogger; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.NonTransientException; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java index 2b76338f7d..81a16f26e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java @@ -31,6 +31,7 @@ import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.Queryable; import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess; import org.hibernate.metamodel.mapping.ordering.OrderByFragment; import org.hibernate.metamodel.mapping.ordering.OrderByFragmentTranslator; @@ -212,7 +213,7 @@ public class PluralAttributeMappingImpl final boolean hasManyToManyOrder = bootDescriptor.getManyToManyOrdering() != null; if ( hasOrder || hasManyToManyOrder ) { - final TranslationContext context = () -> collectionDescriptor.getFactory().getSessionFactoryOptions().getJpaCompliance(); + final TranslationContext context = collectionDescriptor::getFactory; if ( hasOrder ) { if ( log.isDebugEnabled() ) { @@ -796,29 +797,22 @@ public class PluralAttributeMappingImpl @Override public ModelPart findSubPart(String name, EntityMappingType treatTargetType) { + if ( elementDescriptor instanceof Queryable ) { + final ModelPart subPart = ( (Queryable) elementDescriptor ).findSubPart( name, null ); + if ( subPart != null ) { + return subPart; + } + } final CollectionPart.Nature nature = CollectionPart.Nature.fromName( name ); - if ( nature == CollectionPart.Nature.ELEMENT ) { - return elementDescriptor; - } - - if ( nature == CollectionPart.Nature.INDEX ) { - return indexDescriptor; - } - - if ( nature == CollectionPart.Nature.ID ) { - return identifierDescriptor; - } - - if ( elementDescriptor instanceof EntityCollectionPart ) { - return ( (EntityCollectionPart) elementDescriptor ).findSubPart( name ); - } - - if ( elementDescriptor instanceof EmbeddedCollectionPart ) { - return ( (EmbeddedCollectionPart) elementDescriptor ).findSubPart( name, treatTargetType ); - } - - if ( elementDescriptor instanceof DiscriminatedCollectionPart ) { - return ( (DiscriminatedCollectionPart) elementDescriptor ).findSubPart( name, treatTargetType ); + if ( nature != null ) { + switch ( nature ) { + case ELEMENT: + return elementDescriptor; + case INDEX: + return indexDescriptor; + case ID: + return identifierDescriptor; + } } return null; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/TranslationContext.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/TranslationContext.java index b019eeca74..1ac07a5471 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/TranslationContext.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/TranslationContext.java @@ -6,6 +6,7 @@ */ package org.hibernate.metamodel.mapping.ordering; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.jpa.spi.JpaCompliance; /** @@ -14,5 +15,10 @@ import org.hibernate.jpa.spi.JpaCompliance; * @author Steve Ebersole */ public interface TranslationContext { - JpaCompliance getJpaCompliance(); + + SessionFactoryImplementor getFactory(); + + default JpaCompliance getJpaCompliance() { + return getFactory().getSessionFactoryOptions().getJpaCompliance(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/CollectionPartPath.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/CollectionPartPath.java index 349db44d7b..e24deaaaea 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/CollectionPartPath.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/CollectionPartPath.java @@ -56,6 +56,7 @@ public class CollectionPartPath extends AbstractDomainPath { @Override public SequencePart resolvePathPart( String name, + String identifier, boolean isTerminal, TranslationContext translationContext) { if ( referencedPart instanceof EmbeddedCollectionPart ) { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ColumnReference.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ColumnReference.java index 77fe2193a0..dd5fb6f5c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ColumnReference.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ColumnReference.java @@ -34,12 +34,10 @@ import org.hibernate.sql.ast.tree.select.SortSpecification; public class ColumnReference implements OrderingExpression, SequencePart { private final String columnExpression; private final boolean isColumnExpressionFormula; - private final NavigablePath rootPath; - public ColumnReference(String columnExpression, boolean isColumnExpressionFormula, NavigablePath rootPath) { + public ColumnReference(String columnExpression, boolean isColumnExpressionFormula) { this.columnExpression = columnExpression; this.isColumnExpressionFormula = isColumnExpressionFormula; - this.rootPath = rootPath; } public String getColumnExpression() { @@ -50,14 +48,6 @@ public class ColumnReference implements OrderingExpression, SequencePart { return isColumnExpressionFormula; } - @Override - public SequencePart resolvePathPart( - String name, - boolean isTerminal, - TranslationContext translationContext) { - throw new UnsupportedMappingException( "ColumnReference cannot be de-referenced" ); - } - @Override public Expression resolve( QuerySpec ast, @@ -85,6 +75,15 @@ public class ColumnReference implements OrderingExpression, SequencePart { ); } + @Override + public SequencePart resolvePathPart( + String name, + String identifier, + boolean isTerminal, + TranslationContext translationContext) { + throw new UnsupportedMappingException( "ColumnReference cannot be de-referenced" ); + } + @Override public void apply( QuerySpec ast, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/DomainPathContinuation.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/DomainPathContinuation.java index 8f91472dc3..daf6c9b0b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/DomainPathContinuation.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/DomainPathContinuation.java @@ -48,6 +48,7 @@ public class DomainPathContinuation extends AbstractDomainPath { @Override public SequencePart resolvePathPart( String name, + String identifier, boolean isTerminal, TranslationContext translationContext) { if ( referencedModelPart instanceof EmbeddableValuedModelPart ) { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/FkDomainPathContinuation.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/FkDomainPathContinuation.java index d95a8c8a74..d94e60ab00 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/FkDomainPathContinuation.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/FkDomainPathContinuation.java @@ -35,7 +35,11 @@ public class FkDomainPathContinuation extends DomainPathContinuation { } @Override - public SequencePart resolvePathPart(String name, boolean isTerminal, TranslationContext translationContext) { + public SequencePart resolvePathPart( + String name, + String identifier, + boolean isTerminal, + TranslationContext translationContext) { HashSet furtherPaths = new LinkedHashSet<>( possiblePaths.size() ); for ( String path : possiblePaths ) { if ( !path.startsWith( name ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/OrderingSpecification.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/OrderingSpecification.java index 3a9f8fde21..2dc26c4f5d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/OrderingSpecification.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/OrderingSpecification.java @@ -22,20 +22,11 @@ public class OrderingSpecification implements Node { private NullPrecedence nullPrecedence = NullPrecedence.NONE; private String orderByValue; - public String getOrderByValue() { - return orderByValue; - } - public OrderingSpecification(OrderingExpression orderingExpression, String orderByValue) { this.orderingExpression = orderingExpression; this.orderByValue = orderByValue; } - public OrderingSpecification(OrderingExpression orderingExpression, String orderByValue,SortOrder sortOrder) { - this(orderingExpression, orderByValue); - this.sortOrder = sortOrder; - } - public OrderingExpression getExpression() { return orderingExpression; } @@ -64,6 +55,10 @@ public class OrderingSpecification implements Node { this.nullPrecedence = nullPrecedence; } + public String getOrderByValue() { + return orderByValue; + } + public void setOrderByValue(String orderByValue) { this.orderByValue = orderByValue; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ParseTreeVisitor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ParseTreeVisitor.java index a374dffe83..5921f2ca59 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ParseTreeVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ParseTreeVisitor.java @@ -10,13 +10,17 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.hibernate.internal.util.QuotingHelper; +import org.hibernate.query.NullPrecedence; import org.hibernate.query.SortOrder; import org.hibernate.grammars.ordering.OrderingParser; -import org.hibernate.grammars.ordering.OrderingParser.ExpressionContext; import org.hibernate.grammars.ordering.OrderingParserBaseVisitor; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.ordering.TranslationContext; +import org.hibernate.query.sqm.ParsingException; +import org.hibernate.query.sqm.function.SqmFunctionDescriptor; +import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; /** @@ -34,18 +38,19 @@ public class ParseTreeVisitor extends OrderingParserBaseVisitor { } @Override - public List visitOrderByFragment(OrderingParser.OrderByFragmentContext parsedFragment) { - final List parsedSortSpecifications = parsedFragment.sortSpecification(); - assert parsedSortSpecifications != null; + public List visitOrderByFragment(OrderingParser.OrderByFragmentContext ctx) { + final int size = ctx.getChildCount(); + // Shift 1 bit instead of division by 2 + final int specificationCount = ( size + 1 ) >> 1; - if ( parsedSortSpecifications.size() == 1 ) { - return Collections.singletonList( visitSortSpecification( parsedSortSpecifications.get( 0 ) ) ); + if ( specificationCount == 1 ) { + return Collections.singletonList( visitSortSpecification( (OrderingParser.SortSpecificationContext) ctx.getChild( 0 ) ) ); } - final List specifications = new ArrayList<>( parsedSortSpecifications.size() ); + final List specifications = new ArrayList<>( specificationCount ); - for ( OrderingParser.SortSpecificationContext parsedSortSpecification : parsedSortSpecifications ) { - specifications.add( visitSortSpecification( parsedSortSpecification ) ); + for ( int i = 0; i < size; i += 2 ) { + specifications.add( visitSortSpecification( (OrderingParser.SortSpecificationContext) ctx.getChild( i ) ) ); } return specifications; @@ -56,7 +61,7 @@ public class ParseTreeVisitor extends OrderingParserBaseVisitor { assert parsedSpec != null; assert parsedSpec.expression() != null; - final OrderingExpression orderingExpression = visitExpression( parsedSpec.expression() ); + final OrderingExpression orderingExpression = (OrderingExpression) parsedSpec.getChild( 0 ).accept( this ); if ( translationContext.getJpaCompliance().isJpaOrderByMappingComplianceEnabled() ) { if ( orderingExpression instanceof DomainPath ) { // nothing to do @@ -70,85 +75,157 @@ public class ParseTreeVisitor extends OrderingParserBaseVisitor { } } - final OrderingSpecification result = new OrderingSpecification( orderingExpression, parsedSpec.expression().getText()); + final OrderingSpecification result = new OrderingSpecification( orderingExpression, parsedSpec.getChild( 0 ).getText() ); + int i = 1; - if ( parsedSpec.collationSpecification() != null ) { - result.setCollation( parsedSpec.collationSpecification().identifier().getText() ); + if ( parsedSpec.getChildCount() > i ) { + final ParseTree parseTree = parsedSpec.getChild( i ); + if ( parseTree instanceof OrderingParser.CollationSpecificationContext ) { + result.setCollation( (String) parseTree.getChild( 1 ).getChild( 0 ).accept( this ) ); + i++; + } } - - if ( parsedSpec.direction() != null && parsedSpec.direction().DESC() != null ) { - result.setSortOrder( SortOrder.DESCENDING ); + if ( parsedSpec.getChildCount() > i ) { + final ParseTree parseTree = parsedSpec.getChild( i ); + if ( parseTree instanceof OrderingParser.DirectionContext ) { + final OrderingParser.DirectionContext directionCtx = (OrderingParser.DirectionContext) parseTree; + if ( ( (TerminalNode) directionCtx.getChild( 0 ) ).getSymbol().getType() == OrderingParser.ASC ) { + result.setSortOrder( SortOrder.ASCENDING ); + } + else { + result.setSortOrder( SortOrder.DESCENDING ); + } + i++; + } } - else { - result.setSortOrder( SortOrder.ASCENDING ); + if ( parsedSpec.getChildCount() > i ) { + final ParseTree parseTree = parsedSpec.getChild( i ); + if ( parseTree instanceof OrderingParser.NullsPrecedenceContext ) { + final OrderingParser.NullsPrecedenceContext nullsCtx = (OrderingParser.NullsPrecedenceContext) parseTree; + if ( ( (TerminalNode) nullsCtx.getChild( 1 ) ).getSymbol().getType() == OrderingParser.FIRST ) { + result.setNullPrecedence( NullPrecedence.FIRST ); + } + else { + result.setNullPrecedence( NullPrecedence.LAST ); + } + } } - // todo (6.0) : null-precedence (see grammar notes) - return result; } @Override - public OrderingExpression visitExpression(ExpressionContext ctx) { - if ( ctx.function() != null ) { - return visitFunction( ctx.function() ); - } + public OrderingExpression visitFunctionExpression(OrderingParser.FunctionExpressionContext ctx) { + return visitFunction( (OrderingParser.FunctionContext) ctx.getChild( 0 ) ); + } - if ( ctx.identifier() != null ) { - pathConsumer.consumeIdentifier( ctx.identifier().getText(), true, true ); - return (OrderingExpression) pathConsumer.getConsumedPart(); - } + @Override + public OrderingExpression visitIdentifierExpression(OrderingParser.IdentifierExpressionContext ctx) { + return visitIdentifier( (OrderingParser.IdentifierContext) ctx.getChild( 0 ) ); + } - assert ctx.dotIdentifier() != null; - final int numberOfParts = ctx.dotIdentifier().IDENTIFIER().size(); - boolean firstPass = true; + @Override + public OrderingExpression visitDotIdentifierExpression(OrderingParser.DotIdentifierExpressionContext ctx) { + return visitDotIdentifier( (OrderingParser.DotIdentifierContext) ctx.getChild( 0 ) ); + } - for ( int i = 0; i < numberOfParts; i++ ) { - final TerminalNode partNode = ctx.dotIdentifier().IDENTIFIER().get( i ); + @Override + public OrderingExpression visitDotIdentifier(OrderingParser.DotIdentifierContext ctx) { + final int size = ctx.getChildCount(); + final int end = size - 1; + // For nested paths, which must be on the domain model, we don't care about the possibly quoted identifier, + // so we just pass the unquoted one + String partName = (String) ctx.getChild( 0 ).getChild( 0 ).accept( this ); + pathConsumer.consumeIdentifier( + partName, + partName, + true, + false + ); + + for ( int i = 2; i < end; i += 2 ) { + partName = (String) ctx.getChild( i ).getChild( 0 ).accept( this ); pathConsumer.consumeIdentifier( - partNode.getText(), - firstPass, - true + partName, + partName, + false, + false ); - firstPass = false; } + partName = (String) ctx.getChild( end ).getChild( 0 ).accept( this ); + pathConsumer.consumeIdentifier( + partName, + partName, + false, + true + ); return (OrderingExpression) pathConsumer.getConsumedPart(); } @Override - public FunctionExpression visitFunction(OrderingParser.FunctionContext ctx) { - if ( ctx.simpleFunction() != null ) { - final FunctionExpression function = new FunctionExpression( - ctx.simpleFunction().identifier().getText(), - ctx.simpleFunction().functionArguments().expression().size() - ); - - for ( int i = 0; i < ctx.simpleFunction().functionArguments().expression().size(); i++ ) { - final ExpressionContext arg = ctx.simpleFunction().functionArguments().expression( i ); - function.addArgument( visitExpression( arg ) ); - } - - return function; + public OrderingExpression visitIdentifier(OrderingParser.IdentifierContext ctx) { + final String unquotedIdentifier = (String) ctx.getChild( 0 ).accept( this ); + final SqmFunctionDescriptor descriptor = translationContext.getFactory() + .getQueryEngine() + .getSqmFunctionRegistry() + .findFunctionDescriptor( unquotedIdentifier ); + // If there is no function with this name, it always requires parenthesis or if this is a quoted identifiers + // then we interpret this as a path part instead of as function + final String identifier = ctx.getChild( 0 ).getText(); + if ( descriptor == null || descriptor.alwaysIncludesParentheses() || !unquotedIdentifier.equals( identifier ) ) { + pathConsumer.consumeIdentifier( unquotedIdentifier, identifier, true, true ); + return (OrderingExpression) pathConsumer.getConsumedPart(); } + return new SelfRenderingOrderingExpression( unquotedIdentifier ); + } - assert ctx.packagedFunction() != null; - + @Override + public FunctionExpression visitFunction(OrderingParser.FunctionContext ctx) { + final ParseTree functionCtx = ctx.getChild( 0 ); + final OrderingParser.FunctionArgumentsContext argumentsCtx = (OrderingParser.FunctionArgumentsContext) functionCtx.getChild( 1 ); + final int size = argumentsCtx.getChildCount(); + // Shift 1 bit instead of division by 2 + final int expressionsCount = ( ( size - 1 ) >> 1 ); final FunctionExpression function = new FunctionExpression( - ctx.packagedFunction().dotIdentifier().getText(), - ctx.packagedFunction().functionArguments().expression().size() + functionCtx.getChild( 0 ).getText(), + expressionsCount ); - for ( int i = 0; i < ctx.packagedFunction().functionArguments().expression().size(); i++ ) { - final ExpressionContext arg = ctx.packagedFunction().functionArguments().expression( i ); - function.addArgument( visitExpression( arg ) ); + for ( int i = 1; i < size; i += 2 ) { + function.addArgument( (OrderingExpression) argumentsCtx.getChild( i ).accept( this ) ); } return function; } + @Override + public OrderingExpression visitFunctionArgument(OrderingParser.FunctionArgumentContext ctx) { + return (OrderingExpression) ctx.getChild( 0 ).accept( this ); + } + + @Override + public OrderingExpression visitLiteral(OrderingParser.LiteralContext ctx) { + return new SelfRenderingOrderingExpression( ctx.getText() ); + } + @Override public String visitCollationSpecification(OrderingParser.CollationSpecificationContext ctx) { throw new IllegalStateException( "Unexpected call to #visitCollationSpecification" ); } + + @Override + public Object visitTerminal(TerminalNode node) { + if ( node.getSymbol().getType() == OrderingParser.EOF ) { + return null; + } + switch ( node.getSymbol().getType() ) { + case OrderingParser.IDENTIFIER: + return node.getText(); + case OrderingParser.QUOTED_IDENTIFIER: + return QuotingHelper.unquoteIdentifier( node.getText() ); + default: + throw new ParsingException( "Unexpected terminal node [" + node.getText() + "]"); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/PathConsumer.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/PathConsumer.java index 21c765a39b..33078c1c7a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/PathConsumer.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/PathConsumer.java @@ -26,7 +26,7 @@ public class PathConsumer { private final SequencePart rootSequencePart; - private String pathSoFar; + private StringBuilder pathSoFar = new StringBuilder(); private SequencePart currentPart; public PathConsumer( @@ -41,32 +41,33 @@ public class PathConsumer { return currentPart; } - public void consumeIdentifier(String identifier, boolean isBase, boolean isTerminal) { + public void consumeIdentifier( + String unquotedIdentifier, + String identifier, boolean isBase, + boolean isTerminal) { if ( isBase ) { // each time we start a new sequence we need to reset our state reset(); } - if ( pathSoFar == null ) { - pathSoFar = identifier; - } - else { - pathSoFar += ( '.' + identifier ); + if ( pathSoFar.length() != 0 ) { + pathSoFar.append( '.' ); } + pathSoFar.append( unquotedIdentifier ); log.tracef( "BasicDotIdentifierHandler#consumeIdentifier( %s, %s, %s ) - %s", - identifier, + unquotedIdentifier, isBase, isTerminal, pathSoFar ); - currentPart = currentPart.resolvePathPart( identifier, isTerminal, translationContext ); + currentPart = currentPart.resolvePathPart( unquotedIdentifier, identifier, isTerminal, translationContext ); } private void reset() { - pathSoFar = null; + pathSoFar.setLength( 0 ); currentPart = rootSequencePart; } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/PluralAttributePath.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/PluralAttributePath.java index b13dfddf9e..243d5c1fd3 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/PluralAttributePath.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/PluralAttributePath.java @@ -48,6 +48,7 @@ public class PluralAttributePath extends AbstractDomainPath { @Override public DomainPath resolvePathPart( String name, + String identifier, boolean isTerminal, TranslationContext translationContext) { final ModelPart subPart = pluralAttributeMapping.findSubPart( name, null ); @@ -60,8 +61,11 @@ public class PluralAttributePath extends AbstractDomainPath { return new DomainPathContinuation( navigablePath.append( name ), this, subPart ); } if ( subPart instanceof ToOneAttributeMapping ) { - return new FkDomainPathContinuation( navigablePath.append( name ), this, - (ToOneAttributeMapping) subPart ); + return new FkDomainPathContinuation( + navigablePath.append( name ), + this, + (ToOneAttributeMapping) subPart + ); } // leaf case: diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/RootSequencePart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/RootSequencePart.java index e31249a901..c34867c621 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/RootSequencePart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/RootSequencePart.java @@ -26,18 +26,30 @@ public class RootSequencePart implements SequencePart { @Override public SequencePart resolvePathPart( String name, + String identifier, boolean isTerminal, TranslationContext translationContext) { // could be a column-reference (isTerminal would have to be true) or a domain-path - final DomainPath subDomainPath = pluralAttributePath.resolvePathPart( name, isTerminal, translationContext ); + final DomainPath subDomainPath = pluralAttributePath.resolvePathPart( + name, + identifier, + isTerminal, + translationContext + ); if ( subDomainPath != null ) { return subDomainPath; } if ( isTerminal ) { // assume a column-reference - return new ColumnReference( name, false, pluralAttributePath.getNavigablePath() ); + return new ColumnReference( + translationContext.getFactory() + .getJdbcServices() + .getDialect() + .quote( identifier ), + false + ); } throw new PathResolutionException( diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/SelfRenderingOrderingExpression.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/SelfRenderingOrderingExpression.java new file mode 100644 index 0000000000..068994b587 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/SelfRenderingOrderingExpression.java @@ -0,0 +1,86 @@ +/* + * 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.metamodel.mapping.ordering.ast; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.JdbcMappingContainer; +import org.hibernate.query.NullPrecedence; +import org.hibernate.query.SortOrder; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.spi.SqlAstCreationState; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SortSpecification; + +/** + * Represents a self rendering expression i.e. usually a literal used in an order-by fragment + * + * @apiNote This is Hibernate-specific feature. For {@link jakarta.persistence.OrderBy} (JPA) + * all path references are expected to be domain paths (attributes). + * + * @author Christian Beikov + */ +public class SelfRenderingOrderingExpression implements OrderingExpression, SelfRenderingExpression { + private final String expression; + + public SelfRenderingOrderingExpression(String expression) { + this.expression = expression; + } + + public String getExpression() { + return expression; + } + + @Override + public JdbcMappingContainer getExpressionType() { + return null; + } + + @Override + public void renderToSql( + SqlAppender sqlAppender, + SqlAstTranslator walker, + SessionFactoryImplementor sessionFactory) { + sqlAppender.append( expression ); + } + + @Override + public Expression resolve( + QuerySpec ast, + TableGroup tableGroup, + String modelPartName, + SqlAstCreationState creationState) { + return this; + } + + @Override + public void apply( + QuerySpec ast, + TableGroup tableGroup, + String collation, + String modelPartName, + SortOrder sortOrder, + NullPrecedence nullPrecedence, + SqlAstCreationState creationState) { + final Expression expression = resolve( ast, tableGroup, modelPartName, creationState ); + // It makes no sense to order by an expression multiple times + // SQL Server even reports a query error in this case + if ( ast.hasSortSpecifications() ) { + for ( SortSpecification sortSpecification : ast.getSortSpecifications() ) { + if ( sortSpecification.getSortExpression() == expression ) { + return; + } + } + } + + ast.addSortSpecification( new SortSpecification( expression, collation, sortOrder, nullPrecedence ) ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/SequencePart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/SequencePart.java index 5942d8c437..3aadbc9656 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/SequencePart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/SequencePart.java @@ -16,6 +16,7 @@ import org.hibernate.metamodel.mapping.ordering.TranslationContext; public interface SequencePart { SequencePart resolvePathPart( String name, + String identifier, boolean isTerminal, TranslationContext translationContext); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingSqmPathSource.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingSqmPathSource.java index 40eee44b70..bd97974488 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingSqmPathSource.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingSqmPathSource.java @@ -6,6 +6,7 @@ */ package org.hibernate.metamodel.model.domain.internal; +import org.hibernate.metamodel.model.domain.AllowableParameterType; import org.hibernate.metamodel.UnsupportedMappingException; import org.hibernate.metamodel.model.domain.AnyMappingDomainType; import org.hibernate.metamodel.model.domain.BasicDomainType; @@ -19,7 +20,7 @@ import static jakarta.persistence.metamodel.Bindable.BindableType.SINGULAR_ATTRI /** * @author Steve Ebersole */ -public class AnyMappingSqmPathSource extends AbstractSqmPathSource { +public class AnyMappingSqmPathSource extends AbstractSqmPathSource implements AllowableParameterType { private final SqmPathSource keyPathSource; @SuppressWarnings("WeakerAccess") @@ -58,4 +59,14 @@ public class AnyMappingSqmPathSource extends AbstractSqmPathSource { return new SqmAnyValuedSimplePath<>( navigablePath, this, lhs, lhs.nodeBuilder() ); } + @Override + public PersistenceType getPersistenceType() { + // todo (6.0): no idea what is best here + return PersistenceType.EMBEDDABLE; + } + + @Override + public Class getJavaType() { + return getBindableJavaType(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/DiscriminatorSqmPath.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/DiscriminatorSqmPath.java index f280313357..4dee334ec3 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/DiscriminatorSqmPath.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/DiscriminatorSqmPath.java @@ -87,7 +87,7 @@ public class DiscriminatorSqmPath extends AbstractSqmPath implements SelfInterpr } @Override - public SemanticPathPart resolvePathPart(String name, boolean isTerminal, SqmCreationState creationState) { + public SqmPath resolvePathPart(String name, boolean isTerminal, SqmCreationState creationState) { throw new IllegalStateException( "Discriminator cannot be de-referenced" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java index 3efc8c02f4..cb14a67ceb 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java @@ -10,11 +10,14 @@ import java.io.ObjectStreamException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; import java.util.function.Consumer; import java.util.stream.Stream; import jakarta.persistence.EntityGraph; @@ -79,16 +82,16 @@ public class JpaMetamodelImpl implements JpaMetamodel, Serializable { private final TypeConfiguration typeConfiguration; private final JpaCompliance jpaCompliance; - private final Map> jpaEntityTypeMap = new ConcurrentHashMap<>(); - private final Map, MappedSuperclassDomainType> jpaMappedSuperclassTypeMap = new ConcurrentHashMap<>(); - private final Map> jpaEmbeddableDescriptorMap = new ConcurrentHashMap<>(); - private final Map, Enum>> allowedEnumLiteralTexts = new ConcurrentHashMap<>(); + private final Map> jpaEntityTypeMap = new TreeMap<>(); // Need ordering for deterministic implementers list in SqmPolymorphicRootDescriptor + private final Map, MappedSuperclassDomainType> jpaMappedSuperclassTypeMap = new HashMap<>(); + private final Map> jpaEmbeddableDescriptorMap = new HashMap<>(); + private final Map, Enum>> allowedEnumLiteralTexts = new HashMap<>(); private final transient Map entityGraphMap = new ConcurrentHashMap<>(); private final Map> polymorphicEntityReferenceMap = new ConcurrentHashMap<>(); - private final Map entityProxyInterfaceMap = new ConcurrentHashMap<>(); + private final Map entityProxyInterfaceMap = new HashMap<>(); private final Map> nameToImportMap = new ConcurrentHashMap<>(); @@ -465,6 +468,18 @@ public class JpaMetamodelImpl implements JpaMetamodel, Serializable { visitEntityTypes( entityDomainType -> { if ( javaType.isAssignableFrom( entityDomainType.getJavaType() ) ) { + final ManagedDomainType superType = entityDomainType.getSuperType(); + // If the entity super type is also assignable, skip adding this entity type + if ( superType instanceof EntityDomainType + && javaType.isAssignableFrom( superType.getJavaType() ) ) { + final Queryable entityPersister = (Queryable) typeConfiguration.getSessionFactory() + .getMetamodel() + .getEntityDescriptor( ( (EntityDomainType) superType ).getHibernateEntityName() ); + // But only skip adding this type if the parent doesn't require explicit polymorphism + if ( !entityPersister.isExplicitPolymorphism() ) { + return; + } + } final Queryable entityPersister = (Queryable) typeConfiguration.getSessionFactory() .getMetamodel() .getEntityDescriptor( entityDomainType.getHibernateEntityName() ); @@ -531,11 +546,11 @@ public class JpaMetamodelImpl implements JpaMetamodel, Serializable { this.allowedEnumLiteralTexts.computeIfAbsent( enumConstant.name(), - k -> new ConcurrentHashMap<>() + k -> new HashMap<>() ).put( enumClass, enumConstant ); this.allowedEnumLiteralTexts.computeIfAbsent( qualifiedEnumLiteral, - k -> new ConcurrentHashMap<>() + k -> new HashMap<>() ).put( enumClass, enumConstant ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPropertyNames.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPropertyNames.java deleted file mode 100644 index 12eee44378..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPropertyNames.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 . - */ -package org.hibernate.persister.collection; - -/** - * The names of all the collection properties. - * - * @author josh - */ -public final class CollectionPropertyNames { - private CollectionPropertyNames() { - } - - public static final String COLLECTION_SIZE = "size"; - public static final String COLLECTION_ELEMENTS = "elements"; - public static final String COLLECTION_INDICES = "indices"; - public static final String COLLECTION_MAX_INDEX = "maxIndex"; - public static final String COLLECTION_MIN_INDEX = "minIndex"; - public static final String COLLECTION_MAX_ELEMENT = "maxElement"; - public static final String COLLECTION_MIN_ELEMENT = "minElement"; - public static final String COLLECTION_INDEX = "index"; -} diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index eb1388385e..b464da9a63 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -5558,10 +5558,15 @@ public abstract class AbstractEntityPersister creationProcess.registerInitializationCallback( "Entity(" + getEntityName() + ") `sqmMultiTableMutationStrategy` interpretation", () -> { - sqmMultiTableMutationStrategy = interpretSqmMultiTableStrategy( - this, - creationProcess - ); + try { + sqmMultiTableMutationStrategy = interpretSqmMultiTableStrategy( + this, + creationProcess + ); + } + catch (Exception ex) { + return false; + } if ( sqmMultiTableMutationStrategy == null ) { return false; } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java index ef7fcf0a86..26b3e2659b 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java @@ -939,8 +939,8 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { } final Object value = getDiscriminatorValue(); final boolean hasNotNullDiscriminator = value == NOT_NULL_DISCRIMINATOR; - final boolean hasNullDiscrininator = value == NULL_DISCRIMINATOR; - if ( hasNotNullDiscriminator || hasNullDiscrininator ) { + final boolean hasNullDiscriminator = value == NULL_DISCRIMINATOR; + if ( hasNotNullDiscriminator || hasNullDiscriminator ) { final NullnessPredicate nullnessPredicate = new NullnessPredicate( sqlExpression ); if ( hasNotNullDiscriminator ) { return new NegatedPredicate( nullnessPredicate ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java index e28dc33d69..4eea5e4595 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java @@ -10,7 +10,6 @@ import java.lang.reflect.Field; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.metamodel.model.domain.EntityDomainType; -import org.hibernate.query.criteria.JpaPath; import org.hibernate.query.hql.HqlLogging; import org.hibernate.query.hql.spi.DotIdentifierConsumer; import org.hibernate.query.hql.spi.SemanticPathPart; @@ -48,7 +47,7 @@ import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; public class BasicDotIdentifierConsumer implements DotIdentifierConsumer { private final SqmCreationState creationState; - private String pathSoFar; + private StringBuilder pathSoFar = new StringBuilder(); private SemanticPathPart currentPart; public BasicDotIdentifierConsumer(SqmCreationState creationState) { @@ -76,12 +75,10 @@ public class BasicDotIdentifierConsumer implements DotIdentifierConsumer { reset(); } - if ( pathSoFar == null ) { - pathSoFar = identifier; - } - else { - pathSoFar += ( '.' + identifier ); + if ( pathSoFar.length() != 0 ) { + pathSoFar.append( '.' ); } + pathSoFar.append( identifier ); HqlLogging.QUERY_LOGGER.tracef( "BasicDotIdentifierHandler#consumeIdentifier( %s, %s, %s ) - %s", @@ -102,7 +99,7 @@ public class BasicDotIdentifierConsumer implements DotIdentifierConsumer { } protected void reset() { - pathSoFar = null; + pathSoFar.setLength( 0 ); currentPart = createBasePart(); } @@ -179,7 +176,8 @@ public class BasicDotIdentifierConsumer implements DotIdentifierConsumer { return this; } - final String importableName = creationContext.getJpaMetamodel().qualifyImportableName( pathSoFar ); + final String path = pathSoFar.toString(); + final String importableName = creationContext.getJpaMetamodel().qualifyImportableName( path ); if ( importableName != null ) { final EntityDomainType entityDomainType = creationContext.getJpaMetamodel().entity( importableName ); if ( entityDomainType != null ) { @@ -189,7 +187,7 @@ public class BasicDotIdentifierConsumer implements DotIdentifierConsumer { final SqmFunctionDescriptor functionDescriptor = creationContext.getQueryEngine() .getSqmFunctionRegistry() - .findFunctionDescriptor( pathSoFar ); + .findFunctionDescriptor( path ); if ( functionDescriptor != null ) { return functionDescriptor.generateSqmExpression( null, @@ -212,10 +210,10 @@ public class BasicDotIdentifierConsumer implements DotIdentifierConsumer { // } // see if it is a named field/enum reference - final int splitPosition = pathSoFar.lastIndexOf( '.' ); + final int splitPosition = path.lastIndexOf( '.' ); if ( splitPosition > 0 ) { - final String prefix = pathSoFar.substring( 0, splitPosition ); - final String terminal = pathSoFar.substring( splitPosition + 1 ); + final String prefix = path.substring( 0, splitPosition ); + final String terminal = path.substring( splitPosition + 1 ); //TODO: try interpreting paths of form foo.bar.Foo.Bar as foo.bar.Foo$Bar try { @@ -254,7 +252,7 @@ public class BasicDotIdentifierConsumer implements DotIdentifierConsumer { } } - throw new ParsingException( "Could not interpret dot-ident : " + pathSoFar ); + throw new ParsingException( "Could not interpret dot-ident : " + path ); } protected void validateAsRoot(SqmFrom pathRoot) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/DomainPathPart.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/DomainPathPart.java index 846d16e842..b82675353e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/DomainPathPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/DomainPathPart.java @@ -7,19 +7,11 @@ package org.hibernate.query.hql.internal; import org.hibernate.NotYetImplementedFor6Exception; -import org.hibernate.metamodel.mapping.ModelPartContainer; -import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.hql.HqlLogging; import org.hibernate.query.hql.spi.SemanticPathPart; import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.expression.SqmExpression; -import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; -import org.hibernate.query.sqm.tree.from.SqmCrossJoin; -import org.hibernate.query.sqm.tree.from.SqmEntityJoin; -import org.hibernate.query.sqm.tree.from.SqmFrom; -import org.hibernate.query.sqm.tree.from.SqmJoin; -import org.hibernate.query.sqm.tree.from.SqmRoot; /** * Specialized "intermediate" SemanticPathPart for processing domain model paths @@ -49,53 +41,7 @@ public class DomainPathPart implements SemanticPathPart { currentPath, name ); - final SqmPath reusablePath = currentPath.getReusablePath( name ); - if ( reusablePath != null ) { - currentPath = reusablePath; - } - else { - // Try to resolve an existing attribute join without ON clause - SqmPath resolvedPath = null; - if ( currentPath instanceof SqmFrom ) { - ModelPartContainer modelPartContainer = null; - for ( SqmJoin sqmJoin : ( (SqmFrom) currentPath ).getSqmJoins() ) { - if ( sqmJoin instanceof SqmAttributeJoin - && name.equals( sqmJoin.getReferencedPathSource().getPathName() ) ) { - final SqmAttributeJoin attributeJoin = (SqmAttributeJoin) sqmJoin; - if ( attributeJoin.getOn() == null ) { - // todo (6.0): to match the expectation of the JPA spec I think we also have to check - // that the join type is INNER or the default join type for the attribute, - // but as far as I understand, in 5.x we expect to ignore this behavior -// if ( attributeJoin.getSqmJoinType() != SqmJoinType.INNER ) { -// if ( attributeJoin.getAttribute().isCollection() ) { -// continue; -// } -// if ( modelPartContainer == null ) { -// modelPartContainer = findModelPartContainer( attributeJoin, creationState ); -// } -// final TableGroupJoinProducer joinProducer = (TableGroupJoinProducer) modelPartContainer.findSubPart( -// name, -// null -// ); -// if ( attributeJoin.getSqmJoinType().getCorrespondingSqlJoinType() != joinProducer.getDefaultSqlAstJoinType( null ) ) { -// continue; -// } -// } - resolvedPath = sqmJoin; - if ( attributeJoin.isFetched() ) { - break; - } - } - } - } - } - if ( resolvedPath == null ) { - currentPath = currentPath.get( name ); - } - else { - currentPath = resolvedPath; - } - } + currentPath = currentPath.resolvePathPart( name, isTerminal, creationState ); if ( isTerminal ) { return currentPath; } @@ -104,45 +50,6 @@ public class DomainPathPart implements SemanticPathPart { } } - private ModelPartContainer findModelPartContainer(SqmAttributeJoin attributeJoin, SqmCreationState creationState) { - final SqmFrom lhs = attributeJoin.getLhs(); - if ( lhs instanceof SqmAttributeJoin ) { - final SqmAttributeJoin lhsAttributeJoin = (SqmAttributeJoin) lhs; - if ( lhsAttributeJoin.getReferencedPathSource() instanceof EntityDomainType ) { - final String entityName = ( (EntityDomainType) lhsAttributeJoin.getReferencedPathSource() ).getHibernateEntityName(); - return (ModelPartContainer) creationState.getCreationContext().getQueryEngine() - .getTypeConfiguration() - .getSessionFactory() - .getMetamodel() - .entityPersister( entityName ) - .findSubPart( attributeJoin.getAttribute().getName(), null ); - } - else { - return (ModelPartContainer) findModelPartContainer( lhsAttributeJoin, creationState ) - .findSubPart( attributeJoin.getAttribute().getName(), null ); - } - } - else { - final String entityName; - if ( lhs instanceof SqmRoot ) { - entityName = ( (SqmRoot) lhs ).getEntityName(); - } - else if ( lhs instanceof SqmEntityJoin ) { - entityName = ( (SqmEntityJoin) lhs ).getEntityName(); - } - else { - assert lhs instanceof SqmCrossJoin; - entityName = ( (SqmCrossJoin) lhs ).getEntityName(); - } - return (ModelPartContainer) creationState.getCreationContext().getQueryEngine() - .getTypeConfiguration() - .getSessionFactory() - .getMetamodel() - .entityPersister( entityName ) - .findSubPart( attributeJoin.getAttribute().getName(), null ); - } - } - @Override public SqmPath resolveIndexedAccess( SqmExpression selector, diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java index 30c1a3596d..905a720173 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QualifiedJoinPathConsumer.java @@ -9,7 +9,6 @@ package org.hibernate.query.hql.internal; import java.util.Locale; import org.hibernate.metamodel.model.domain.EntityDomainType; -import org.hibernate.query.NavigablePath; import org.hibernate.query.SemanticException; import org.hibernate.query.hql.HqlInterpretationException; import org.hibernate.query.hql.spi.DotIdentifierConsumer; @@ -19,11 +18,13 @@ import org.hibernate.query.sqm.SqmJoinable; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmCreationState; +import org.hibernate.query.sqm.spi.SqmCreationHelper; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmEntityJoin; import org.hibernate.query.sqm.tree.from.SqmFrom; +import org.hibernate.query.sqm.tree.from.SqmJoin; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.jboss.logging.Logger; @@ -37,7 +38,7 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { private static final Logger log = Logger.getLogger( QualifiedJoinPathConsumer.class ); private final SqmCreationState creationState; - private final SqmRoot sqmRoot; + private final SqmRoot sqmRoot; private final SqmJoinType joinType; private final boolean fetch; @@ -110,7 +111,7 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { final SqmCreationProcessingState processingState = creationState.getCurrentProcessingState(); final SqmPathRegistry pathRegistry = processingState.getPathRegistry(); - final SqmFrom pathRootByAlias = pathRegistry.findFromByAlias( identifier ); + final SqmFrom pathRootByAlias = pathRegistry.findFromByAlias( identifier ); if ( pathRootByAlias != null ) { // identifier is an alias (identification variable) @@ -127,7 +128,7 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { ); } - final SqmFrom pathRootByExposedNavigable = pathRegistry.findFromExposing( identifier ); + final SqmFrom pathRootByExposedNavigable = pathRegistry.findFromExposing( identifier ); if ( pathRootByExposedNavigable != null ) { return new AttributeJoinDelegate( createJoin( pathRootByExposedNavigable, identifier, isTerminal ), @@ -152,7 +153,7 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { ); } - private SqmFrom createJoin(SqmFrom lhs, String identifier, boolean isTerminal) { + private SqmFrom createJoin(SqmFrom lhs, String identifier, boolean isTerminal) { return createJoin( lhs, identifier, @@ -164,15 +165,16 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { ); } - private static SqmFrom createJoin( - SqmFrom lhs, + private static SqmFrom createJoin( + SqmFrom lhs, String name, SqmJoinType joinType, String alias, boolean fetch, boolean isTerminal, SqmCreationState creationState) { - final SqmPathSource subPathSource = lhs.getReferencedPathSource().findSubPathSource( name ); + //noinspection unchecked + final SqmPathSource subPathSource = (SqmPathSource) lhs.getReferencedPathSource().findSubPathSource( name ); if ( subPathSource == null ) { throw new HqlInterpretationException( String.format( @@ -183,14 +185,22 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { ) ); } - final SqmAttributeJoin join = ( (SqmJoinable) subPathSource ).createSqmJoin( + if ( !isTerminal ) { + for ( SqmJoin sqmJoin : lhs.getSqmJoins() ) { + if ( sqmJoin.getAlias() == null && sqmJoin.getReferencedPathSource() == subPathSource ) { + //noinspection unchecked + return (SqmFrom) sqmJoin; + } + } + } + @SuppressWarnings("unchecked") + final SqmAttributeJoin join = ( (SqmJoinable) subPathSource ).createSqmJoin( lhs, joinType, - isTerminal ? alias : null, + isTerminal ? alias : SqmCreationHelper.IMPLICIT_ALIAS, fetch, creationState ); - //noinspection unchecked lhs.addSqmJoin( join ); creationState.getCurrentProcessingState().getPathRegistry().register( join ); return join; @@ -209,10 +219,10 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { private final boolean fetch; private final String alias; - private SqmFrom currentPath; + private SqmFrom currentPath; public AttributeJoinDelegate( - SqmFrom base, + SqmFrom base, SqmJoinType joinType, boolean fetch, String alias, @@ -222,7 +232,8 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { this.alias = alias; this.creationState = creationState; - this.currentPath = base; + //noinspection unchecked + this.currentPath = (SqmFrom) base; } @Override @@ -240,7 +251,7 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { @Override public void consumeTreat(String entityName, boolean isTerminal) { - final EntityDomainType entityDomainType = creationState.getCreationContext().getJpaMetamodel() + final EntityDomainType entityDomainType = creationState.getCreationContext().getJpaMetamodel() .entity( entityName ); currentPath = currentPath.treatAs( entityDomainType, isTerminal ? alias : null ); creationState.getCurrentProcessingState().getPathRegistry().register( currentPath ); @@ -254,7 +265,7 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { private static class ExpectingEntityJoinDelegate implements ConsumerDelegate { private final SqmCreationState creationState; - private final SqmRoot sqmRoot; + private final SqmRoot sqmRoot; private final SqmJoinType joinType; private final boolean fetch; @@ -267,7 +278,7 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { public ExpectingEntityJoinDelegate( String identifier, boolean isTerminal, - SqmRoot sqmRoot, + SqmRoot sqmRoot, SqmJoinType joinType, String alias, boolean fetch, diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java index 386c5a3641..ba5fe06ffb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java @@ -20,9 +20,14 @@ import org.hibernate.query.hql.spi.SqmCreationOptions; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.hql.spi.SqmPathRegistry; +import org.hibernate.query.sqm.internal.SqmDmlCreationProcessingState; import org.hibernate.query.sqm.internal.SqmQuerySpecCreationProcessingStateStandardImpl; import org.hibernate.query.sqm.spi.BaseSemanticQueryWalker; import org.hibernate.query.sqm.spi.SqmCreationContext; +import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; +import org.hibernate.query.sqm.tree.SqmQuery; +import org.hibernate.query.sqm.tree.cte.SqmCteContainer; +import org.hibernate.query.sqm.tree.cte.SqmCteStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath; @@ -81,6 +86,7 @@ import org.hibernate.type.descriptor.java.JavaType; * @author Steve Ebersole */ public class QuerySplitter { + public static SqmSelectStatement[] split( SqmSelectStatement statement, SessionFactoryImplementor sessionFactory) { @@ -128,10 +134,46 @@ public class QuerySplitter { } } + public static SqmDeleteStatement[] split( + SqmDeleteStatement statement, + SessionFactoryImplementor sessionFactory) { + // We only allow unmapped polymorphism in a very restricted way. Specifically, + // the unmapped polymorphic reference can only be a root and can be the only + // root. Use that restriction to locate the unmapped polymorphic reference + final SqmRoot unmappedPolymorphicReference = findUnmappedPolymorphicReference( statement ); + + if ( unmappedPolymorphicReference == null ) { + return new SqmDeleteStatement[] { statement }; + } + + final SqmPolymorphicRootDescriptor unmappedPolymorphicDescriptor = (SqmPolymorphicRootDescriptor) unmappedPolymorphicReference.getReferencedPathSource(); + final SqmDeleteStatement[] expanded = new SqmDeleteStatement[ unmappedPolymorphicDescriptor.getImplementors().size() ]; + + int i = -1; + for ( EntityDomainType mappedDescriptor : unmappedPolymorphicDescriptor.getImplementors() ) { + i++; + final UnmappedPolymorphismReplacer replacer = new UnmappedPolymorphismReplacer<>( + unmappedPolymorphicReference, + mappedDescriptor, + sessionFactory + ); + expanded[i] = replacer.visitDeleteStatement( statement ); + } + + return expanded; + } + + private static SqmRoot findUnmappedPolymorphicReference(SqmDeleteOrUpdateStatement queryPart) { + if ( queryPart.getTarget().getReferencedPathSource() instanceof SqmPolymorphicRootDescriptor ) { + return queryPart.getTarget(); + } + return null; + } + @SuppressWarnings("unchecked") private static class UnmappedPolymorphismReplacer extends BaseSemanticQueryWalker implements SqmCreationState { private final SqmRoot unmappedPolymorphicFromElement; - private final EntityDomainType mappedDescriptor; + private final EntityDomainType mappedDescriptor; private final SqmCreationContext creationContext; private final Stack processingStateStack = new StandardStack<>(); @@ -168,9 +210,56 @@ public class QuerySplitter { throw new UnsupportedOperationException( "Not valid" ); } + @Override + public Object visitCteContainer(SqmCteContainer consumer) { + final SqmCteContainer processingQuery = (SqmCteContainer) getProcessingStateStack().getCurrent() + .getProcessingQuery(); + processingQuery.setWithRecursive( consumer.isWithRecursive() ); + for ( SqmCteStatement cteStatement : consumer.getCteStatements() ) { + processingQuery.addCteStatement( visitCteStatement( cteStatement ) ); + } + return processingQuery; + } + + @Override + public SqmCteStatement visitCteStatement(SqmCteStatement sqmCteStatement) { + // No need to copy anything here + return sqmCteStatement; + } + @Override public SqmDeleteStatement visitDeleteStatement(SqmDeleteStatement statement) { - throw new UnsupportedOperationException( "Not valid" ); + final SqmRoot sqmRoot = statement.getTarget(); + final SqmRoot copy = new SqmRoot<>( + mappedDescriptor, + sqmRoot.getExplicitAlias(), + sqmRoot.isAllowJoins(), + sqmRoot.nodeBuilder() + ); + sqmFromCopyMap.put( sqmRoot, copy ); + sqmPathCopyMap.put( sqmRoot.getNavigablePath(), copy ); + final SqmDeleteStatement statementCopy = new SqmDeleteStatement<>( + copy, + statement.getQuerySource(), + statement.nodeBuilder() + ); + + processingStateStack.push( + new SqmDmlCreationProcessingState( + statementCopy, + this + ) + ); + getProcessingStateStack().getCurrent().getPathRegistry().register( copy ); + try { + visitCteContainer( statement ); + statementCopy.setWhereClause( visitWhereClause( statement.getWhereClause() ) ); + } + finally { + processingStateStack.pop(); + } + + return statementCopy; } @Override @@ -185,6 +274,7 @@ public class QuerySplitter { ) ); try { + visitCteContainer( statement ); copy.setQueryPart( visitQueryPart( statement.getQueryPart() ) ); } finally { @@ -292,15 +382,13 @@ public class QuerySplitter { sqmRoot.isAllowJoins(), sqmRoot.nodeBuilder() ); - return (SqmRoot) getProcessingStateStack().getCurrent().getPathRegistry().resolvePath( - copy.getNavigablePath(), - navigablePath -> { - sqmFromCopyMap.put( sqmRoot, copy ); - sqmPathCopyMap.put( sqmRoot.getNavigablePath(), copy ); - currentFromClauseCopy.addRoot( copy ); - return copy; - } - ); + getProcessingStateStack().getCurrent().getPathRegistry().register( copy ); + sqmFromCopyMap.put( sqmRoot, copy ); + sqmPathCopyMap.put( sqmRoot.getNavigablePath(), copy ); + if ( currentFromClauseCopy != null ) { + currentFromClauseCopy.addRoot( copy ); + } + return copy; } @Override @@ -309,21 +397,17 @@ public class QuerySplitter { if ( sqmFrom != null ) { return (SqmCrossJoin) sqmFrom; } - return (SqmCrossJoin) getProcessingStateStack().getCurrent().getPathRegistry().resolvePath( - join.getNavigablePath(), - navigablePath -> { - final SqmRoot sqmRoot = (SqmRoot) sqmFromCopyMap.get( join.findRoot() ); - final SqmCrossJoin copy = new SqmCrossJoin<>( - join.getReferencedPathSource(), - join.getExplicitAlias(), - sqmRoot - ); - sqmFromCopyMap.put( join, copy ); - sqmPathCopyMap.put( join.getNavigablePath(), copy ); - sqmRoot.addSqmJoin( copy ); - return copy; - } + final SqmRoot sqmRoot = (SqmRoot) sqmFromCopyMap.get( join.findRoot() ); + final SqmCrossJoin copy = new SqmCrossJoin<>( + join.getReferencedPathSource(), + join.getExplicitAlias(), + sqmRoot ); + getProcessingStateStack().getCurrent().getPathRegistry().register( copy ); + sqmFromCopyMap.put( join, copy ); + sqmPathCopyMap.put( join.getNavigablePath(), copy ); + sqmRoot.addSqmJoin( copy ); + return copy; } @Override @@ -332,22 +416,18 @@ public class QuerySplitter { if ( sqmFrom != null ) { return (SqmEntityJoin) sqmFrom; } - return (SqmEntityJoin) getProcessingStateStack().getCurrent().getPathRegistry().resolvePath( - join.getNavigablePath(), - navigablePath -> { - final SqmRoot sqmRoot = (SqmRoot) sqmFromCopyMap.get( join.findRoot() ); - final SqmEntityJoin copy = new SqmEntityJoin<>( - join.getReferencedPathSource(), - join.getExplicitAlias(), - join.getSqmJoinType(), - sqmRoot - ); - sqmFromCopyMap.put( join, copy ); - sqmPathCopyMap.put( join.getNavigablePath(), copy ); - sqmRoot.addSqmJoin( copy ); - return copy; - } + final SqmRoot sqmRoot = (SqmRoot) sqmFromCopyMap.get( join.findRoot() ); + final SqmEntityJoin copy = new SqmEntityJoin<>( + join.getReferencedPathSource(), + join.getExplicitAlias(), + join.getSqmJoinType(), + sqmRoot ); + getProcessingStateStack().getCurrent().getPathRegistry().register( copy ); + sqmFromCopyMap.put( join, copy ); + sqmPathCopyMap.put( join.getNavigablePath(), copy ); + sqmRoot.addSqmJoin( copy ); + return copy; } @Override @@ -356,92 +436,69 @@ public class QuerySplitter { if ( sqmFrom != null ) { return (SqmAttributeJoin) sqmFrom; } - return (SqmAttributeJoin) getProcessingStateStack().getCurrent().getPathRegistry().resolvePath( - join.getNavigablePath(), - navigablePath -> { - SqmAttributeJoin copy = join.makeCopy( getProcessingStateStack().getCurrent() ); - sqmFromCopyMap.put( join, copy ); - sqmPathCopyMap.put( join.getNavigablePath(), copy ); - ( (SqmFrom) copy.getParent() ).addSqmJoin( copy ); - return copy; - } - ); + SqmAttributeJoin copy = join.makeCopy( getProcessingStateStack().getCurrent() ); + getProcessingStateStack().getCurrent().getPathRegistry().register( copy ); + sqmFromCopyMap.put( join, copy ); + sqmPathCopyMap.put( join.getNavigablePath(), copy ); + ( (SqmFrom) copy.getParent() ).addSqmJoin( copy ); + return copy; } @Override public SqmBasicValuedSimplePath visitBasicValuedPath(SqmBasicValuedSimplePath path) { final SqmPathRegistry pathRegistry = getProcessingStateStack().getCurrent().getPathRegistry(); - return (SqmBasicValuedSimplePath) pathRegistry.resolvePath( + final SqmBasicValuedSimplePath copy = new SqmBasicValuedSimplePath<>( path.getNavigablePath(), - navigablePath -> { - final SqmBasicValuedSimplePath copy = new SqmBasicValuedSimplePath<>( - navigablePath, - path.getReferencedPathSource(), - pathRegistry.findFromByPath( path.getLhs().getNavigablePath() ), - path.nodeBuilder() - ); - sqmPathCopyMap.put( path.getNavigablePath(), copy ); - return copy; - } + path.getReferencedPathSource(), + pathRegistry.findFromByPath( path.getLhs().getNavigablePath() ), + path.nodeBuilder() ); + pathRegistry.register( copy ); + sqmPathCopyMap.put( path.getNavigablePath(), copy ); + return copy; } @Override public SqmEmbeddedValuedSimplePath visitEmbeddableValuedPath(SqmEmbeddedValuedSimplePath path) { final SqmPathRegistry pathRegistry = getProcessingStateStack().getCurrent().getPathRegistry(); - - return (SqmEmbeddedValuedSimplePath) pathRegistry.resolvePath( + final SqmEmbeddedValuedSimplePath copy = new SqmEmbeddedValuedSimplePath<>( path.getNavigablePath(), - navigablePath -> { - final SqmEmbeddedValuedSimplePath copy = new SqmEmbeddedValuedSimplePath<>( - navigablePath, - path.getReferencedPathSource(), - pathRegistry.findFromByPath( path.getLhs().getNavigablePath() ), - path.nodeBuilder() - ); - sqmPathCopyMap.put( path.getNavigablePath(), copy ); - return copy; - } + path.getReferencedPathSource(), + pathRegistry.findFromByPath( path.getLhs().getNavigablePath() ), + path.nodeBuilder() ); + pathRegistry.register( copy ); + sqmPathCopyMap.put( path.getNavigablePath(), copy ); + return copy; } @Override public SqmEntityValuedSimplePath visitEntityValuedPath(SqmEntityValuedSimplePath path) { final SqmPathRegistry pathRegistry = getProcessingStateStack().getCurrent().getPathRegistry(); - - return (SqmEntityValuedSimplePath) pathRegistry.resolvePath( + final SqmEntityValuedSimplePath copy = new SqmEntityValuedSimplePath<>( path.getNavigablePath(), - navigablePath -> { - final SqmEntityValuedSimplePath copy = new SqmEntityValuedSimplePath<>( - navigablePath, - path.getReferencedPathSource(), - pathRegistry.findFromByPath( path.getLhs().getNavigablePath() ), - path.nodeBuilder() - ); - sqmPathCopyMap.put( path.getNavigablePath(), copy ); - return copy; - } + path.getReferencedPathSource(), + pathRegistry.findFromByPath( path.getLhs().getNavigablePath() ), + path.nodeBuilder() ); + pathRegistry.register( copy ); + sqmPathCopyMap.put( path.getNavigablePath(), copy ); + return copy; } @Override public SqmPluralValuedSimplePath visitPluralValuedPath(SqmPluralValuedSimplePath path) { final SqmPathRegistry pathRegistry = getProcessingStateStack().getCurrent().getPathRegistry(); - - return (SqmPluralValuedSimplePath) pathRegistry.resolvePath( + final SqmPluralValuedSimplePath copy = new SqmPluralValuedSimplePath<>( path.getNavigablePath(), - navigablePath -> { - final SqmPluralValuedSimplePath copy = new SqmPluralValuedSimplePath<>( - navigablePath, - path.getReferencedPathSource(), - pathRegistry.findFromByPath( path.getLhs().getNavigablePath() ), - path.nodeBuilder() - ); - sqmPathCopyMap.put( path.getNavigablePath(), copy ); - return copy; - } + path.getReferencedPathSource(), + pathRegistry.findFromByPath( path.getLhs().getNavigablePath() ), + path.nodeBuilder() ); + pathRegistry.register( copy ); + sqmPathCopyMap.put( path.getNavigablePath(), copy ); + return copy; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index 66b896fa86..27e7166abf 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -41,6 +41,7 @@ import org.hibernate.grammars.hql.HqlLexer; import org.hibernate.grammars.hql.HqlParser; import org.hibernate.grammars.hql.HqlParserBaseVisitor; import org.hibernate.internal.util.CharSequenceHelper; +import org.hibernate.internal.util.QuotingHelper; import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.metamodel.CollectionClassification; @@ -106,7 +107,6 @@ import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor; -import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef; import org.hibernate.query.sqm.tree.expression.SqmAny; import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic; @@ -134,7 +134,6 @@ import org.hibernate.query.sqm.tree.expression.SqmToDuration; import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification; import org.hibernate.query.sqm.tree.expression.SqmTuple; import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation; -import org.hibernate.query.sqm.tree.from.DowncastLocation; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmCrossJoin; import org.hibernate.query.sqm.tree.from.SqmEntityJoin; @@ -339,7 +338,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem final ParseTree parseTree = ctx.getChild( 0 ); if ( parseTree instanceof HqlParser.SelectStatementContext ) { final SqmSelectStatement selectStatement = visitSelectStatement( (HqlParser.SelectStatementContext) parseTree ); - selectStatement.getQueryPart().validateQueryGroupFetchStructure(); + selectStatement.getQueryPart().validateFetchStructureAndOwners(); return selectStatement; } else if ( parseTree instanceof HqlParser.InsertStatementContext ) { @@ -425,6 +424,11 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem dmlTargetIndex + 1 ); final SqmRoot root = visitDmlTarget( dmlTargetContext ); + if ( root.getReferencedPathSource() instanceof SqmPolymorphicRootDescriptor ) { + throw new SemanticException( + "Can't create an INSERT for a non entity name: " + root.getReferencedPathSource().getHibernateEntityName() + ); + } final HqlParser.QueryExpressionContext queryExpressionContext = ctx.queryExpression(); if ( queryExpressionContext != null ) { @@ -477,10 +481,12 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem processingState.getPathRegistry().register( root ); try { - for ( HqlParser.ValuesContext values : ctx.valuesList().values() ) { - SqmValues sqmValues = new SqmValues(); - for ( HqlParser.ExpressionContext expressionContext : values.expression() ) { - sqmValues.getExpressions().add( (SqmExpression) expressionContext.accept( this ) ); + final HqlParser.ValuesListContext valuesListContext = ctx.valuesList(); + for ( int i = 1; i < valuesListContext.getChildCount(); i += 2 ) { + final ParseTree values = valuesListContext.getChild( i ); + final SqmValues sqmValues = new SqmValues(); + for ( int j = 1; j < values.getChildCount(); j += 2 ) { + sqmValues.getExpressions().add( (SqmExpression) values.getChild( j ).accept( this ) ); } insertStatement.getValuesList().add( sqmValues ); } @@ -506,6 +512,11 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem final int dmlTargetIndex = versioned ? 2 : 1; final HqlParser.DmlTargetContext dmlTargetContext = (HqlParser.DmlTargetContext) ctx.getChild( dmlTargetIndex ); final SqmRoot root = visitDmlTarget( dmlTargetContext ); + if ( root.getReferencedPathSource() instanceof SqmPolymorphicRootDescriptor ) { + throw new SemanticException( + "Can't create an UPDATE for a non entity name: " + root.getReferencedPathSource().getHibernateEntityName() + ); + } final SqmUpdateStatement updateStatement = new SqmUpdateStatement<>( root, creationContext.getNodeBuilder() ); parameterCollector = updateStatement; @@ -884,7 +895,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem } else { resultIdentifier = applyJpaCompliance( - visitResultIdentifier( (HqlParser.ResultIdentifierContext) ctx.getChild( 1 ) ) + visitIdentificationVariableDef( (HqlParser.IdentificationVariableDefContext) ctx.getChild( 1 ) ) ); } final SqmSelectableNode selectableNode = visitSelectableNode( ctx ); @@ -912,16 +923,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem private SqmSelectableNode visitSelectableNode(HqlParser.SelectionContext ctx) { final ParseTree subCtx = ctx.getChild( 0 ).getChild( 0 ); - if ( subCtx instanceof HqlParser.DynamicInstantiationContext ) { - return visitDynamicInstantiation( (HqlParser.DynamicInstantiationContext) subCtx ); - } - else if ( subCtx instanceof HqlParser.JpaSelectObjectSyntaxContext ) { - return visitJpaSelectObjectSyntax( (HqlParser.JpaSelectObjectSyntaxContext) subCtx ); - } - else if ( subCtx instanceof HqlParser.MapEntrySelectionContext ) { - return visitMapEntrySelection( (HqlParser.MapEntrySelectionContext) subCtx ); - } - else if ( subCtx instanceof HqlParser.ExpressionContext ) { + if ( subCtx instanceof HqlParser.ExpressionOrPredicateContext ) { final SqmExpression sqmExpression = (SqmExpression) subCtx.accept( this ); if ( sqmExpression instanceof SqmPath ) { final SqmPath sqmPath = (SqmPath) sqmExpression; @@ -945,40 +947,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return sqmExpression; } - - throw new ParsingException( "Unexpected selection rule type : " + ctx.getText() ); - } - - @Override - public String visitResultIdentifier(HqlParser.ResultIdentifierContext resultIdentifierContext) { - if ( resultIdentifierContext != null ) { - if ( resultIdentifierContext.getChildCount() == 1 ) { - return resultIdentifierContext.getText(); - } - else { - final HqlParser.IdentifierContext identifierContext = (HqlParser.IdentifierContext) resultIdentifierContext.getChild( 1 ); - final Token aliasToken = identifierContext.getStart(); - final String explicitAlias = aliasToken.getText(); - - if ( aliasToken.getType() != IDENTIFIER ) { - // we have a reserved word used as an identification variable. - if ( creationOptions.useStrictJpaCompliance() ) { - throw new StrictJpaComplianceViolation( - String.format( - Locale.ROOT, - "Strict JPQL compliance was violated : %s [%s]", - StrictJpaComplianceViolation.Type.RESERVED_WORD_USED_AS_ALIAS.description(), - explicitAlias - ), - StrictJpaComplianceViolation.Type.RESERVED_WORD_USED_AS_ALIAS - ); - } - } - return explicitAlias; - } - } - - return null; + return (SqmSelectableNode) subCtx.accept( this ); } @Override @@ -1041,15 +1010,13 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem public SqmDynamicInstantiationArgument visitDynamicInstantiationArg(HqlParser.DynamicInstantiationArgContext ctx) { final String alias; if ( ctx.getChildCount() > 1 ) { - alias = ctx.getChild( ctx.getChildCount() - 1 ).getText(); + alias = visitIdentificationVariableDef( (HqlParser.IdentificationVariableDefContext) ctx.getChild( ctx.getChildCount() - 1 ) ); } else { alias = null; } - final SqmSelectableNode argExpression = visitDynamicInstantiationArgExpression( - (HqlParser.DynamicInstantiationArgExpressionContext) ctx.getChild( 0 ) - ); + final SqmSelectableNode argExpression = (SqmSelectableNode) ctx.getChild( 0 ).accept( this ); final SqmDynamicInstantiationArgument argument = new SqmDynamicInstantiationArgument<>( argExpression, @@ -1068,19 +1035,6 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return argument; } - @Override - public SqmSelectableNode visitDynamicInstantiationArgExpression(HqlParser.DynamicInstantiationArgExpressionContext ctx) { - final ParseTree parseTree = ctx.getChild( 0 ); - if ( parseTree instanceof HqlParser.DynamicInstantiationContext ) { - return visitDynamicInstantiation( (HqlParser.DynamicInstantiationContext) parseTree ); - } - else if ( parseTree instanceof HqlParser.ExpressionContext ) { - return (SqmExpression) parseTree.accept( this ); - } - - throw new ParsingException( "Unexpected dynamic-instantiation-argument rule type : " + ctx.getText() ); - } - @Override public SqmPath visitJpaSelectObjectSyntax(HqlParser.JpaSelectObjectSyntaxContext ctx) { final String alias = ctx.getChild( 2 ).getText(); @@ -1126,7 +1080,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return new SqmAliasedNodeRef( position, integerDomainType, creationContext.getNodeBuilder() ); } else if ( child instanceof HqlParser.IdentifierContext ) { - final String identifierText = child.getText(); + final String identifierText = visitIdentifier( (HqlParser.IdentifierContext) child ); final Integer correspondingPosition = getCurrentProcessingState() .getPathRegistry() @@ -1312,13 +1266,36 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem } @Override - public SqmExpression visitPathExpression(HqlParser.PathExpressionContext ctx) { - final HqlParser.PathContext path = (HqlParser.PathContext) ctx.getChild( 0 ); - final Object accept = path.accept( this ); - if ( accept instanceof DomainPathPart ) { - return ( (DomainPathPart) accept ).getSqmExpression(); + public Object visitSyntacticPathExpression(HqlParser.SyntacticPathExpressionContext ctx) { + SemanticPathPart part = visitSyntacticDomainPath( (HqlParser.SyntacticDomainPathContext) ctx.getChild( 0 ) ); + if ( ctx.getChildCount() == 2 ) { + dotIdentifierConsumerStack.push( + new BasicDotIdentifierConsumer( part, this ) { + @Override + protected void reset() { + } + } + ); + try { + part = (SemanticPathPart) ctx.getChild( 1 ).accept( this ); + } + finally { + dotIdentifierConsumerStack.pop(); + } } - return (SqmExpression) accept; + if ( part instanceof DomainPathPart ) { + return ( (DomainPathPart) part ).getSqmExpression(); + } + return (SqmExpression) part; + } + + @Override + public Object visitGeneralPathExpression(HqlParser.GeneralPathExpressionContext ctx) { + final SemanticPathPart part = visitGeneralPathFragment( (HqlParser.GeneralPathFragmentContext) ctx.getChild( 0 ) ); + if ( part instanceof DomainPathPart ) { + return ( (DomainPathPart) part ).getSqmExpression(); + } + return (SqmExpression) part; } @Override @@ -1365,31 +1342,46 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return (SqmExpression) firstChild.accept( this ); } + public String getEntityName(HqlParser.EntityNameContext parserEntityName) { + final StringBuilder sb = new StringBuilder(); + final int end = parserEntityName.getChildCount(); + sb.append( visitIdentifier( (HqlParser.IdentifierContext) parserEntityName.getChild( 0 ) ) ); + for ( int i = 2; i < end; i += 2 ) { + sb.append( '.' ); + sb.append( visitIdentifier( (HqlParser.IdentifierContext) parserEntityName.getChild( i ) ) ); + } + return sb.toString(); + } + + @Override + public String visitIdentifier(HqlParser.IdentifierContext ctx) { + final TerminalNode node = (TerminalNode) ctx.getChild( 0 ); + if ( node.getSymbol().getType() == HqlParser.QUOTED_IDENTIFIER ) { + return QuotingHelper.unquoteIdentifier( node.getText() ); + } + return node.getText(); + } + @Override public EntityDomainType visitEntityName(HqlParser.EntityNameContext parserEntityName) { - final String entityName = parserEntityName.fullNameText; - final EntityDomainType entityReference = resolveEntityReference( entityName ); + final String entityName = getEntityName( parserEntityName ); + final EntityDomainType entityReference = getCreationContext() + .getJpaMetamodel() + .getHqlEntityReference( entityName ); if ( entityReference == null ) { throw new UnknownEntityException( "Could not resolve entity name [" + entityName + "] as DML target", entityName ); } checkFQNEntityNameJpaComplianceViolationIfNeeded( entityName, entityReference ); + if ( entityReference instanceof SqmPolymorphicRootDescriptor && getCreationOptions().useStrictJpaCompliance() ) { + throw new StrictJpaComplianceViolation( + "Encountered the use of a non entity name [" + entityName + "], " + + "but strict JPQL compliance was requested which doesn't allow this", + StrictJpaComplianceViolation.Type.NON_ENTITY_NAME + ); + } return entityReference; } - private EntityDomainType resolveEntityReference(String entityName) { - log.debugf( "Attempting to resolve path [%s] as entity reference...", entityName ); - EntityDomainType reference = null; - try { - entityName = creationContext.getJpaMetamodel().qualifyImportableName( entityName ); - reference = creationContext.getJpaMetamodel().entity( entityName ); - } - catch (Exception ignore) { - } - - return reference; - } - - @Override public SqmFromClause visitFromClause(HqlParser.FromClauseContext parserFromClause) { final SqmFromClause fromClause; @@ -1436,7 +1428,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem public SqmRoot visitPathRoot(HqlParser.PathRootContext ctx) { final HqlParser.EntityNameContext entityNameContext = (HqlParser.EntityNameContext) ctx.getChild( 0 ); final List entityNameParseTreeChildren = entityNameContext.children; - final String name = entityNameContext.fullNameText; + final String name = getEntityName( entityNameContext ); log.debugf( "Handling root path - %s", name ); final EntityDomainType entityDescriptor = getCreationContext() @@ -1542,10 +1534,14 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem ); } } - return identifierContext.getText(); + return visitIdentifier( identifierContext ); } else { - return lastChild.getText(); + final TerminalNode node = (TerminalNode) lastChild; + if ( node.getSymbol().getType() == HqlParser.QUOTED_IDENTIFIER ) { + return QuotingHelper.unquoteIdentifier( node.getText() ); + } + return node.getText(); } } @@ -1569,7 +1565,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem private void consumeCrossJoin(HqlParser.CrossJoinContext parserJoin, SqmRoot sqmRoot) { final HqlParser.PathRootContext pathRootContext = (HqlParser.PathRootContext) parserJoin.getChild( 2 ); final HqlParser.EntityNameContext entityNameContext = (HqlParser.EntityNameContext) pathRootContext.getChild( 0 ); - final String name = entityNameContext.fullNameText; + final String name = getEntityName( entityNameContext ); SqmTreeCreationLogger.LOGGER.debugf( "Handling root path - %s", name ); @@ -1640,12 +1636,17 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem identificationVariableDefContext = null; } final String alias = visitIdentificationVariableDef( identificationVariableDefContext ); + final boolean fetch = parserJoin.getChild( 2 ) instanceof TerminalNode; + + if ( fetch && processingStateStack.depth() > 1 ) { + throw new SemanticException( "fetch not allowed in subquery from-elements" ); + } dotIdentifierConsumerStack.push( new QualifiedJoinPathConsumer( sqmRoot, joinType, - parserJoin.getChild( 2 ) instanceof TerminalNode, + fetch, alias, this ) @@ -1927,15 +1928,11 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem ctx = ctx.getChild( 0 ); } - if ( ctx instanceof HqlParser.PathContext && ctx.getChildCount() == 1 ) { + if ( ctx instanceof HqlParser.GeneralPathFragmentContext && ctx.getChildCount() == 1 ) { ctx = ctx.getChild( 0 ); - if ( ctx instanceof HqlParser.GeneralPathFragmentContext && ctx.getChildCount() == 1 ) { - ctx = ctx.getChild( 0 ); - - if ( ctx instanceof HqlParser.DotIdentifierSequenceContext ) { - return creationContext.getJpaMetamodel().getAllowedEnumLiteralTexts().get( ctx.getText() ); - } + if ( ctx instanceof HqlParser.DotIdentifierSequenceContext ) { + return creationContext.getJpaMetamodel().getAllowedEnumLiteralTexts().get( ctx.getText() ); } } } @@ -2008,10 +2005,12 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem final List> listExpressions = new ArrayList<>( estimatedSize ); for ( int i = 1; i < size; i++ ) { final ParseTree parseTree = tupleExpressionListContext.getChild( i ); - if ( parseTree instanceof HqlParser.ExpressionContext ) { - final HqlParser.ExpressionContext expressionContext = (HqlParser.ExpressionContext) parseTree; + if ( parseTree instanceof HqlParser.ExpressionOrPredicateContext ) { + final ParseTree child = parseTree.getChild( 0 ); + final HqlParser.ExpressionContext expressionContext; final Map, Enum> possibleEnumValues; - if ( isEnum && ( possibleEnumValues = getPossibleEnumValues( expressionContext ) ) != null ) { + if ( isEnum && child instanceof HqlParser.ExpressionContext + && ( possibleEnumValues = getPossibleEnumValues( expressionContext = (HqlParser.ExpressionContext) child ) ) != null ) { listExpressions.add( resolveEnumShorthandLiteral( expressionContext, @@ -2021,7 +2020,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem ); } else { - listExpressions.add( (SqmExpression) expressionContext.accept( this ) ); + listExpressions.add( (SqmExpression) child.accept( this ) ); } } } @@ -2367,7 +2366,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem final List> expressions = new ArrayList<>( estimateExpressionsCount ); for ( int i = 0; i < size; i++ ) { final ParseTree parseTree = parentContext.getChild( i ); - if ( parseTree instanceof HqlParser.ExpressionContext ) { + if ( parseTree instanceof HqlParser.ExpressionOrPredicateContext ) { expressions.add( (SqmExpression) parseTree.accept( this ) ); } } @@ -2524,6 +2523,36 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return (SqmExpression) ctx.getChild( 0 ).accept( this ); } + @Override + public SqmExpression visitUnaryNumericLiteralExpression(HqlParser.UnaryNumericLiteralExpressionContext ctx) { + final TerminalNode node = (TerminalNode) ctx.getChild( 1 ).getChild( 0 ); + final String text; + if ( ( (TerminalNode) ctx.getChild( 0 ).getChild( 0 ) ).getSymbol().getType() == HqlParser.MINUS ) { + text = "-" + node.getText(); + } + else { + text = node.getText(); + } + switch ( node.getSymbol().getType() ) { + case HqlParser.INTEGER_LITERAL: + return integerOrLongLiteral( text ); + case HqlParser.LONG_LITERAL: + return longLiteral( text ); + case HqlParser.BIG_INTEGER_LITERAL: + return bigIntegerLiteral( text ); + case HqlParser.HEX_LITERAL: + return hexLiteral( text ); + case HqlParser.FLOAT_LITERAL: + return floatLiteral( text ); + case HqlParser.DOUBLE_LITERAL: + return doubleLiteral( text ); + case HqlParser.BIG_DECIMAL_LITERAL: + return bigDecimalLiteral( text ); + default: + throw new ParsingException("Unexpected terminal node [" + text + "]"); + } + } + @Override public Object visitBinaryLiteral(HqlParser.BinaryLiteralContext ctx) { final TerminalNode firstNode = (TerminalNode) ctx.getChild( 0 ); @@ -2561,7 +2590,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem case HqlParser.STRING_LITERAL: return stringLiteral( node.getText() ); case HqlParser.INTEGER_LITERAL: - return integerLiteral( node.getText() ); + return integerOrLongLiteral( node.getText() ); case HqlParser.LONG_LITERAL: return longLiteral( node.getText() ); case HqlParser.BIG_INTEGER_LITERAL: @@ -2680,7 +2709,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem final TerminalNode firstChild = (TerminalNode) ctx.getChild( 0 ); final String timezoneText; if ( firstChild.getSymbol().getType() == HqlParser.STRING_LITERAL ) { - timezoneText = unescapeStringLiteral( ctx.getText() ); + timezoneText = QuotingHelper.unquoteStringLiteral( ctx.getText() ); } else { timezoneText = ctx.getText(); @@ -2864,77 +2893,9 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem ); } - private String unescapeStringLiteral(String text) { - // Unescape the parsed literal and handle escape sequences - final StringBuilder sb = new StringBuilder( text.length() - 2 ); - final int end = text.length() - 1; - final char delimiter = text.charAt( 0 ); - for ( int i = 1; i < end; i++ ) { - char c = text.charAt( i ); - switch ( c ) { - case '\'': - if ( delimiter == '\'' ) { - i++; - } - break; - case '"': - if ( delimiter == '"' ) { - i++; - } - break; - case '\\': - if ( ( i + 1 ) < end ) { - char nextChar = text.charAt( ++i ); - switch ( nextChar ) { - case 'b': - c = '\b'; - break; - case 't': - c = '\t'; - break; - case 'n': - c = '\n'; - break; - case 'f': - c = '\f'; - break; - case 'r': - c = '\r'; - break; - case '\\': - c = '\\'; - break; - case '\'': - c = '\''; - break; - case '"': - c = '"'; - break; - case '`': - c = '`'; - break; - case 'u': - c = (char) Integer.parseInt( text.substring( i + 1, i + 5 ), 16 ); - i += 4; - break; - default: - sb.append( '\\' ); - c = nextChar; - break; - } - } - break; - default: - break; - } - sb.append( c ); - } - return sb.toString(); - } - private SqmLiteral stringLiteral(String text) { return new SqmLiteral<>( - unescapeStringLiteral( text ), + QuotingHelper.unquoteStringLiteral( text ), resolveExpressableTypeBasic( String.class ), creationContext.getNodeBuilder() ); @@ -2950,6 +2911,35 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem ); } + private SqmLiteral integerOrLongLiteral(String text) { + try { + final Integer value = Integer.valueOf( text ); + return new SqmLiteral<>( + value, + resolveExpressableTypeBasic( Integer.class ), + creationContext.getNodeBuilder() + ); + } + catch (NumberFormatException e) { + // This is at least what 5.x did + try { + final Long value = Long.valueOf( text ); + return new SqmLiteral<>( + value, + resolveExpressableTypeBasic( Long.class ), + creationContext.getNodeBuilder() + ); + } + catch (NumberFormatException e2) { + e.addSuppressed( e2 ); + throw new LiteralNumberFormatException( + "Unable to convert sqm literal [" + text + "] to Integer", + e + ); + } + } + } + private SqmLiteral integerLiteral(String text) { try { final Integer value = Integer.valueOf( text ); @@ -3132,7 +3122,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem @Override public SqmExpression visitJpaNonStandardFunction(HqlParser.JpaNonStandardFunctionContext ctx) { - final String functionName = unescapeStringLiteral( ctx.getChild( 2 ).getText() ).toLowerCase(); + final String functionName = QuotingHelper.unquoteStringLiteral( ctx.getChild( 2 ).getText() ).toLowerCase(); final List> functionArguments; if ( ctx.getChildCount() > 4 ) { //noinspection unchecked @@ -3249,7 +3239,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem for ( ; i < size; i += 2 ) { // we handle the final argument differently... if ( i == lastIndex ) { - arguments.add( visitFinalFunctionArgument( (HqlParser.ExpressionContext) ctx.getChild( i ) ) ); + arguments.add( visitFinalFunctionArgument( ctx.getChild( i ) ) ); } else { arguments.add( (SqmTypedNode) ctx.getChild( i ).accept( this ) ); @@ -3275,7 +3265,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return arguments; } - private SqmExpression visitFinalFunctionArgument(HqlParser.ExpressionContext expression) { + private SqmExpression visitFinalFunctionArgument(ParseTree expression) { // the final argument to a function may accept multi-value parameter (varargs), // but only if we are operating in non-strict JPA mode parameterDeclarationContextStack.push( () -> !creationOptions.useStrictJpaCompliance() ); @@ -3467,7 +3457,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem @Override public Object visitFormat(HqlParser.FormatContext ctx) { - String format = unescapeStringLiteral( ctx.getChild( 0 ).getText() ); + String format = QuotingHelper.unquoteStringLiteral( ctx.getChild( 0 ).getText() ); if (!FORMAT.matcher(format).matches()) { throw new SemanticException("illegal format pattern: '" + format + "'"); } @@ -3843,7 +3833,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem @Override public SqmLiteral visitTrimCharacter(HqlParser.TrimCharacterContext ctx) { final String trimCharText = ctx != null - ? unescapeStringLiteral( ctx.getText() ) + ? QuotingHelper.unquoteStringLiteral( ctx.getText() ) : " "; // JPA says space is the default if ( trimCharText.length() != 1 ) { @@ -4059,7 +4049,19 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem final SqmPath indexedPath = pathPart.resolveIndexedAccess( indexExpression, !hasIndexContinuation, this ); if ( hasIndexContinuation ) { - return (SemanticPathPart) idxCtx.getChild( 4 ).accept( this ); + dotIdentifierConsumerStack.push( + new BasicDotIdentifierConsumer( indexedPath, this ) { + @Override + protected void reset() { + } + } + ); + try { + return (SemanticPathPart) idxCtx.getChild( 4 ).accept( this ); + } + finally { + dotIdentifierConsumerStack.pop(); + } } return indexedPath; } @@ -4079,7 +4081,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem assert identifierContext.getChildCount() == 1; dotIdentifierConsumer.consumeIdentifier( - identifierContext.getChild( 0 ).getText(), + visitIdentifier( identifierContext ), true, ! hasContinuations ); @@ -4090,7 +4092,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem final HqlParser.IdentifierContext identifier = (HqlParser.IdentifierContext) continuation.getChild( 1 ); assert identifier.getChildCount() == 1; dotIdentifierConsumer.consumeIdentifier( - identifier.getChild( 0 ).getText(), + visitIdentifier( identifier ), false, i >= numberOfContinuations ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SqmPathRegistryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SqmPathRegistryImpl.java index 6a0bc1f956..703718bcdc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SqmPathRegistryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SqmPathRegistryImpl.java @@ -8,13 +8,11 @@ package org.hibernate.query.hql.internal; import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.function.Function; -import org.hibernate.internal.util.MutableInteger; import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.query.NavigablePath; import org.hibernate.query.hql.HqlLogging; @@ -117,27 +115,6 @@ public class SqmPathRegistryImpl implements SqmPathRegistry { } } - @Override - public SqmPath findPath(NavigablePath path) { - final SqmPath found = sqmPathByPath.get( path ); - if ( found != null ) { - //noinspection unchecked - return (SqmPath) found; - } - - if ( associatedProcessingState.getParentProcessingState() != null ) { - final SqmFrom containingQueryFrom = associatedProcessingState.getParentProcessingState() - .getPathRegistry() - .findFromByPath( path ); - if ( containingQueryFrom != null ) { - // todo (6.0) create a correlation? - return containingQueryFrom; - } - } - - return null; - } - @Override public > X findFromByPath(NavigablePath navigablePath) { final SqmFrom found = sqmFromByPath.get( navigablePath ); @@ -247,21 +224,6 @@ public class SqmPathRegistryImpl implements SqmPathRegistry { return (X) sqmFrom; } - @Override - public SqmPath resolvePath(NavigablePath navigablePath, Function> creator) { - SqmTreeCreationLogger.LOGGER.tracef( "SqmProcessingIndex#resolvePath(NavigablePath) : %s", navigablePath ); - - final SqmPath existing = sqmPathByPath.get( navigablePath ); - if ( existing != null ) { - //noinspection unchecked - return (SqmPath) existing; - } - - final SqmPath sqmPath = creator.apply( navigablePath ); - register( sqmPath ); - return sqmPath; - } - private boolean definesAttribute(SqmPathSource containerType, String name) { return containerType.findSubPathSource( name ) != null; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/spi/SqmPathRegistry.java b/hibernate-core/src/main/java/org/hibernate/query/hql/spi/SqmPathRegistry.java index 75782a66cf..c9ca43c1a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/spi/SqmPathRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/spi/SqmPathRegistry.java @@ -71,22 +71,6 @@ public interface SqmPathRegistry { */ > X resolveFrom(SqmPath path); - /** - * Find an SqmPath by its NavigablePath. Will return a SqmFrom if the NavigablePath - * has (yet) been resolved to a SqmFrom. Otherwise, it will be a non-SqmFrom SqmPath - * - * @return matching SqmPath or {@code null} - */ - SqmPath findPath(NavigablePath path); - - /** - * Similar to {@link #findPath}, but accepting a producer to be used - * to create and register a SqmPath if none yet registered. - * - * @return The existing or just-created SqmPath - */ - SqmPath resolvePath(NavigablePath path, Function> creator); - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // SqmSelection diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryHelper.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryHelper.java index 1d62a81247..d26b9f8132 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryHelper.java @@ -27,8 +27,7 @@ public class QueryHelper { return types[0]; } - //noinspection unchecked - SqmExpressable highest = highestPrecedenceType2( (SqmExpressable) types[0], types[1] ); + SqmExpressable highest = highestPrecedenceType2( types[0], types[1] ); for ( int i = 2; i < types.length; i++ ) { highest = highestPrecedenceType2( highest, types[i] ); } @@ -58,6 +57,9 @@ public class QueryHelper { } // any other precedence rules? + if ( type2.getExpressableJavaTypeDescriptor().isWider( type1.getExpressableJavaTypeDescriptor() ) ) { + return type2; + } return type1; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java index d2a8b6d3ae..069b14a35c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java @@ -9,6 +9,7 @@ package org.hibernate.query.internal; import java.util.ArrayList; import java.util.Arrays; import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -195,6 +196,8 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings { for ( QueryParameterBinding binding : parameterBindingMap.values() ) { final MappingModelExpressable mappingType = determineMappingType( binding, persistenceContext ); assert mappingType instanceof JavaTypedExpressable; + //noinspection unchecked + final JavaType javaType = ( (JavaTypedExpressable) mappingType ).getExpressableJavaTypeDescriptor(); if ( binding.isMultiValued() ) { for ( Object bindValue : binding.getBindValues() ) { @@ -203,8 +206,9 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings { final Object disassembled = mappingType.disassemble( bindValue, persistenceContext ); allBindValues.add( disassembled ); - //noinspection unchecked - final int valueHashCode = ( (JavaTypedExpressable) mappingType ).getExpressableJavaTypeDescriptor().extractHashCode( bindValue ); + final int valueHashCode = bindValue != null + ? javaType.extractHashCode( bindValue ) + : 0; hashCode = 31 * hashCode + valueHashCode; } @@ -215,8 +219,9 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings { final Object disassembled = mappingType.disassemble( bindValue, persistenceContext ); allBindValues.add( disassembled ); - //noinspection unchecked - final int valueHashCode = ( (JavaTypedExpressable) mappingType ).getExpressableJavaTypeDescriptor().extractHashCode( bindValue ); + final int valueHashCode = bindValue != null + ? javaType.extractHashCode( bindValue ) + : 0; hashCode = 31 * hashCode + valueHashCode; } @@ -249,12 +254,19 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings { } if ( binding.isMultiValued() ) { - final Object firstBindValue = binding.getBindValues().iterator().next(); - return typeConfiguration.getBasicTypeForJavaType( firstBindValue.getClass() ); + final Iterator iterator = binding.getBindValues().iterator(); + Object firstNonNullBindValue = null; + if ( iterator.hasNext() && firstNonNullBindValue == null ) { + firstNonNullBindValue = iterator.next(); + } + if ( firstNonNullBindValue != null ) { + return typeConfiguration.getBasicTypeForJavaType( firstNonNullBindValue.getClass() ); + } } - else { + else if ( binding.getBindValue() != null ) { return typeConfiguration.getBasicTypeForJavaType( binding.getBindValue().getClass() ); } + return typeConfiguration.getBasicTypeForJavaType( binding.getBindType().getJavaType() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java index 3b097ec1f3..00dff7d5db 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java @@ -287,7 +287,7 @@ public class ResultSetMappingImpl implements ResultSetMapping { JdbcValuesMetadata jdbcResultsMetadata, SessionFactoryImplementor sessionFactory) { final int jdbcPosition = valuesArrayPosition + 1; - final BasicType jdbcMapping = jdbcResultsMetadata.resolveType( jdbcPosition, null ); + final BasicType jdbcMapping = jdbcResultsMetadata.resolveType( jdbcPosition, null, sessionFactory ); final String name = jdbcResultsMetadata.resolveColumnName( jdbcPosition ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java index c7d83cb2e0..08b2148983 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java @@ -119,7 +119,11 @@ public class CompleteResultBuilderBasicValuedStandard implements CompleteResultB basicType = explicitType; } else { - basicType = jdbcResultsMetadata.resolveType( jdbcPosition, explicitJavaTypeDescriptor ); + basicType = jdbcResultsMetadata.resolveType( + jdbcPosition, + explicitJavaTypeDescriptor, + sessionFactory + ); } final int valuesArrayPosition = ResultsHelper.jdbcPositionToValuesArrayPosition( jdbcPosition ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilder.java index e76d534e0a..7205eb5b4c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilder.java @@ -6,6 +6,8 @@ */ package org.hibernate.query.results.dynamic; +import java.util.List; + import org.hibernate.query.NativeQuery; import org.hibernate.query.results.FetchBuilder; import org.hibernate.sql.results.graph.Fetchable; @@ -14,4 +16,5 @@ import org.hibernate.sql.results.graph.Fetchable; * @author Steve Ebersole */ public interface DynamicFetchBuilder extends FetchBuilder, NativeQuery.ReturnProperty { + List getColumnAliases(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderLegacy.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderLegacy.java index 683c862fdb..8eae4729e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderLegacy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderLegacy.java @@ -209,6 +209,11 @@ public class DynamicFetchBuilderLegacy implements DynamicFetchBuilder, NativeQue return this; } + @Override + public List getColumnAliases() { + return columnNames; + } + @Override public NativeQuery.FetchReturn setLockMode(LockMode lockMode) { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderStandard.java index 74d108997d..69a6ec12a1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderStandard.java @@ -129,4 +129,9 @@ public class DynamicFetchBuilderStandard columnNames.add( columnAlias ); return this; } + + @Override + public List getColumnAliases() { + return columnNames; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicConverted.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicConverted.java index fdd1879dc3..a1683f4058 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicConverted.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicConverted.java @@ -122,7 +122,13 @@ public class DynamicResultBuilderBasicConverted implements DynamicResultBui else { jdbcPosition = currentJdbcPosition; } - final BasicType basicType = jdbcResultsMetadata.resolveType( jdbcPosition, basicValueConverter.getRelationalJavaDescriptor() ); + final BasicType basicType = jdbcResultsMetadata.resolveType( + jdbcPosition, + basicValueConverter.getRelationalJavaDescriptor(), + domainResultCreationState.getSqlAstCreationState() + .getCreationContext() + .getSessionFactory() + ); final int valuesArrayPosition = ResultsHelper.jdbcPositionToValuesArrayPosition( jdbcPosition ); return new SqlSelectionImpl( valuesArrayPosition, (BasicValuedMapping) basicType ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicStandard.java index de114ec73a..c32d51aa0e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicStandard.java @@ -136,7 +136,11 @@ public class DynamicResultBuilderBasicStandard implements DynamicResultBuilderBa basicType = explicitType; } else { - basicType = jdbcResultsMetadata.resolveType( jdbcPosition, explicitJavaTypeDescriptor ); + basicType = jdbcResultsMetadata.resolveType( + jdbcPosition, + explicitJavaTypeDescriptor, + sessionFactory + ); } return new SqlSelectionImpl( valuesArrayPosition, (BasicValuedMapping) basicType ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityStandard.java index 88539e7828..ac6ddffe31 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityStandard.java @@ -18,6 +18,7 @@ import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping; import org.hibernate.query.NativeQuery; import org.hibernate.query.NavigablePath; import org.hibernate.query.results.DomainResultCreationStateImpl; @@ -169,13 +170,23 @@ public class DynamicResultBuilderEntityStandard } ); final TableReference tableReference = tableGroup.getPrimaryTableReference(); - - if ( idColumnNames != null ) { + final List idColumnAliases; + final DynamicFetchBuilder idFetchBuilder; + if ( this.idColumnNames != null ) { + idColumnAliases = this.idColumnNames; + } + else if ( ( idFetchBuilder = findIdFetchBuilder() ) != null ) { + idColumnAliases = idFetchBuilder.getColumnAliases(); + } + else { + idColumnAliases = null; + } + if ( idColumnAliases != null ) { final EntityIdentifierMapping identifierMapping = entityMapping.getIdentifierMapping(); identifierMapping.forEachSelectable( (selectionIndex, selectableMapping) -> { resolveSqlSelection( - idColumnNames.get( selectionIndex ), + idColumnAliases.get( selectionIndex ), createColumnReferenceKey( tableReference, selectableMapping.getSelectionExpression() ), selectableMapping.getJdbcMapping(), jdbcResultsMetadata, @@ -229,6 +240,14 @@ public class DynamicResultBuilderEntityStandard } } + private DynamicFetchBuilder findIdFetchBuilder() { + final EntityIdentifierMapping identifierMapping = entityMapping.getIdentifierMapping(); + if ( identifierMapping instanceof SingleAttributeIdentifierMapping ) { + return findFetchBuilder( ( (SingleAttributeIdentifierMapping) identifierMapping ).getAttributeName() ); + } + return findFetchBuilder( identifierMapping.getPartName() ); + } + private void resolveSqlSelection( String columnAlias, String columnKey, diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEmbeddable.java b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEmbeddable.java index cbe81497b7..1291b04a52 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEmbeddable.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEmbeddable.java @@ -28,7 +28,6 @@ import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import static org.hibernate.query.results.ResultsHelper.impl; -import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey; /** * @author Steve Ebersole diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java index 6dae496ba2..78a9cf39ea 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java @@ -1545,7 +1545,7 @@ public abstract class AbstractQuery implements QueryImplementor { throw getSession().getExceptionConverter().convert( e ); } finally { - afterQuery( true ); + afterQuery( success ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/StrictJpaComplianceViolation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/StrictJpaComplianceViolation.java index 7588b2ae45..775a3a4860 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/StrictJpaComplianceViolation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/StrictJpaComplianceViolation.java @@ -30,6 +30,7 @@ public class StrictJpaComplianceViolation extends SemanticException { LIMIT_OFFSET_CLAUSE( "use of LIMIT/OFFSET clause" ), IDENTIFICATION_VARIABLE_NOT_DECLARED_IN_FROM_CLAUSE( "use of an alias not declared in the FROM clause" ), FQN_ENTITY_NAME( "use of FQN for entity name" ), + NON_ENTITY_NAME( "use of class or interface FQN for entity name" ), IMPLICIT_TREAT( "use of implicit treat" ), MIXED_POSITIONAL_NAMED_PARAMETERS( "mix of positional and named parameters" ), ; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java index 4d1e326a4f..92752cca06 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java @@ -78,7 +78,7 @@ public class SelfRenderingSqmFunction extends SqmFunction { getRenderingSupport(), resolveSqlAstArguments( getArguments(), walker ), resultType, - getMappingModelExpressable( walker, resultType ) + resultType == null ? null : getMappingModelExpressable( walker, resultType ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AggregatedNonSelectQueryPlanImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AggregatedNonSelectQueryPlanImpl.java new file mode 100644 index 0000000000..70f2ff1850 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AggregatedNonSelectQueryPlanImpl.java @@ -0,0 +1,30 @@ +/* + * 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.query.sqm.internal; + +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.spi.NonSelectQueryPlan; + +/** + * @author Christian Beikov + */ +public class AggregatedNonSelectQueryPlanImpl implements NonSelectQueryPlan { + private final NonSelectQueryPlan[] aggregatedQueryPlans; + + public AggregatedNonSelectQueryPlanImpl(NonSelectQueryPlan[] aggregatedQueryPlans) { + this.aggregatedQueryPlans = aggregatedQueryPlans; + } + + @Override + public int executeUpdate(DomainQueryExecutionContext executionContext) { + int updated = 0; + for ( NonSelectQueryPlan aggregatedQueryPlan : aggregatedQueryPlans ) { + updated += aggregatedQueryPlan.executeUpdate( executionContext ); + } + return updated; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AggregatedSelectQueryPlanImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AggregatedSelectQueryPlanImpl.java index 2f7fd8a0d4..e5d19bb865 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AggregatedSelectQueryPlanImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AggregatedSelectQueryPlanImpl.java @@ -12,6 +12,7 @@ import java.util.List; import org.hibernate.ScrollMode; import org.hibernate.internal.EmptyScrollableResults; +import org.hibernate.query.Limit; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.query.spi.SelectQueryPlan; @@ -30,13 +31,41 @@ public class AggregatedSelectQueryPlanImpl implements SelectQueryPlan { @Override public List performList(DomainQueryExecutionContext executionContext) { - if ( executionContext.getQueryOptions().getEffectiveLimit().getMaxRowsJpa() == 0 ) { + final Limit effectiveLimit = executionContext.getQueryOptions().getEffectiveLimit(); + final int maxRowsJpa = effectiveLimit.getMaxRowsJpa(); + if ( maxRowsJpa == 0 ) { return Collections.emptyList(); } + int elementsToSkip = effectiveLimit.getFirstRowJpa(); final List overallResults = new ArrayList<>(); for ( SelectQueryPlan aggregatedQueryPlan : aggregatedQueryPlans ) { - overallResults.addAll( aggregatedQueryPlan.performList( executionContext ) ); + final List list = aggregatedQueryPlan.performList( executionContext ); + final int size = list.size(); + if ( size <= elementsToSkip ) { + // More elements to skip than the collection size + elementsToSkip -= size; + continue; + } + final int availableElements = size - elementsToSkip; + if ( overallResults.size() + availableElements >= maxRowsJpa ) { + // This result list is the last one i.e. fulfills the limit + final int end = elementsToSkip + ( maxRowsJpa - overallResults.size() ); + for ( int i = elementsToSkip; i < end; i++ ) { + overallResults.add( list.get( i ) ); + } + break; + } + else if ( elementsToSkip > 0 ) { + // We can skip a part of this result list + for ( int i = availableElements; i < size; i++ ) { + overallResults.add( list.get( i ) ); + } + elementsToSkip = 0; + } + else { + overallResults.addAll( list ); + } } return overallResults; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java index 9b44542a19..033f477326 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java @@ -34,6 +34,7 @@ import org.hibernate.query.sqm.sql.SqmTranslation; import org.hibernate.query.sqm.sql.SqmTranslator; import org.hibernate.query.sqm.sql.SqmTranslatorFactory; import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.query.sqm.tree.select.SqmSelection; import org.hibernate.sql.ast.SqlAstTranslator; @@ -239,12 +240,17 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { SqmSelectStatement sqm, QueryOptions queryOptions) { final List aliases = new ArrayList<>(); - sqm.getQuerySpec().getSelectClause().getSelections().forEach( - sqmSelection -> - sqmSelection.getSelectableNode().visitSubSelectableNodes( - subSelection -> aliases.add( subSelection.getAlias() ) - ) - ); + for ( SqmSelection sqmSelection : sqm.getQuerySpec().getSelectClause().getSelections() ) { + // The row a tuple transformer gets to see only contains 1 element for a dynamic instantiation + if ( sqmSelection.getSelectableNode() instanceof SqmDynamicInstantiation ) { + aliases.add( sqmSelection.getAlias() ); + } + else { + sqmSelection.getSelectableNode().visitSubSelectableNodes( + subSelection -> aliases.add( subSelection.getAlias() ) + ); + } + } return new RowTransformerTupleTransformerAdapter<>( ArrayHelper.toStringArray( aliases ), queryOptions.getTupleTransformer() diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java index aec4510e75..1bc7426640 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java @@ -31,6 +31,7 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.collections.IdentitySet; import org.hibernate.metamodel.model.domain.BasicDomainType; import org.hibernate.metamodel.model.domain.DomainType; +import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode; import org.hibernate.query.Query; @@ -226,7 +227,7 @@ public class QuerySqmImpl SqmUtil.verifyIsSelectStatement( sqmStatement, null ); final SqmQueryPart queryPart = ( (SqmSelectStatement) sqmStatement ).getQueryPart(); // For criteria queries, we have to validate the fetch structure here - queryPart.validateQueryGroupFetchStructure(); + queryPart.validateFetchStructureAndOwners(); visitQueryReturnType( queryPart, resultType, @@ -627,7 +628,12 @@ public class QuerySqmImpl executionContextToUse = this; } else { - executionContextToUse = new DelegatingDomainQueryExecutionContext( this ); + executionContextToUse = new DelegatingDomainQueryExecutionContext( this ) { + @Override + public QueryOptions getQueryOptions() { + return normalizedQueryOptions; + } + }; } } else { @@ -792,11 +798,23 @@ public class QuerySqmImpl } private NonSelectQueryPlan buildDeleteQueryPlan() { - final SqmDeleteStatement sqmDelete = (SqmDeleteStatement) getSqmStatement(); + final SqmDeleteStatement[] concreteSqmStatements = QuerySplitter.split( + (SqmDeleteStatement) getSqmStatement(), + getSessionFactory() + ); - final String entityNameToDelete = sqmDelete.getTarget().getReferencedPathSource().getHibernateEntityName(); + if ( concreteSqmStatements.length > 1 ) { + return buildAggregatedDeleteQueryPlan( concreteSqmStatements ); + } + else { + return buildConcreteDeleteQueryPlan( concreteSqmStatements[0] ); + } + } + + private NonSelectQueryPlan buildConcreteDeleteQueryPlan(SqmDeleteStatement sqmDelete) { + final EntityDomainType entityDomainType = sqmDelete.getTarget().getReferencedPathSource(); + final String entityNameToDelete = entityDomainType.getHibernateEntityName(); final EntityPersister entityDescriptor = getSessionFactory().getDomainModel().findEntityDescriptor( entityNameToDelete ); - final SqmMultiTableMutationStrategy multiTableStrategy = entityDescriptor.getSqmMultiTableMutationStrategy(); if ( multiTableStrategy == null ) { return new SimpleDeleteQueryPlan( entityDescriptor, sqmDelete, domainParameterXref ); @@ -806,6 +824,16 @@ public class QuerySqmImpl } } + private NonSelectQueryPlan buildAggregatedDeleteQueryPlan(SqmDeleteStatement[] concreteSqmStatements) { + final NonSelectQueryPlan[] aggregatedQueryPlans = new NonSelectQueryPlan[ concreteSqmStatements.length ]; + + for ( int i = 0, x = concreteSqmStatements.length; i < x; i++ ) { + aggregatedQueryPlans[i] = buildConcreteDeleteQueryPlan( concreteSqmStatements[i] ); + } + + return new AggregatedNonSelectQueryPlanImpl( aggregatedQueryPlans ); + } + private NonSelectQueryPlan buildUpdateQueryPlan() { final SqmUpdateStatement sqmUpdate = (SqmUpdateStatement) getSqmStatement(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java index 2103e47508..c5f6306326 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java @@ -201,6 +201,7 @@ public class MatchingIdSelectionHelper { final MultiTableSqmMutationConverter sqmConverter = new MultiTableSqmMutationConverter( entityDescriptor, + sqmMutationStatement, sqmMutationStatement.getTarget(), domainParameterXref, executionContext.getQueryOptions(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java index 12a7dc8532..ee00a601fa 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java @@ -21,6 +21,7 @@ import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter; import org.hibernate.query.sqm.sql.internal.DomainResultProducer; import org.hibernate.query.sqm.sql.internal.SqlAstProcessingStateImpl; import org.hibernate.query.sqm.sql.internal.SqlAstQueryPartProcessingStateImpl; +import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; @@ -63,6 +64,7 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter statement, SqmRoot sqmRoot, DomainParameterXref domainParameterXref, QueryOptions queryOptions, @@ -71,6 +73,7 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter statement, SqmRoot sqmRoot, String sourceAlias, DomainParameterXref domainParameterXref, @@ -90,7 +94,7 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter assignmentConsumer) { - final Assignable assignable = (Assignable) sqmAssignment.getTargetPath().accept( this ); - - final Expression value = (Expression) sqmAssignment.getValue().accept( this ); - - assignmentConsumer.accept( new Assignment( assignable, value ) ); - } - @Override public Assignment visitAssignment(SqmAssignment sqmAssignment) { return new Assignment( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java index ef1ed2d474..5a70a42bf4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java @@ -104,7 +104,7 @@ public class SqmMutationStrategyHelper { else { // element-collection or many-to-many - delete the collection-table row - final TableReference tableReference = new TableReference( separateCollectionTable, null, true, sessionFactory ); + final TableReference tableReference = new TableReference( separateCollectionTable, DeleteStatement.DEFAULT_ALIAS, true, sessionFactory ); final DeleteStatement sqlAstDelete = new DeleteStatement( tableReference, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java index 34b437657e..a9f1b0ef72 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java @@ -117,6 +117,7 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler final MultiTableSqmMutationConverter sqmConverter = new MultiTableSqmMutationConverter( entityDescriptor, + sqmMutationStatement, sqmMutationStatement.getTarget(), explicitDmlTargetAlias, domainParameterXref, @@ -140,6 +141,7 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler columnReference -> {}, (sqmParam, mappingType, jdbcParameters) -> paramTypeResolutions.put( sqmParam, mappingType ) ); + sqmConverter.pruneTableGroupJoins(); final CteStatement idSelectCte = new CteStatement( BaseSqmToSqlAstConverter.createCteTable( getCteTable(), factory ), @@ -165,7 +167,7 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler .getSqlAstTranslatorFactory() .buildSelectTranslator( factory, statement ); - final Expression count = createCountStart( factory, sqmConverter ); + final Expression count = createCountStar( factory, sqmConverter ); domainResults.add( new BasicResult<>( 0, @@ -215,7 +217,7 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler return ( (Number) list.get( 0 ) ).intValue(); } - private Expression createCountStart( + private Expression createCountStar( SessionFactoryImplementor factory, MultiTableSqmMutationConverter sqmConverter) { final SqmExpression arg = new SqmStar( factory.getNodeBuilder() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/ExecuteWithIdTableHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/ExecuteWithIdTableHelper.java index ba87abfa63..d967e6011a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/ExecuteWithIdTableHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/ExecuteWithIdTableHelper.java @@ -160,7 +160,7 @@ public final class ExecuteWithIdTableHelper { public static QuerySpec createIdTableSelectQuerySpec( IdTable idTable, - Function sessionUidAccess, + Function sessionUidAccess, EntityMappingType entityDescriptor, ExecutionContext executionContext) { return createIdTableSelectQuerySpec( idTable, null, sessionUidAccess, entityDescriptor, executionContext ); @@ -169,14 +169,14 @@ public final class ExecuteWithIdTableHelper { public static QuerySpec createIdTableSelectQuerySpec( IdTable idTable, ModelPart fkModelPart, - Function sessionUidAccess, + Function sessionUidAccess, EntityMappingType entityDescriptor, ExecutionContext executionContext) { final QuerySpec querySpec = new QuerySpec( false ); final TableReference idTableReference = new TableReference( idTable.getTableExpression(), - null, + IdTable.DEFAULT_ALIAS, true, executionContext.getSession().getFactory() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/IdTable.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/IdTable.java index 389b2f4d43..183c39c01c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/IdTable.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/IdTable.java @@ -7,23 +7,33 @@ package org.hibernate.query.sqm.mutation.internal.idtable; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.function.Function; +import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.Exportable; import org.hibernate.dialect.Dialect; +import org.hibernate.mapping.Collection; +import org.hibernate.mapping.Column; import org.hibernate.mapping.Contributable; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Selectable; +import org.hibernate.mapping.Value; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; -import org.hibernate.persister.entity.Joinable; -import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; /** * @author Steve Ebersole */ public class IdTable implements Exportable, Contributable { + + public static final String DEFAULT_ALIAS = "idtable_"; + private final EntityMappingType entityDescriptor; private final String qualifiedTableName; @@ -34,27 +44,38 @@ public class IdTable implements Exportable, Contributable { public IdTable( EntityMappingType entityDescriptor, - Function idTableNameAdjuster, - Dialect dialect) { + Function idTableNameAdjuster, + Dialect dialect, + RuntimeModelCreationContext runtimeModelCreationContext) { this.entityDescriptor = entityDescriptor; - this.qualifiedTableName = idTableNameAdjuster.apply( - // The table name might be a sub-query, which is inappropriate for an id table name - entityDescriptor.getEntityPersister().getSynchronizedQuerySpaces()[0] - ); + // The table name might be a sub-query, which is inappropriate for an id table name + final String originalTableName = entityDescriptor.getEntityPersister().getSynchronizedQuerySpaces()[0]; + if ( Identifier.isQuoted( originalTableName ) ) { + this.qualifiedTableName = dialect.quote( idTableNameAdjuster.apply( Identifier.unQuote( originalTableName ) ) ); + } + else { + this.qualifiedTableName = idTableNameAdjuster.apply( originalTableName ); + } + + final PersistentClass entityBinding = runtimeModelCreationContext.getBootModel() + .getEntityBinding( entityDescriptor.getEntityName() ); + + final Iterator itr = entityBinding.getTable().getPrimaryKey().getColumnIterator(); + final Iterator jdbcMappings = entityDescriptor.getIdentifierMapping().getJdbcMappings().iterator(); + while ( itr.hasNext() ) { + final Column column = itr.next(); + final JdbcMapping jdbcMapping = jdbcMappings.next(); + columns.add( + new IdTableColumn( + this, + column.getText( dialect ), + jdbcMapping, + column.getSqlType( dialect, runtimeModelCreationContext.getMetadata() ) + ) + ); + } - entityDescriptor.getIdentifierMapping().forEachSelectable( - (columnIndex, selection) -> columns.add( - new IdTableColumn( - this, - selection.getSelectionExpression(), - selection.getJdbcMapping(), - dialect.getTypeName( - selection.getJdbcMapping().getJdbcTypeDescriptor() - ) - ) - ) - ); entityDescriptor.visitSubTypeAttributeMappings( attribute -> { if ( attribute instanceof PluralAttributeMapping ) { @@ -64,17 +85,27 @@ public class IdTable implements Exportable, Contributable { // Ensure that the FK target columns are available final ModelPart fkTarget = pluralAttribute.getKeyDescriptor().getTargetPart(); if ( !( fkTarget instanceof EntityIdentifierMapping ) ) { + final Value value = entityBinding.getSubclassProperty( pluralAttribute.getAttributeName() ) + .getValue(); + final Iterator columnIterator = ( (Collection) value ).getKey() + .getColumnIterator(); fkTarget.forEachSelectable( - (columnIndex, selection) -> columns.add( - new IdTableColumn( - this, - selection.getSelectionExpression(), - selection.getJdbcMapping(), - dialect.getTypeName( - selection.getJdbcMapping().getJdbcTypeDescriptor() + (columnIndex, selection) -> { + final Selectable selectable = columnIterator.next(); + if ( selectable instanceof Column ) { + columns.add( + new IdTableColumn( + this, + selectable.getText( dialect ), + selection.getJdbcMapping(), + ( (Column) selectable ).getSqlType( + dialect, + runtimeModelCreationContext.getMetadata() + ) ) - ) - ) + ); + } + } ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java index ce68aa54ed..a07dadadbe 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java @@ -12,7 +12,6 @@ import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -23,6 +22,7 @@ import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.FilterHelper; +import org.hibernate.internal.util.MutableBoolean; import org.hibernate.internal.util.MutableInteger; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; @@ -51,7 +51,7 @@ import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.expression.SqlTuple; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.from.UnionTableGroup; +import org.hibernate.sql.ast.tree.from.UnionTableReference; import org.hibernate.sql.ast.tree.predicate.FilterPredicate; import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; import org.hibernate.sql.ast.tree.predicate.Predicate; @@ -107,9 +107,9 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle this.idTableExporterAccess = idTableExporterAccess; this.sessionUidAccess = sessionUidAccess; this.sessionFactory = sessionFactory; - - converter = new MultiTableSqmMutationConverter( + this.converter = new MultiTableSqmMutationConverter( entityDescriptor, + sqmDelete, sqmDelete.getTarget(), domainParameterXref, queryOptions, @@ -149,12 +149,12 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle // 2) we also inspect each ColumnReference that is part of the where-clause to see which // table it comes from. if all of the referenced columns (if any at all) are from the root table // we can perform all of the deletes without using an id-table - final AtomicBoolean needsIdTableWrapper = new AtomicBoolean( false ); + final MutableBoolean needsIdTableWrapper = new MutableBoolean( false ); Predicate predicate = converter.visitWhereClause( sqmDelete.getWhereClause(), columnReference -> { if ( ! hierarchyRootTableReference.getIdentificationVariable().equals( columnReference.getQualifier() ) ) { - needsIdTableWrapper.set( true ); + needsIdTableWrapper.setValue( true ); } }, (sqmParameter, mappingType, jdbcParameters) -> { @@ -172,11 +172,17 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle deletingTableGroup ); if ( filterPredicate != null ) { - needsIdTableWrapper.set( true ); + needsIdTableWrapper.setValue( true ); predicate = SqlAstTreeHelper.combinePredicates( predicate, filterPredicate ); } + converter.pruneTableGroupJoins(); - boolean needsIdTable = needsIdTableWrapper.get(); + // We need an id table if we want to delete from an intermediate table to avoid FK violations + // The intermediate table has a FK to the root table, so we can't delete from the root table first + // Deleting from the intermediate table first also isn't possible, + // because that is the source for deletion in other tables, hence we need an id table + final boolean needsIdTable = needsIdTableWrapper.getValue() + || entityDescriptor != entityDescriptor.getRootEntityDescriptor(); final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ); @@ -208,17 +214,9 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle Map paramTypeResolutions, SqlExpressionResolver sqlExpressionResolver, ExecutionContext executionContext) { - final EntityPersister rootEntityPersister; - final String rootEntityName = entityDescriptor.getEntityPersister().getRootEntityName(); - if ( rootEntityName.equals( entityDescriptor.getEntityName() ) ) { - rootEntityPersister = entityDescriptor.getEntityPersister(); - } - else { - rootEntityPersister = sessionFactory.getDomainModel().findEntityDescriptor( rootEntityName ); - } - - final MutableInteger rows = new MutableInteger(); + assert entityDescriptor == entityDescriptor.getRootEntityDescriptor(); + final EntityPersister rootEntityPersister = entityDescriptor.getEntityPersister(); final String rootTableName = ( (Joinable) rootEntityPersister ).getTableName(); final TableReference rootTableReference = tableGroup.resolveTableReference( tableGroup.getNavigablePath(), @@ -247,48 +245,114 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle executionContext.getSession() ); - entityDescriptor.visitConstraintOrderedTables( - (tableExpression, tableKeyColumnVisitationSupplier) -> { - if ( tableExpression.equals( rootTableName ) ) { - rows.set( - deleteFromRootTableWithoutIdTable( - rootTableReference, - suppliedPredicate, - jdbcParameterBindings, - executionContext - ) - ); + SqmMutationStrategyHelper.cleanUpCollectionTables( + entityDescriptor, + (tableReference, attributeMapping) -> { + // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup + if ( suppliedPredicate == null ) { + return null; + } + final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); + final QuerySpec idSelectFkSubQuery; + // todo (6.0): based on the location of the attribute mapping, we could prune the table group of the subquery + if ( fkDescriptor.getTargetPart() instanceof EntityIdentifierMapping ) { + idSelectFkSubQuery = matchingIdSubQuerySpec; } else { - rows.set( - rows.get() + deleteFromNonRootTableWithoutIdTable( - resolveUnionTableReference( tableGroup, tableExpression ), + idSelectFkSubQuery = ExecuteWithoutIdTableHelper.createIdMatchingSubQuerySpec( + tableGroup.getNavigablePath(), + rootTableReference, + suppliedPredicate, + rootEntityPersister, + sqlExpressionResolver, + sessionFactory + ); + } + return new InSubQueryPredicate( + MappingModelHelper.buildColumnReferenceExpression( + fkDescriptor, + null, + sessionFactory + ), + idSelectFkSubQuery, + false + ); + + }, + jdbcParameterBindings, + executionContext + ); + + if ( rootTableReference instanceof UnionTableReference ) { + final MutableInteger rows = new MutableInteger(); + entityDescriptor.visitConstraintOrderedTables( + (tableExpression, tableKeyColumnVisitationSupplier) -> { + final TableReference tableReference = new TableReference( + tableExpression, + tableGroup.getPrimaryTableReference().getIdentificationVariable(), + false, + sessionFactory + ); + final QuerySpec idMatchingSubQuerySpec; + // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup + if ( suppliedPredicate == null ) { + idMatchingSubQuerySpec = null; + } + else { + idMatchingSubQuerySpec = matchingIdSubQuerySpec; + } + rows.plus( + deleteFromNonRootTableWithoutIdTable( + tableReference, tableKeyColumnVisitationSupplier, sqlExpressionResolver, tableGroup, - matchingIdSubQuerySpec, + idMatchingSubQuerySpec, jdbcParameterBindings, executionContext ) ); } - } - ); - - return rows.get(); - } - - private TableReference resolveUnionTableReference(TableGroup tableGroup, String tableExpression) { - if ( tableGroup instanceof UnionTableGroup ) { - return new TableReference( - tableExpression, - tableGroup.getPrimaryTableReference().getIdentificationVariable(), - false, - sessionFactory ); + return rows.get(); } else { - return tableGroup.getTableReference( tableGroup.getNavigablePath(), tableExpression, true, true ); + entityDescriptor.visitConstraintOrderedTables( + (tableExpression, tableKeyColumnVisitationSupplier) -> { + if ( !tableExpression.equals( rootTableName ) ) { + final TableReference tableReference = tableGroup.getTableReference( + tableGroup.getNavigablePath(), + tableExpression, + true, + true + ); + final QuerySpec idMatchingSubQuerySpec; + // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup + if ( suppliedPredicate == null ) { + idMatchingSubQuerySpec = null; + } + else { + idMatchingSubQuerySpec = matchingIdSubQuerySpec; + } + deleteFromNonRootTableWithoutIdTable( + tableReference, + tableKeyColumnVisitationSupplier, + sqlExpressionResolver, + tableGroup, + idMatchingSubQuerySpec, + jdbcParameterBindings, + executionContext + ); + } + } + ); + + return deleteFromRootTableWithoutIdTable( + rootTableReference, + suppliedPredicate, + jdbcParameterBindings, + executionContext + ); } } @@ -304,7 +368,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle ); } - private int deleteFromNonRootTableWithoutIdTable( + private int deleteFromNonRootTableWithoutIdTable( TableReference targetTableReference, Supplier> tableKeyColumnVisitationSupplier, SqlExpressionResolver sqlExpressionResolver, @@ -315,50 +379,62 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle assert targetTableReference != null; log.tracef( "deleteFromNonRootTable - %s", targetTableReference.getTableExpression() ); - /* - * delete from sub_table - * where sub_id in ( - * select root_id from root_table - * where {predicate} - * ) - */ - - /* - * Create the `sub_id` reference as the LHS of the in-subquery predicate - */ - final List deletingTableColumnRefs = new ArrayList<>(); - tableKeyColumnVisitationSupplier.get().accept( - (columnIndex, selection) -> { - assert targetTableReference.getTableReference( selection.getContainingTableExpression() ) != null; - - final Expression expression = sqlExpressionResolver.resolveSqlExpression( - SqlExpressionResolver.createColumnReferenceKey( targetTableReference, selection.getSelectionExpression() ), - sqlAstProcessingState -> new ColumnReference( - targetTableReference, - selection, - sessionFactory - ) - ); - - deletingTableColumnRefs.add( (ColumnReference) expression ); - } + final TableReference deleteTableReference = new TableReference( + targetTableReference.getTableExpression(), + DeleteStatement.DEFAULT_ALIAS, + true, + sessionFactory ); - - final Expression deletingTableColumnRefsExpression; - if ( deletingTableColumnRefs.size() == 1 ) { - deletingTableColumnRefsExpression = deletingTableColumnRefs.get( 0 ); + final Predicate tableDeletePredicate; + if ( matchingIdSubQuerySpec == null ) { + tableDeletePredicate = null; } else { - deletingTableColumnRefsExpression = new SqlTuple( deletingTableColumnRefs, entityDescriptor.getIdentifierMapping() ); + /* + * delete from sub_table + * where sub_id in ( + * select root_id from root_table + * where {predicate} + * ) + */ + + /* + * Create the `sub_id` reference as the LHS of the in-subquery predicate + */ + final List deletingTableColumnRefs = new ArrayList<>(); + tableKeyColumnVisitationSupplier.get().accept( + (columnIndex, selection) -> { + assert deleteTableReference.getTableReference( selection.getContainingTableExpression() ) != null; + + final Expression expression = sqlExpressionResolver.resolveSqlExpression( + SqlExpressionResolver.createColumnReferenceKey( deleteTableReference, selection.getSelectionExpression() ), + sqlAstProcessingState -> new ColumnReference( + deleteTableReference, + selection, + sessionFactory + ) + ); + + deletingTableColumnRefs.add( (ColumnReference) expression ); + } + ); + + final Expression deletingTableColumnRefsExpression; + if ( deletingTableColumnRefs.size() == 1 ) { + deletingTableColumnRefsExpression = deletingTableColumnRefs.get( 0 ); + } + else { + deletingTableColumnRefsExpression = new SqlTuple( deletingTableColumnRefs, entityDescriptor.getIdentifierMapping() ); + } + + tableDeletePredicate = new InSubQueryPredicate( + deletingTableColumnRefsExpression, + matchingIdSubQuerySpec, + false + ); } - final InSubQueryPredicate idMatchPredicate = new InSubQueryPredicate( - deletingTableColumnRefsExpression, - matchingIdSubQuerySpec, - false - ); - - final DeleteStatement sqlAstDelete = new DeleteStatement( targetTableReference, idMatchPredicate ); + final DeleteStatement sqlAstDelete = new DeleteStatement( deleteTableReference, tableDeletePredicate ); final int rows = executeSqlDelete( sqlAstDelete, jdbcParameterBindings, @@ -510,6 +586,12 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( entityDescriptor ); + final TableReference targetTable = new TableReference( + tableExpression, + DeleteStatement.DEFAULT_ALIAS, + true, + factory + ); tableKeyColumnVisitationSupplier.get().accept( (columnIndex, selection) -> { @@ -520,7 +602,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle keyColumnCollector.apply( new ColumnReference( - (String) null, + targetTable, selection, factory ) @@ -535,10 +617,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle ); executeSqlDelete( - new DeleteStatement( - new TableReference( tableExpression, null, true, factory ), - predicate - ), + new DeleteStatement( targetTable, predicate ), JdbcParameterBindings.NO_BINDINGS, executionContext ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedUpdateHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedUpdateHandler.java index 4cb2e5775d..5cf80ad32e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedUpdateHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedUpdateHandler.java @@ -126,6 +126,7 @@ public class TableBasedUpdateHandler final MultiTableSqmMutationConverter converterDelegate = new MultiTableSqmMutationConverter( entityDescriptor, + getSqmDeleteOrUpdateStatement(), getSqmDeleteOrUpdateStatement().getTarget(), domainParameterXref, executionContext.getQueryOptions(), @@ -203,6 +204,7 @@ public class TableBasedUpdateHandler if ( filterPredicate != null ) { predicate = SqlAstTreeHelper.combinePredicates( predicate, filterPredicate ); } + converterDelegate.pruneTableGroupJoins(); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // cross-reference the TableReference by alias. The TableGroup already diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TempIdTableExporter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TempIdTableExporter.java index 3c53e871af..05045ec2dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TempIdTableExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TempIdTableExporter.java @@ -58,7 +58,7 @@ public class TempIdTableExporter implements IdTableExporter { buffer.append( column.getColumnName() ).append( ' ' ); final int sqlTypeCode = column.getJdbcMapping().getJdbcTypeDescriptor().getDefaultSqlTypeCode(); - final String databaseTypeName = databaseTypeNameResolver.apply( sqlTypeCode ); + final String databaseTypeName = column.getSqlTypeDefinition(); buffer.append( " " ).append( databaseTypeName ).append( " " ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineDeleteHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineDeleteHandler.java index 5c28f74fcf..b4f2462f21 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineDeleteHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineDeleteHandler.java @@ -162,7 +162,7 @@ public class InlineDeleteHandler implements DeleteHandler { DomainQueryExecutionContext executionContext) { final TableReference targetTableReference = new TableReference( targetTableExpression, - null, + DeleteStatement.DEFAULT_ALIAS, false, sessionFactory ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/SqmCreationHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/SqmCreationHelper.java index acae6de850..5453ffceab 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/SqmCreationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/SqmCreationHelper.java @@ -15,18 +15,35 @@ import org.hibernate.query.sqm.tree.domain.SqmPath; * @author Steve Ebersole */ public class SqmCreationHelper { + + /** + * This is a special alias that we use for implicit joins within the FROM clause. + * Passing this alias will cause that we don't generate a unique alias for a path, + * but instead use a null alias. + * + * The effect of this is, that we use the same table group for a query like + * `... exists ( from alias.intermediate.attribute where alias.intermediate.otherAttribute is not null )` + * for the path in the FROM clause and the one in the WHERE clause. + */ + public static final String IMPLICIT_ALIAS = "{implicit}"; + public static NavigablePath buildRootNavigablePath(String base, String alias) { - // Make sure we always create a unique alias, otherwise we might use a wrong table group for the same join - return alias == null - ? new NavigablePath( base, Long.toString( System.nanoTime() ) ) - : new NavigablePath( base, alias ); + return new NavigablePath( base, determineAlias( alias ) ); } public static NavigablePath buildSubNavigablePath(NavigablePath lhs, String base, String alias) { + return lhs.append( base, determineAlias( alias ) ); + } + + private static String determineAlias(String alias) { // Make sure we always create a unique alias, otherwise we might use a wrong table group for the same join - return alias == null - ? lhs.append( base, Long.toString( System.nanoTime() ) ) - : lhs.append( base, alias ); + if ( alias == null ) { + return Long.toString( System.nanoTime() ); + } + else if ( alias == IMPLICIT_ALIAS ) { + return null; + } + return alias; } public static NavigablePath buildSubNavigablePath(SqmPath lhs, String subNavigable, String alias) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index 9acfaeaf39..f7e9b57214 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -82,6 +82,7 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.PluralPersistentAttribute; import org.hibernate.metamodel.model.domain.internal.CompositeSqmPathSource; import org.hibernate.query.criteria.JpaPath; +import org.hibernate.query.internal.QueryHelper; import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression; import org.hibernate.query.sqm.produce.function.internal.PatternRenderer; import org.hibernate.query.sqm.tree.SqmJoinType; @@ -368,12 +369,13 @@ public abstract class BaseSqmToSqlAstConverter extends Base private final EntityGraphTraversalState entityGraphTraversalState; private int fetchDepth; + private String currentBagRole; private boolean resolvingCircularFetch; private ForeignKeyDescriptor.Nature currentlyResolvingForeignKeySide; private SqmQueryPart currentSqmQueryPart; private Map collectionFilterPredicates; - private OrderByFragmentConsumer orderByFragmentConsumer; + private List> orderByFragments; private final SqlAliasBaseManager sqlAliasBaseManager = new SqlAliasBaseManager(); private final Stack processingStateStack = new StandardStack<>(); @@ -1134,6 +1136,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base for ( SqmDynamicInstantiationArgument sqmArgument : sqmDynamicInstantiation.getArguments() ) { final SqmSelectableNode selectableNode = sqmArgument.getSelectableNode(); + if ( selectableNode instanceof SqmPath ) { + prepareForSelection( (SqmPath) selectableNode ); + } final DomainResultProducer argumentResultProducer = (DomainResultProducer) selectableNode.accept( this ); dynamicInstantiation.addArgument( sqmArgument.getAlias(), argumentResultProducer, this ); } @@ -1423,10 +1428,6 @@ public abstract class BaseSqmToSqlAstConverter extends Base pushProcessingState( processingState ); try { - if ( topLevel ) { - orderByFragmentConsumer = new StandardOrderByFragmentConsumer(); - } - // we want to visit the from-clause first visitFromClause( sqmQuerySpec.getFromClause() ); @@ -1451,12 +1452,16 @@ public abstract class BaseSqmToSqlAstConverter extends Base visitOrderByOffsetAndFetch( sqmQuerySpec, sqlQuerySpec ); if ( topLevel && statement instanceof SqmSelectStatement ) { - orderByFragmentConsumer.visitFragments( - (orderByFragment, tableGroup) -> { - orderByFragment.apply( sqlQuerySpec, tableGroup, this ); - } - ); - orderByFragmentConsumer = null; + if ( orderByFragments != null ) { + orderByFragments.forEach( + entry -> entry.getKey().apply( + sqlQuerySpec, + entry.getValue(), + this + ) + ); + orderByFragments = null; + } applyCollectionFilterPredicates( sqlQuerySpec ); } @@ -1500,33 +1505,6 @@ public abstract class BaseSqmToSqlAstConverter extends Base return getFromClauseAccess().getTableGroup( navigablePath ); } - private interface OrderByFragmentConsumer { - void accept(OrderByFragment orderByFragment, TableGroup tableGroup); - - void visitFragments(BiConsumer consumer); - } - - private static class StandardOrderByFragmentConsumer implements OrderByFragmentConsumer { - private Map fragments; - - @Override - public void accept(OrderByFragment orderByFragment, TableGroup tableGroup) { - if ( fragments == null ) { - fragments = new LinkedHashMap<>(); - } - fragments.put( orderByFragment, tableGroup ); - } - - @Override - public void visitFragments(BiConsumer consumer) { - if ( fragments == null || fragments.isEmpty() ) { - return; - } - - fragments.forEach( consumer ); - } - } - protected void applyCollectionFilterPredicates(QuerySpec sqlQuerySpec) { final List roots = sqlQuerySpec.getFromClause().getRoots(); if ( roots != null && roots.size() == 1 ) { @@ -2410,8 +2388,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base implicitJoinChecker = BaseSqmToSqlAstConverter::verifyManipulationImplicitJoin; } final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent(); - final boolean useInnerJoin = currentClauseStack.getCurrent() == Clause.SELECT; - prepareReusablePath( fromClauseIndex, sqmPath, useInnerJoin, implicitJoinChecker ); + prepareReusablePath( fromClauseIndex, sqmPath, false, implicitJoinChecker ); return supplier.get(); } @@ -2437,6 +2414,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base ); if ( parentPath instanceof SqmTreatedPath ) { fromClauseIndex.register( (SqmPath) parentPath, parentTableGroup ); + return parentTableGroup; } final TableGroup newTableGroup = createTableGroup( parentTableGroup, (SqmPath) parentPath, useInnerJoin ); if ( newTableGroup != null ) { @@ -2974,6 +2952,14 @@ public abstract class BaseSqmToSqlAstConverter extends Base this, creationContext ); + final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate( + getLoadQueryInfluencers(), + (Joinable) pluralAttributeMapping.getCollectionDescriptor(), + tableGroup + ); + if ( filterPredicate != null ) { + subQuerySpec.applyPredicate( filterPredicate ); + } getFromClauseAccess().registerTableGroup( pluralPath.getNavigablePath(), tableGroup ); registerPluralTableGroupParts( tableGroup ); @@ -3109,6 +3095,14 @@ public abstract class BaseSqmToSqlAstConverter extends Base this, creationContext ); + final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate( + getLoadQueryInfluencers(), + (Joinable) pluralAttributeMapping.getCollectionDescriptor(), + tableGroup + ); + if ( filterPredicate != null ) { + subQuerySpec.applyPredicate( filterPredicate ); + } getFromClauseAccess().registerTableGroup( pluralPartPath.getNavigablePath(), tableGroup ); registerPluralTableGroupParts( tableGroup ); @@ -3687,6 +3681,10 @@ public abstract class BaseSqmToSqlAstConverter extends Base log.debugf( "Determining mapping-model type for generalized SqmExpression : %s", sqmExpression ); final SqmExpressable nodeType = sqmExpression.getNodeType(); + if ( nodeType == null ) { + // We can't determine the type of the expression + return null; + } final MappingModelExpressable valueMapping = domainModel.resolveMappingExpressable( nodeType, this::findTableGroupByPath @@ -4040,20 +4038,19 @@ public abstract class BaseSqmToSqlAstConverter extends Base } private BasicValuedMapping getExpressionType(SqmBinaryArithmetic expression) { - final SqmExpressable leftHandOperandType = expression.getLeftHandOperand().getNodeType(); - if ( leftHandOperandType instanceof BasicValuedMapping ) { - return (BasicValuedMapping) leftHandOperandType; + final SqmExpressable sqmExpressable = QueryHelper.highestPrecedenceType( + expression.getLeftHandOperand().getNodeType(), + expression.getRightHandOperand().getNodeType() + ); + if ( sqmExpressable instanceof BasicValuedMapping ) { + return (BasicValuedMapping) sqmExpressable; } - else { - final SqmExpressable rightHandOperandType = expression.getRightHandOperand().getNodeType(); - if ( rightHandOperandType instanceof BasicValuedMapping ) { - return (BasicValuedMapping) rightHandOperandType; - } - + else if ( sqmExpressable != null ) { return getTypeConfiguration().getBasicTypeForJavaType( - leftHandOperandType.getExpressableJavaTypeDescriptor().getJavaTypeClass() + sqmExpressable.getExpressableJavaTypeDescriptor().getJavaTypeClass() ); } + return JavaObjectType.INSTANCE; } private Expression toSqlExpression(Object value) { @@ -4664,33 +4661,6 @@ public abstract class BaseSqmToSqlAstConverter extends Base ); } -// @Override -// public Object visitPluralAttributeElementBinding(PluralAttributeElementBinding binding) { -// final TableGroup resolvedTableGroup = fromClauseIndex.findResolvedTableGroup( binding.getFromElement() ); -// -// return getCurrentDomainReferenceExpressionBuilder().buildPluralAttributeElementReferenceExpression( -// binding, -// resolvedTableGroup, -// PersisterHelper.convert( binding.getNavigablePath() ) -// ); -// } -// -// @Override -// public ColumnReference visitExplicitColumnReference(SqmColumnReference sqmColumnReference) { -// final TableGroup tableGroup = fromClauseIndex.findTableGroup( -// sqmColumnReference.getSqmFromBase().getNavigablePath() -// ); -// -// final ColumnReference columnReference = tableGroup.locateColumnReferenceByName( sqmColumnReference.getColumnName() ); -// -// if ( columnReference == null ) { -// throw new HibernateException( "Could not resolve ColumnReference" ); -// } -// -// return columnReference; -// } - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Predicates @@ -4763,6 +4733,14 @@ public abstract class BaseSqmToSqlAstConverter extends Base this, creationContext ); + final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate( + getLoadQueryInfluencers(), + (Joinable) pluralAttributeMapping.getCollectionDescriptor(), + tableGroup + ); + if ( filterPredicate != null ) { + subQuerySpec.applyPredicate( filterPredicate ); + } getFromClauseAccess().registerTableGroup( pluralPath.getNavigablePath(), tableGroup ); registerPluralTableGroupParts( tableGroup ); @@ -4837,8 +4815,11 @@ public abstract class BaseSqmToSqlAstConverter extends Base finally { inferrableTypeAccessStack.pop(); } - - return new ComparisonPredicate( lhs, predicate.getSqmOperator(), rhs, getBooleanType() ); + ComparisonOperator sqmOperator = predicate.getSqmOperator(); + if ( predicate.isNegated() ) { + sqmOperator = sqmOperator.negated(); + } + return new ComparisonPredicate( lhs, sqmOperator, rhs, getBooleanType() ); } @Override @@ -4906,11 +4887,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base new SqlSelectionImpl( 1, 0, jdbcLiteral ) ); - final ExistsPredicate existsPredicate = new ExistsPredicate( subQuerySpec, getBooleanType() ); - if ( predicate.isNegated() ) { - return existsPredicate; - } - return new NegatedPredicate( existsPredicate ); + return new ExistsPredicate( subQuerySpec, !predicate.isNegated(), getBooleanType() ); } finally { popProcessingStateStack(); @@ -5081,19 +5058,22 @@ public abstract class BaseSqmToSqlAstConverter extends Base SqmParameter sqmParameter, QueryParameterImplementor domainParam, QueryParameterBinding domainParamBinding) { + final Iterator iterator = domainParamBinding.getBindValues().iterator(); final InListPredicate inListPredicate = new InListPredicate( (Expression) sqmPredicate.getTestExpression().accept( this ), sqmPredicate.isNegated(), getBooleanType() ); + if ( !iterator.hasNext() ) { + return inListPredicate; + } inferrableTypeAccessStack.push( () -> determineValueMapping( sqmPredicate.getTestExpression() ) ); try { - final Iterator iterator = domainParamBinding.getBindValues().iterator(); inListPredicate.addExpression( consumeSingleSqmParameter( sqmParameter ) ); iterator.next(); while ( iterator.hasNext() ) { @@ -5145,7 +5125,11 @@ public abstract class BaseSqmToSqlAstConverter extends Base @Override public Object visitExistsPredicate(SqmExistsPredicate predicate) { - return new ExistsPredicate( (QueryPart) predicate.getExpression().accept( this ), getBooleanType() ); + return new ExistsPredicate( + (QueryPart) predicate.getExpression().accept( this ), + predicate.isNegated(), + getBooleanType() + ); } @Override @@ -5174,165 +5158,192 @@ public abstract class BaseSqmToSqlAstConverter extends Base // .getOrMakeJavaDescriptor( namedClass ); } - @Override - public List visitFetches(FetchParent fetchParent) { - final List fetches = CollectionHelper.arrayList( fetchParent.getReferencedMappingType().getNumberOfFetchables() ); - final List bagRoles = new ArrayList<>(); + public void addFetch(List fetches, FetchParent fetchParent, Fetchable fetchable, Boolean isKeyFetchable) { + final NavigablePath resolvedNavigablePath = fetchParent.resolveNavigablePath( fetchable ); - final BiConsumer fetchableBiConsumer = (fetchable, isKeyFetchable) -> { - final NavigablePath resolvedNavigablePath = fetchParent.resolveNavigablePath( fetchable ); + final String alias; + FetchTiming fetchTiming = fetchable.getMappedFetchOptions().getTiming(); + boolean joined = false; - final String alias; - FetchTiming fetchTiming = fetchable.getMappedFetchOptions().getTiming(); - boolean joined = false; + EntityGraphTraversalState.TraversalResult traversalResult = null; + final FromClauseIndex fromClauseIndex = getFromClauseIndex(); + final SqmAttributeJoin fetchedJoin = fromClauseIndex.findFetchedJoinByPath( resolvedNavigablePath ); + boolean explicitFetch = false; - EntityGraphTraversalState.TraversalResult traversalResult = null; - final FromClauseIndex fromClauseIndex = getFromClauseIndex(); - final SqmAttributeJoin fetchedJoin = fromClauseIndex.findFetchedJoinByPath( resolvedNavigablePath ); - boolean explicitFetch = false; + final NavigablePath fetchablePath; + if ( fetchedJoin != null ) { + fetchablePath = fetchedJoin.getNavigablePath(); + // there was an explicit fetch in the SQM + // there should be a TableGroupJoin registered for this `fetchablePath` already + assert fromClauseIndex.getTableGroup( fetchedJoin.getNavigablePath() ) != null; - final NavigablePath fetchablePath; - if ( fetchedJoin != null ) { - fetchablePath = fetchedJoin.getNavigablePath(); - // there was an explicit fetch in the SQM - // there should be a TableGroupJoin registered for this `fetchablePath` already - assert fromClauseIndex.getTableGroup( fetchedJoin.getNavigablePath() ) != null; - - if ( fetchedJoin.isFetched() ) { - fetchTiming = FetchTiming.IMMEDIATE; - } - joined = true; - alias = fetchedJoin.getExplicitAlias(); - explicitFetch = true; + if ( fetchedJoin.isFetched() ) { + fetchTiming = FetchTiming.IMMEDIATE; } - else { - fetchablePath = resolvedNavigablePath; - // there was not an explicit fetch in the SQM - alias = null; + joined = true; + alias = fetchedJoin.getExplicitAlias(); + explicitFetch = true; + } + else { + fetchablePath = resolvedNavigablePath; + // there was not an explicit fetch in the SQM + alias = null; - if ( !( fetchable instanceof CollectionPart ) ) { - if ( entityGraphTraversalState != null ) { - traversalResult = entityGraphTraversalState.traverse( fetchParent, fetchable, isKeyFetchable ); - fetchTiming = traversalResult.getFetchTiming(); - joined = traversalResult.isJoined(); - explicitFetch = true; - } - else if ( getLoadQueryInfluencers().hasEnabledFetchProfiles() ) { - // There is no point in checking the fetch profile if it can't affect this fetchable - if ( fetchTiming != FetchTiming.IMMEDIATE || fetchable.incrementFetchDepth() ) { - final String fetchableRole = fetchable.getNavigableRole().getFullPath(); + if ( !( fetchable instanceof CollectionPart ) ) { + if ( entityGraphTraversalState != null ) { + traversalResult = entityGraphTraversalState.traverse( + fetchParent, + fetchable, + isKeyFetchable + ); + fetchTiming = traversalResult.getFetchTiming(); + joined = traversalResult.isJoined(); + explicitFetch = true; + } + else if ( getLoadQueryInfluencers().hasEnabledFetchProfiles() ) { + // There is no point in checking the fetch profile if it can't affect this fetchable + if ( fetchTiming != FetchTiming.IMMEDIATE || fetchable.incrementFetchDepth() ) { + final String fetchableRole = fetchable.getNavigableRole().getFullPath(); - for ( String enabledFetchProfileName : getLoadQueryInfluencers().getEnabledFetchProfileNames() ) { - final FetchProfile enabledFetchProfile = getCreationContext().getSessionFactory() - .getFetchProfile( enabledFetchProfileName ); - final org.hibernate.engine.profile.Fetch profileFetch = enabledFetchProfile.getFetchByRole( - fetchableRole ); + for ( String enabledFetchProfileName : getLoadQueryInfluencers() + .getEnabledFetchProfileNames() ) { + final FetchProfile enabledFetchProfile = getCreationContext() + .getSessionFactory() + .getFetchProfile( enabledFetchProfileName ); + final org.hibernate.engine.profile.Fetch profileFetch = enabledFetchProfile.getFetchByRole( + fetchableRole ); - if ( profileFetch != null ) { - fetchTiming = FetchTiming.IMMEDIATE; - joined = joined || profileFetch.getStyle() == org.hibernate.engine.profile.Fetch.Style.JOIN; - explicitFetch = true; + if ( profileFetch != null ) { + fetchTiming = FetchTiming.IMMEDIATE; + joined = joined || profileFetch.getStyle() == org.hibernate.engine.profile.Fetch.Style.JOIN; + explicitFetch = true; + + if ( currentBagRole != null && fetchable instanceof PluralAttributeMapping ) { + final CollectionClassification collectionClassification = ( (PluralAttributeMapping) fetchable ).getMappedType() + .getCollectionSemantics() + .getCollectionClassification(); + if ( collectionClassification == CollectionClassification.BAG ) { + // To avoid a MultipleBagFetchException due to fetch profiles in a circular model, + // we skip join fetching in case we encounter an existing bag role + joined = false; + } } } } } } + } - final TableGroup existingJoinedGroup = fromClauseIndex.findTableGroup( fetchablePath ); - if ( existingJoinedGroup != null ) { - // we can use this to trigger the fetch from the joined group. + final TableGroup existingJoinedGroup = fromClauseIndex.findTableGroup( fetchablePath ); + if ( existingJoinedGroup != null ) { + // we can use this to trigger the fetch from the joined group. - // todo (6.0) : do we want to do this though? - // On the positive side it would allow EntityGraph to use the existing TableGroup. But that ties in - // to the discussion above regarding how to handle eager and EntityGraph (JOIN versus SELECT). - // Can be problematic if the existing one is restricted - //fetchTiming = FetchTiming.IMMEDIATE; - } + // todo (6.0) : do we want to do this though? + // On the positive side it would allow EntityGraph to use the existing TableGroup. But that ties in + // to the discussion above regarding how to handle eager and EntityGraph (JOIN versus SELECT). + // Can be problematic if the existing one is restricted + //fetchTiming = FetchTiming.IMMEDIATE; + } - // lastly, account for any app-defined max-fetch-depth - final Integer maxDepth = getCreationContext().getMaximumFetchDepth(); - if ( maxDepth != null ) { - if ( fetchDepth >= maxDepth ) { - joined = false; - } - } - - if ( joined && fetchable instanceof TableGroupJoinProducer ) { - TableGroupJoinProducer tableGroupJoinProducer = (TableGroupJoinProducer) fetchable; - fromClauseIndex.resolveTableGroup( - fetchablePath, - np -> { - // generate the join - final TableGroup lhs = fromClauseIndex.getTableGroup( fetchParent.getNavigablePath() ); - final TableGroupJoin tableGroupJoin = ( (TableGroupJoinProducer) fetchable ).createTableGroupJoin( - fetchablePath, - lhs, - alias, - tableGroupJoinProducer.getDefaultSqlAstJoinType( lhs ), - true, - false, - this - ); - lhs.addTableGroupJoin( tableGroupJoin ); - return tableGroupJoin.getJoinedGroup(); - } - ); + // lastly, account for any app-defined max-fetch-depth + final Integer maxDepth = getCreationContext().getMaximumFetchDepth(); + if ( maxDepth != null ) { + if ( fetchDepth >= maxDepth ) { + joined = false; } } - final boolean incrementFetchDepth = fetchable.incrementFetchDepth(); - try { - if ( incrementFetchDepth ) { - fetchDepth++; - } - // There is no need to check for circular fetches if this is an explicit fetch - if ( !explicitFetch && !isResolvingCircularFetch() ) { - final Fetch biDirectionalFetch = fetchable.resolveCircularFetch( - fetchablePath, - fetchParent, - fetchTiming, - this - ); - - if ( biDirectionalFetch != null ) { - fetches.add( biDirectionalFetch ); - return; - } - } - final Fetch fetch = buildFetch( fetchablePath, fetchParent, fetchable, fetchTiming, joined, alias ); - - if ( fetch != null ) { - if ( fetch.getTiming() == FetchTiming.IMMEDIATE && fetchable instanceof PluralAttributeMapping ) { - final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable; - final CollectionClassification collectionClassification = pluralAttributeMapping.getMappedType() - .getCollectionSemantics() - .getCollectionClassification(); - if ( collectionClassification == CollectionClassification.BAG ) { - bagRoles.add( fetchable.getNavigableRole().getNavigableName() ); + if ( joined && fetchable instanceof TableGroupJoinProducer ) { + TableGroupJoinProducer tableGroupJoinProducer = (TableGroupJoinProducer) fetchable; + fromClauseIndex.resolveTableGroup( + fetchablePath, + np -> { + // generate the join + final TableGroup lhs = fromClauseIndex.getTableGroup( fetchParent.getNavigablePath() ); + final TableGroupJoin tableGroupJoin = ( (TableGroupJoinProducer) fetchable ).createTableGroupJoin( + fetchablePath, + lhs, + alias, + tableGroupJoinProducer.getDefaultSqlAstJoinType( lhs ), + true, + false, + BaseSqmToSqlAstConverter.this + ); + lhs.addTableGroupJoin( tableGroupJoin ); + return tableGroupJoin.getJoinedGroup(); } - } + ); + } + } - fetches.add( fetch ); + final boolean incrementFetchDepth = fetchable.incrementFetchDepth(); + try { + if ( incrementFetchDepth ) { + fetchDepth++; + } + // There is no need to check for circular fetches if this is an explicit fetch + if ( !explicitFetch && !isResolvingCircularFetch() ) { + final Fetch biDirectionalFetch = fetchable.resolveCircularFetch( + fetchablePath, + fetchParent, + fetchTiming, + this + ); + + if ( biDirectionalFetch != null ) { + fetches.add( biDirectionalFetch ); + return; } } - finally { - if ( incrementFetchDepth ) { - fetchDepth--; - } - if ( entityGraphTraversalState != null && traversalResult != null ) { - entityGraphTraversalState.backtrack( traversalResult.getPreviousContext() ); + final Fetch fetch = buildFetch( + fetchablePath, + fetchParent, + fetchable, + fetchTiming, + joined, + alias + ); + + if ( fetch != null ) { + if ( fetch.getTiming() == FetchTiming.IMMEDIATE && fetchable instanceof PluralAttributeMapping ) { + final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable; + final CollectionClassification collectionClassification = pluralAttributeMapping.getMappedType() + .getCollectionSemantics() + .getCollectionClassification(); + if ( collectionClassification == CollectionClassification.BAG ) { + if ( currentBagRole != null ) { + throw new MultipleBagFetchException( + Arrays.asList( + currentBagRole, + fetchable.getNavigableRole().getNavigableName() + ) + ); + } + currentBagRole = fetchable.getNavigableRole().getNavigableName(); + } } + fetches.add( fetch ); } - }; + } + finally { + if ( incrementFetchDepth ) { + fetchDepth--; + } + if ( entityGraphTraversalState != null && traversalResult != null ) { + entityGraphTraversalState.backtrack( traversalResult.getPreviousContext() ); + } + } + } + + @Override + public List visitFetches(FetchParent fetchParent) { + final List fetches = CollectionHelper.arrayList( fetchParent.getReferencedMappingType().getNumberOfFetchables() ); // todo (6.0) : determine how to best handle TREAT // fetchParent.getReferencedMappingContainer().visitKeyFetchables( fetchableBiConsumer, treatTargetType ); // fetchParent.getReferencedMappingContainer().visitFetchables( fetchableBiConsumer, treatTargetType ); - fetchParent.getReferencedMappingContainer().visitKeyFetchables( fetchable -> fetchableBiConsumer.accept( fetchable, true ), null ); - fetchParent.getReferencedMappingContainer().visitFetchables( fetchable -> fetchableBiConsumer.accept( fetchable, false ), null ); - if ( bagRoles.size() > 1 ) { - throw new MultipleBagFetchException( bagRoles ); - } + fetchParent.getReferencedMappingContainer().visitKeyFetchables( fetchable -> addFetch( fetches, fetchParent, fetchable, true ), null ); + fetchParent.getReferencedMappingContainer().visitFetchables( fetchable -> addFetch( fetches, fetchParent, fetchable, false ), null ); return fetches; } @@ -5400,16 +5411,9 @@ public abstract class BaseSqmToSqlAstConverter extends Base } } - if ( orderByFragmentConsumer != null ) { + if ( currentQuerySpec().isRoot() ) { assert tableGroup.getModelPart() == pluralAttributeMapping; - - if ( pluralAttributeMapping.getOrderByFragment() != null ) { - orderByFragmentConsumer.accept( pluralAttributeMapping.getOrderByFragment(), tableGroup ); - } - - if ( pluralAttributeMapping.getManyToManyOrderByFragment() != null ) { - orderByFragmentConsumer.accept( pluralAttributeMapping.getManyToManyOrderByFragment(), tableGroup ); - } + applyOrdering( tableGroup, pluralAttributeMapping ); } } @@ -5428,6 +5432,23 @@ public abstract class BaseSqmToSqlAstConverter extends Base } } + private void applyOrdering(TableGroup tableGroup, PluralAttributeMapping pluralAttributeMapping) { + if ( pluralAttributeMapping.getOrderByFragment() != null ) { + applyOrdering( tableGroup, pluralAttributeMapping.getOrderByFragment() ); + } + + if ( pluralAttributeMapping.getManyToManyOrderByFragment() != null ) { + applyOrdering( tableGroup, pluralAttributeMapping.getManyToManyOrderByFragment() ); + } + } + + private void applyOrdering(TableGroup tableGroup, OrderByFragment orderByFragment) { + if ( orderByFragments == null ) { + orderByFragments = new ArrayList<>(); + } + orderByFragments.add( new AbstractMap.SimpleEntry<>( orderByFragment, tableGroup ) ); + } + @Override public boolean isResolvingCircularFetch() { return resolvingCircularFetch; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java index 999fbcc8a2..73642a49dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java @@ -7,7 +7,9 @@ package org.hibernate.query.sqm.sql.internal; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; @@ -31,6 +33,7 @@ import org.hibernate.sql.ast.tree.expression.SqlTuple; import org.hibernate.sql.ast.tree.expression.SqlTupleContainer; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.update.Assignable; import org.hibernate.sql.results.graph.DomainResultCreationState; import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey; @@ -38,7 +41,8 @@ import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnRefere /** * @author Koen Aers */ -public class EntityValuedPathInterpretation extends AbstractSqmPathInterpretation implements SqlTupleContainer { +public class EntityValuedPathInterpretation extends AbstractSqmPathInterpretation implements SqlTupleContainer, + Assignable { public static EntityValuedPathInterpretation from( SqmEntityValuedSimplePath sqmPath, @@ -232,12 +236,34 @@ public class EntityValuedPathInterpretation extends AbstractSqmPathInterpreta sqlExpression.accept( sqlTreeWalker ); } + @Override + public List getColumnReferences() { + if ( sqlExpression instanceof SqlTuple ) { + //noinspection unchecked + return (List) ( (SqlTuple) sqlExpression ).getExpressions(); + } + return Collections.singletonList( (ColumnReference) sqlExpression ); + } + + @Override + public void visitColumnReferences(Consumer columnReferenceConsumer) { + if ( sqlExpression instanceof SqlTuple ) { + for ( Expression e : ( (SqlTuple) sqlExpression ).getExpressions() ) { + columnReferenceConsumer.accept( (ColumnReference) e ); + } + } + else { + columnReferenceConsumer.accept( (ColumnReference) sqlExpression ); + } + } + @Override public SqlTuple getSqlTuple() { return sqlExpression instanceof SqlTuple ? (SqlTuple) sqlExpression : null; } + @Override public void applySqlSelections(DomainResultCreationState creationState) { creationState.getSqlAstCreationState().getSqlExpressionResolver().resolveSqlSelection( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmAttributeJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmAttributeJoin.java index aeb95a23b4..89bd969110 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmAttributeJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmAttributeJoin.java @@ -53,7 +53,7 @@ public abstract class AbstractSqmAttributeJoin lhs, SqmCreationHelper.buildSubNavigablePath( lhs, joinedNavigable.getName(), alias ), joinedNavigable, - alias, + alias == SqmCreationHelper.IMPLICIT_ALIAS ? null : alias, joinType, fetched, nodeBuilder diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java index 79849010e0..e4567583f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java @@ -24,6 +24,7 @@ import jakarta.persistence.metamodel.PluralAttribute; import jakarta.persistence.metamodel.SetAttribute; import jakarta.persistence.metamodel.SingularAttribute; +import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.model.domain.BagPersistentAttribute; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.ListPersistentAttribute; @@ -44,6 +45,7 @@ import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.spi.SqmCreationHelper; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; +import org.hibernate.query.sqm.tree.from.SqmCrossJoin; import org.hibernate.query.sqm.tree.from.SqmEntityJoin; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmJoin; @@ -129,18 +131,84 @@ public abstract class AbstractSqmFrom extends AbstractSqmPath implements String name, boolean isTerminal, SqmCreationState creationState) { - final NavigablePath subNavPath = getNavigablePath().append( name ); - return creationState.getProcessingStateStack().getCurrent().getPathRegistry().resolvePath( - subNavPath, - snp -> { - final SqmPathSource subSource = getReferencedPathSource().findSubPathSource( name ); - if ( subSource == null ) { - throw UnknownPathException.unknownSubPath( this, name ); + // Try to resolve an existing attribute join without ON clause + SqmPath resolvedPath = null; + ModelPartContainer modelPartContainer = null; + for ( SqmJoin sqmJoin : getSqmJoins() ) { + if ( sqmJoin instanceof SqmAttributeJoin + && name.equals( sqmJoin.getReferencedPathSource().getPathName() ) ) { + final SqmAttributeJoin attributeJoin = (SqmAttributeJoin) sqmJoin; + if ( attributeJoin.getOn() == null ) { + // todo (6.0): to match the expectation of the JPA spec I think we also have to check + // that the join type is INNER or the default join type for the attribute, + // but as far as I understand, in 5.x we expect to ignore this behavior +// if ( attributeJoin.getSqmJoinType() != SqmJoinType.INNER ) { +// if ( attributeJoin.getAttribute().isCollection() ) { +// continue; +// } +// if ( modelPartContainer == null ) { +// modelPartContainer = findModelPartContainer( attributeJoin, creationState ); +// } +// final TableGroupJoinProducer joinProducer = (TableGroupJoinProducer) modelPartContainer.findSubPart( +// name, +// null +// ); +// if ( attributeJoin.getSqmJoinType().getCorrespondingSqlJoinType() != joinProducer.getDefaultSqlAstJoinType( null ) ) { +// continue; +// } +// } + resolvedPath = sqmJoin; + if ( attributeJoin.isFetched() ) { + break; } - - return subSource.createSqmPath( this, getReferencedPathSource().getIntermediatePathSource( subSource ) ); } - ); + } + } + if ( resolvedPath != null ) { + return resolvedPath; + } + final SqmPath sqmPath = get( name ); + creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); + return sqmPath; + } + + private ModelPartContainer findModelPartContainer(SqmAttributeJoin attributeJoin, SqmCreationState creationState) { + final SqmFrom lhs = attributeJoin.getLhs(); + if ( lhs instanceof SqmAttributeJoin ) { + final SqmAttributeJoin lhsAttributeJoin = (SqmAttributeJoin) lhs; + if ( lhsAttributeJoin.getReferencedPathSource() instanceof EntityDomainType ) { + final String entityName = ( (EntityDomainType) lhsAttributeJoin.getReferencedPathSource() ).getHibernateEntityName(); + return (ModelPartContainer) creationState.getCreationContext().getQueryEngine() + .getTypeConfiguration() + .getSessionFactory() + .getMetamodel() + .entityPersister( entityName ) + .findSubPart( attributeJoin.getAttribute().getName(), null ); + } + else { + return (ModelPartContainer) findModelPartContainer( lhsAttributeJoin, creationState ) + .findSubPart( attributeJoin.getAttribute().getName(), null ); + } + } + else { + final String entityName; + if ( lhs instanceof SqmRoot ) { + entityName = ( (SqmRoot) lhs ).getEntityName(); + } + else if ( lhs instanceof SqmEntityJoin ) { + entityName = ( (SqmEntityJoin) lhs ).getEntityName(); + } + else { + assert lhs instanceof SqmCrossJoin; + entityName = ( (SqmCrossJoin) lhs ).getEntityName(); + } + return (ModelPartContainer) creationState.getCreationContext().getQueryEngine() + .getTypeConfiguration() + .getSessionFactory() + .getMetamodel() + .entityPersister( entityName ) + .findSubPart( attributeJoin.getAttribute().getName(), null ); + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/NonAggregatedCompositeSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/NonAggregatedCompositeSimplePath.java index d344316365..e32fb6568b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/NonAggregatedCompositeSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/NonAggregatedCompositeSimplePath.java @@ -34,16 +34,13 @@ public class NonAggregatedCompositeSimplePath extends SqmEntityValuedSimplePa } @Override - public SemanticPathPart resolvePathPart( + public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - final SqmPathSource subPathSource = getReferencedPathSource().findSubPathSource( name ); - if ( subPathSource == null ) { - throw UnknownPathException.unknownSubPath( this, name ); - } - - return subPathSource.createSqmPath( this, getReferencedPathSource().getIntermediatePathSource( subPathSource ) ); + final SqmPath sqmPath = get( name ); + creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); + return sqmPath; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmAnyValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmAnyValuedSimplePath.java index 3c1375e08c..cc24a98ea3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmAnyValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmAnyValuedSimplePath.java @@ -55,17 +55,13 @@ public class SqmAnyValuedSimplePath extends AbstractSqmSimplePath { } @Override - public SemanticPathPart resolvePathPart( + public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - final SqmPathSource subPathSource = getReferencedPathSource().findSubPathSource( name ); - if ( subPathSource == null ) { - throw UnknownPathException.unknownSubPath( this, name ); - } - - return subPathSource.createSqmPath( this, getReferencedPathSource().getIntermediatePathSource( subPathSource ) ); - + final SqmPath sqmPath = get( name ); + creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); + return sqmPath; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java index f917dc8c92..14045285e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java @@ -46,7 +46,7 @@ public class SqmBasicValuedSimplePath // SemanticPathPart @Override - public SemanticPathPart resolvePathPart( + public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java index 6d30fcc1c9..6e362bb98b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.sqm.tree.domain; +import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -36,6 +37,22 @@ public class SqmCorrelatedRoot extends SqmRoot implements SqmPathWrapper alias(String name) { + setAlias( name ); + return this; + } + @Override public boolean isCorrelated() { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java index f07e98f8b4..f3036784c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java @@ -46,16 +46,13 @@ public class SqmEmbeddedValuedSimplePath extends AbstractSqmSimplePath imp } @Override - public SemanticPathPart resolvePathPart( + public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - final SqmPathSource subPathSource = getReferencedPathSource().findSubPathSource( name ); - if ( subPathSource == null ) { - throw UnknownPathException.unknownSubPath( this, name ); - } - - return subPathSource.createSqmPath( this, getReferencedPathSource().getIntermediatePathSource( subPathSource ) ); + final SqmPath sqmPath = get( name ); + creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); + return sqmPath; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java index 45c1749926..29b3ce4e3d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java @@ -28,19 +28,13 @@ public class SqmEntityValuedSimplePath extends AbstractSqmSimplePath { } @Override - public SemanticPathPart resolvePathPart( + public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - final SqmPathSource referencedPathSource = getReferencedPathSource(); - final SqmPathSource subPathSource = referencedPathSource.findSubPathSource( name ); - - assert getLhs() == null || creationState.getProcessingStateStack() - .getCurrent() - .getPathRegistry() - .findPath( getLhs().getNavigablePath() ) != null; - - return subPathSource.createSqmPath( this, getReferencedPathSource().getIntermediatePathSource( subPathSource ) ); + final SqmPath sqmPath = get( name ); + creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); + return sqmPath; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexedCollectionAccessPath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexedCollectionAccessPath.java index a31a5822f8..d53186bbd0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexedCollectionAccessPath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexedCollectionAccessPath.java @@ -29,7 +29,7 @@ public class SqmIndexedCollectionAccessPath extends AbstractSqmPath implem //noinspection unchecked super( navigablePath, - (PluralPersistentAttribute) pluralDomainPath.getReferencedPathSource(), + ( (PluralPersistentAttribute) pluralDomainPath.getReferencedPathSource() ).getElementPathSource(), pluralDomainPath, pluralDomainPath.nodeBuilder() ); @@ -41,18 +41,18 @@ public class SqmIndexedCollectionAccessPath extends AbstractSqmPath implem return selectorExpression; } - @Override - public PluralPersistentAttribute getReferencedPathSource() { - return (PluralPersistentAttribute) super.getReferencedPathSource(); + public PluralPersistentAttribute getPluralAttribute() { + return (PluralPersistentAttribute) getLhs().getReferencedPathSource(); } @Override - public SemanticPathPart resolvePathPart( + public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - final SqmPathSource subPathSource = getReferencedPathSource().getElementPathSource().findSubPathSource( name ); - return subPathSource.createSqmPath( this, getReferencedPathSource().getElementPathSource() ); + final SqmPath sqmPath = get( name ); + creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); + return sqmPath; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMaxElementPath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMaxElementPath.java index cbb70f5508..44d3042216 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMaxElementPath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMaxElementPath.java @@ -6,13 +6,10 @@ */ package org.hibernate.query.sqm.tree.domain; -import org.hibernate.metamodel.model.domain.ManagedDomainType; import org.hibernate.metamodel.model.domain.PluralPersistentAttribute; -import org.hibernate.query.SemanticException; -import org.hibernate.query.hql.spi.SemanticPathPart; -import org.hibernate.query.sqm.SqmPathSource; -import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.hql.spi.SqmCreationState; +import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.SqmPathSource; /** * @author Steve Ebersole @@ -30,15 +27,13 @@ public class SqmMaxElementPath extends AbstractSqmSpecificPluralPartPath { } @Override - public SemanticPathPart resolvePathPart( + public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - if ( getPluralAttribute().getElementPathSource().getSqmPathType() instanceof ManagedDomainType ) { - return getPluralAttribute().getElementPathSource().createSqmPath( this, null ); - } - - throw new SemanticException( "Collection element cannot be de-referenced : " + getPluralDomainPath().getNavigablePath() ); + final SqmPath sqmPath = get( name ); + creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); + return sqmPath; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMaxIndexPath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMaxIndexPath.java index 5bb68697a8..8cd981c09d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMaxIndexPath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMaxIndexPath.java @@ -44,11 +44,13 @@ public class SqmMaxIndexPath extends AbstractSqmSpecificPluralPartPath { } @Override - public SemanticPathPart resolvePathPart( + public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - return indexPathSource.createSqmPath( this, null ); + final SqmPath sqmPath = get( name ); + creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); + return sqmPath; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMinElementPath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMinElementPath.java index bcc338d0d1..83c135953a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMinElementPath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMinElementPath.java @@ -30,15 +30,13 @@ public class SqmMinElementPath extends AbstractSqmSpecificPluralPartPath { } @Override - public SemanticPathPart resolvePathPart( + public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - if ( getPluralAttribute().getElementPathSource().getSqmPathType() instanceof ManagedDomainType ) { - return getPluralAttribute().getElementPathSource().createSqmPath( this, null ); - } - - throw new SemanticException( "Collection element cannot be de-referenced : " + getPluralDomainPath().getNavigablePath() ); + final SqmPath sqmPath = get( name ); + creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); + return sqmPath; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMinIndexPath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMinIndexPath.java index 1c284f0308..1d1862165b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMinIndexPath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMinIndexPath.java @@ -44,11 +44,13 @@ public class SqmMinIndexPath extends AbstractSqmSpecificPluralPartPath { } @Override - public SemanticPathPart resolvePathPart( + public SqmPath resolvePathPart( String name, boolean isTerminal, SqmCreationState creationState) { - return indexPathSource.createSqmPath( this, null ); + final SqmPath sqmPath = get( name ); + creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); + return sqmPath; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPath.java index fc15e94d81..0e0f7f564e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPath.java @@ -127,6 +127,11 @@ public interface SqmPath extends SqmExpression, SemanticPathPart, JpaPath< throw new ParsingException( "Could not find root" ); } + SqmPath resolvePathPart( + String name, + boolean isTerminal, + SqmCreationState creationState); + @Override default SqmPath resolveIndexedAccess( SqmExpression selector, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralValuedSimplePath.java index 1ae579cff0..0c52d11002 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralValuedSimplePath.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.sqm.tree.domain; +import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.ListPersistentAttribute; import org.hibernate.metamodel.model.domain.MapPersistentAttribute; @@ -69,11 +70,13 @@ public class SqmPluralValuedSimplePath extends AbstractSqmSimplePath { boolean isTerminal, SqmCreationState creationState) { // this is a reference to a collection outside of the from-clause... - final NavigablePath navigablePath = getNavigablePath().append( name ); - return creationState.getProcessingStateStack().getCurrent().getPathRegistry().resolvePath( - navigablePath, - np -> get( np.getUnaliasedLocalName() ) - ); + final CollectionPart.Nature nature = CollectionPart.Nature.fromNameExact( name ); + if ( nature == null ) { + throw new SemanticException( "illegal attempt to dereference collection [" + getNavigablePath() + "] with element property reference [" + name + "]" ); + } + final SqmPath sqmPath = get( name ); + creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); + return sqmPath; } @Override @@ -86,8 +89,12 @@ public class SqmPluralValuedSimplePath extends AbstractSqmSimplePath { final NavigablePath navigablePath = getNavigablePath().getParent().append( getNavigablePath().getUnaliasedLocalName(), alias - ); - SqmFrom path = pathRegistry.findFromByPath( navigablePath ); + ).append( CollectionPart.Nature.ELEMENT.getName() ); + final SqmFrom indexedPath = pathRegistry.findFromByPath( navigablePath ); + if ( indexedPath != null ) { + return indexedPath; + } + SqmFrom path = pathRegistry.findFromByPath( navigablePath.getParent() ); if ( path == null ) { final PluralPersistentAttribute referencedPathSource = getReferencedPathSource(); final SqmFrom parent = pathRegistry.resolveFrom( getLhs() ); @@ -124,11 +131,13 @@ public class SqmPluralValuedSimplePath extends AbstractSqmSimplePath { parent.addSqmJoin( join ); pathRegistry.register( path = join ); } - return new SqmIndexedCollectionAccessPath<>( - path.getNavigablePath(), + final SqmIndexedCollectionAccessPath result = new SqmIndexedCollectionAccessPath<>( + navigablePath, path, selector ); + pathRegistry.register( result ); + return result; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedRoot.java index 04789ebe7c..ae493dde56 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedRoot.java @@ -7,11 +7,9 @@ package org.hibernate.query.sqm.tree.domain; import org.hibernate.metamodel.model.domain.EntityDomainType; -import org.hibernate.query.NavigablePath; import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmPathSource; -import org.hibernate.query.sqm.UnknownPathException; import org.hibernate.query.sqm.tree.from.SqmJoin; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -85,25 +83,9 @@ public class SqmTreatedRoot extends SqmRoot implements SqmTre String name, boolean isTerminal, SqmCreationState creationState) { - final NavigablePath subNavPath = getNavigablePath().append( name ); - return creationState.getProcessingStateStack().getCurrent().getPathRegistry().resolvePath( - subNavPath, - snp -> { - final SqmPathSource subSource; - if ( creationState.getCreationOptions().useStrictJpaCompliance() ) { - subSource = getManagedType().findSubPathSource( name ); - } - else { - subSource = treatTarget.findSubPathSource( name ); - } - - if ( subSource == null ) { - throw UnknownPathException.unknownSubPath( this, name ); - } - - return subSource.createSqmPath( this, null ); - } - ); + final SqmPath sqmPath = get( name ); + creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath ); + return sqmPath; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFrom.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFrom.java index 76cc6069dd..8fa1bb021a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFrom.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFrom.java @@ -49,9 +49,6 @@ public interface SqmFrom extends SqmVisitableNode, SqmPath, JpaFrom getReferencedPathSource(); - @Override - SqmPath resolvePathPart(String name, boolean isTerminal, SqmCreationState creationState); - boolean hasJoins(); /** diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/jpa/ParameterCollector.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/jpa/ParameterCollector.java index 420528fe77..3d70fa2157 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/jpa/ParameterCollector.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/jpa/ParameterCollector.java @@ -235,7 +235,7 @@ public class ParameterCollector extends BaseSemanticQueryWalker { @Override public Object visitIndexedPluralAccessPath(SqmIndexedCollectionAccessPath path) { path.getLhs().accept( this ); - withTypeInference( path.getReferencedPathSource().getIndexPathSource(), () -> path.getSelectorExpression().accept( this ) ); + withTypeInference( path.getPluralAttribute().getIndexPathSource(), () -> path.getSelectorExpression().accept( this ) ); return path; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java index a2c93e8ff4..73a3dbd134 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java @@ -242,16 +242,6 @@ public class SqmDynamicInstantiation return list; } - @Override - public String getAlias() { - return null; - } - - @Override - public JpaSelection alias(String name) { - return null; - } - @Override public boolean isCompoundSelection() { return false; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryGroup.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryGroup.java index 779060b10b..4713a5ef6d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryGroup.java @@ -22,6 +22,7 @@ import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmJoin; +import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.type.descriptor.java.JavaType; /** @@ -111,8 +112,12 @@ public class SqmQueryGroup extends SqmQueryPart implements JpaQueryGroup firstQuerySpec = getFirstQuerySpec(); + // We only need to validate the first query spec regarding fetch owner, + // because the fetch structure must match in all query parts of the group which we validate next + firstQuerySpec.validateFetchOwners(); + validateQueryGroupFetchStructure( firstQuerySpec ); } private void validateQueryGroupFetchStructure(SqmQuerySpec firstQuerySpec) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryPart.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryPart.java index 40e2ada4aa..10df2c375f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryPart.java @@ -148,7 +148,7 @@ public abstract class SqmQueryPart implements SqmVisitableNode, JpaQueryPart< return this; } - public abstract void validateQueryGroupFetchStructure(); + public abstract void validateFetchStructureAndOwners(); public void appendHqlString(StringBuilder sb) { if ( orderByClause == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java index 9c86673c02..2ec783d77c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java @@ -14,7 +14,9 @@ import java.util.Set; import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.Predicate; +import org.hibernate.metamodel.model.domain.EmbeddableDomainType; import org.hibernate.query.FetchClauseType; +import org.hibernate.query.SemanticException; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaOrder; import org.hibernate.query.criteria.JpaPredicate; @@ -24,6 +26,8 @@ import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.SqmNode; +import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; +import org.hibernate.query.sqm.tree.domain.SqmTreatedRoot; import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; @@ -345,8 +349,86 @@ public class SqmQuerySpec extends SqmQueryPart } @Override - public void validateQueryGroupFetchStructure() { - // No-op + public void validateFetchStructureAndOwners() { + validateFetchOwners(); + } + + public void validateFetchOwners() { + if ( getFromClause() == null ) { + return; + } + final Set> selectedFromSet; + if ( selectClause == null || selectClause.getSelections().isEmpty() ) { + selectedFromSet = Collections.singleton( getFromClause().getRoots().get( 0 ) ); + } + else { + selectedFromSet = new HashSet<>( selectClause.getSelections().size() ); + for ( SqmSelection selection : selectClause.getSelections() ) { + collectSelectedFromSet( selectedFromSet, selection.getSelectableNode() ); + } + } + + for ( SqmRoot root : getFromClause().getRoots() ) { + validateFetchOwners( selectedFromSet, root ); + } + } + + private void collectSelectedFromSet(Set> selectedFromSet, SqmSelectableNode selectableNode) { + if ( selectableNode instanceof SqmJpaCompoundSelection ) { + final SqmJpaCompoundSelection compoundSelection = (SqmJpaCompoundSelection) selectableNode; + for ( SqmSelectableNode selectionItem : compoundSelection.getSelectionItems() ) { + collectSelectedFromSet( selectedFromSet, selectionItem ); + } + } + else if ( selectableNode instanceof SqmDynamicInstantiation ) { + final SqmDynamicInstantiation instantiation = (SqmDynamicInstantiation) selectableNode; + for ( SqmDynamicInstantiationArgument selectionItem : instantiation.getArguments() ) { + collectSelectedFromSet( selectedFromSet, selectionItem.getSelectableNode() ); + } + } + else if ( selectableNode instanceof SqmFrom ) { + collectSelectedFromSet( selectedFromSet, (SqmFrom) selectableNode ); + } + } + + private void collectSelectedFromSet(Set> selectedFromSet, SqmFrom sqmFrom) { + selectedFromSet.add( sqmFrom ); + for ( SqmJoin sqmJoin : sqmFrom.getSqmJoins() ) { + if ( sqmJoin.getReferencedPathSource().getSqmPathType() instanceof EmbeddableDomainType ) { + collectSelectedFromSet( selectedFromSet, sqmJoin ); + } + } + + for ( SqmFrom sqmTreat : sqmFrom.getSqmTreats() ) { + collectSelectedFromSet( selectedFromSet, sqmTreat ); + } + } + + private void validateFetchOwners(Set> selectedFromSet, SqmFrom owner) { + for ( SqmJoin sqmJoin : owner.getSqmJoins() ) { + if ( sqmJoin instanceof SqmAttributeJoin ) { + final SqmAttributeJoin attributeJoin = (SqmAttributeJoin) sqmJoin; + if ( attributeJoin.isFetched() ) { + assertFetchOwner( selectedFromSet, owner, sqmJoin ); + // Only need to check the first level + continue; + } + } + validateFetchOwners( selectedFromSet, sqmJoin ); + } + for ( SqmFrom sqmTreat : owner.getSqmTreats() ) { + validateFetchOwners( selectedFromSet, sqmTreat ); + } + } + + private void assertFetchOwner(Set> selectedFromSet, SqmFrom owner, SqmJoin sqmJoin) { + if ( !selectedFromSet.contains( owner ) ) { + throw new SemanticException( + "query specified join fetching, but the owner " + + "of the fetched association was not present in the select list " + + "[" + sqmJoin.asLoggableText() + "]" + ); + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java b/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java index c70678737d..5176858c5b 100644 --- a/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java @@ -219,6 +219,7 @@ public class OutputsImpl implements Outputs { null, this.context.getQueryOptions(), resultSetMapping.resolve( resultSetAccess, getSessionFactory() ), + null, executionContext ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index 6e822b2c28..c63b224540 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -28,6 +28,7 @@ import org.hibernate.QueryException; import org.hibernate.dialect.RowLockStrategy; import org.hibernate.dialect.SelectItemReferenceStrategy; import org.hibernate.internal.util.MathHelper; +import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.persister.entity.EntityPersister; @@ -184,7 +185,7 @@ public abstract class AbstractSqlAstTranslator implemen private final Dialect dialect; private final Statement statement; private final Set affectedTableNames = new HashSet<>(); - private String dmlTargetTableAlias; + private MutationStatement dmlStatement; private boolean needsSelectAliases; // We must reset the queryPartForRowNumbering fields to null if a query part is visited that does not // contribute to the row numbering i.e. if the query part is a sub-query in the where clause. @@ -343,13 +344,17 @@ public abstract class AbstractSqlAstTranslator implemen } protected String getDmlTargetTableAlias() { - return dmlTargetTableAlias; + return dmlStatement == null ? null : dmlStatement.getTargetTable().getIdentificationVariable(); } protected Statement getStatement() { return statement; } + public MutationStatement getCurrentDmlStatement() { + return dmlStatement; + } + protected SqlAstNodeRenderingMode getParameterRenderingMode() { return parameterRenderingMode; } @@ -777,42 +782,42 @@ public abstract class AbstractSqlAstTranslator implemen @Override public void visitSelectStatement(SelectStatement statement) { - String oldDmlTargetTableAlias = dmlTargetTableAlias; - dmlTargetTableAlias = null; + MutationStatement oldDmlStatement = dmlStatement; + dmlStatement = null; try { visitCteContainer( statement ); statement.getQueryPart().accept( this ); } finally { - dmlTargetTableAlias = oldDmlTargetTableAlias; + dmlStatement = oldDmlStatement; } } @Override public void visitDeleteStatement(DeleteStatement statement) { - String oldDmlTargetTableAlias = dmlTargetTableAlias; - dmlTargetTableAlias = null; + MutationStatement oldDmlStatement = dmlStatement; + dmlStatement = null; try { visitCteContainer( statement ); - dmlTargetTableAlias = statement.getTargetTable().getIdentificationVariable(); + dmlStatement = statement; visitDeleteStatementOnly( statement ); } finally { - dmlTargetTableAlias = oldDmlTargetTableAlias; + dmlStatement = oldDmlStatement; } } @Override public void visitUpdateStatement(UpdateStatement statement) { - String oldDmlTargetTableAlias = dmlTargetTableAlias; - dmlTargetTableAlias = null; + MutationStatement oldDmlTargetTableAlias = dmlStatement; + dmlStatement = null; try { visitCteContainer( statement ); - dmlTargetTableAlias = statement.getTargetTable().getIdentificationVariable(); + dmlStatement = statement; visitUpdateStatementOnly( statement ); } finally { - dmlTargetTableAlias = oldDmlTargetTableAlias; + dmlStatement = oldDmlTargetTableAlias; } } @@ -823,14 +828,14 @@ public abstract class AbstractSqlAstTranslator implemen @Override public void visitInsertStatement(InsertStatement statement) { - String oldDmlTargetTableAlias = dmlTargetTableAlias; - dmlTargetTableAlias = null; + MutationStatement oldDmlStatement = dmlStatement; + dmlStatement = null; try { visitCteContainer( statement ); visitInsertStatementOnly( statement ); } finally { - dmlTargetTableAlias = oldDmlTargetTableAlias; + dmlStatement = oldDmlStatement; } } @@ -3060,11 +3065,23 @@ public abstract class AbstractSqlAstTranslator implemen if ( e instanceof SqlSelectionExpression ) { return (SqlSelectionExpression) e; } + else if ( e instanceof SqmPathInterpretation ) { + final Expression sqlExpression = ( (SqmPathInterpretation) e ).getSqlExpression(); + if ( sqlExpression instanceof SqlSelectionExpression ) { + return (SqlSelectionExpression) sqlExpression; + } + } } } else if ( expression instanceof SqlSelectionExpression ) { return (SqlSelectionExpression) expression; } + else if ( expression instanceof SqmPathInterpretation ) { + final Expression sqlExpression = ( (SqmPathInterpretation) expression ).getSqlExpression(); + if ( sqlExpression instanceof SqlSelectionExpression ) { + return (SqlSelectionExpression) sqlExpression; + } + } return null; } @@ -3620,21 +3637,35 @@ public abstract class AbstractSqlAstTranslator implemen @Override public void visitColumnReference(ColumnReference columnReference) { + final String dmlTargetTableAlias = getDmlTargetTableAlias(); if ( dmlTargetTableAlias != null && dmlTargetTableAlias.equals( columnReference.getQualifier() ) ) { // todo (6.0) : use the Dialect to determine how to handle column references // - specifically should they use the table-alias, the table-expression // or neither for its qualifier - // for now, use the unqualified form + final String tableExpression = getCurrentDmlStatement().getTargetTable().getTableExpression(); + // Qualify the column reference with the table expression only in subqueries + final boolean qualifyColumn = !queryPartStack.isEmpty(); if ( columnReference.isColumnExpressionFormula() ) { // For formulas, we have to replace the qualifier as the alias was already rendered into the formula // This is fine for now as this is only temporary anyway until we render aliases for table references + final String replacement; + if ( qualifyColumn ) { + replacement = "$1" + tableExpression + ".$3"; + } + else { + replacement = "$1$3"; + } appendSql( columnReference.getColumnExpression() - .replaceAll( "(\\b)(" + dmlTargetTableAlias + "\\.)(\\b)", "$1$3" ) + .replaceAll( "(\\b)(" + dmlTargetTableAlias + "\\.)(\\b)", replacement ) ); } else { + if ( qualifyColumn ) { + appendSql( tableExpression ); + appendSql( '.' ); + } appendSql( columnReference.getColumnExpression() ); } } @@ -4465,6 +4496,9 @@ public abstract class AbstractSqlAstTranslator implemen @Override public void visitExistsPredicate(ExistsPredicate existsPredicate) { + if ( existsPredicate.isNegated() ) { + appendSql( "not " ); + } appendSql( "exists" ); existsPredicate.getExpression().accept( this ); } @@ -4577,11 +4611,27 @@ public abstract class AbstractSqlAstTranslator implemen final SqlTuple tuple; if ( ( tuple = SqlTupleContainer.getSqlTuple( expression ) ) != null ) { String separator = NO_SEPARATOR; - for ( Expression exp : tuple.getExpressions() ) { - appendSql( separator ); - exp.accept( this ); - appendSql( predicateValue ); - separator = " and "; + // HQL has different semantics for the not null check on embedded attribute mappings + // as the embeddable is not considered as null, if at least one sub-part is not null + if ( nullnessPredicate.isNegated() && expression.getExpressionType() instanceof AttributeMapping ) { + appendSql( '(' ); + for ( Expression exp : tuple.getExpressions() ) { + appendSql( separator ); + exp.accept( this ); + appendSql( predicateValue ); + separator = " or "; + } + appendSql( ')' ); + } + // For the is null check, and also for tuples in SQL in general, + // the semantics is that all sub-parts must match the predicate + else { + for ( Expression exp : tuple.getExpressions() ) { + appendSql( separator ); + exp.accept( this ); + appendSql( predicateValue ); + separator = " and "; + } } } else { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/delete/DeleteStatement.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/delete/DeleteStatement.java index e63349381e..9f5061dfdb 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/delete/DeleteStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/delete/DeleteStatement.java @@ -24,6 +24,7 @@ import org.hibernate.sql.ast.tree.predicate.Predicate; */ public class DeleteStatement extends AbstractMutationStatement { + public static final String DEFAULT_ALIAS = "to_delete_"; private final Predicate restriction; public DeleteStatement(TableReference targetTable, Predicate restriction) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/predicate/ExistsPredicate.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/predicate/ExistsPredicate.java index 104333d357..a567771db5 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/predicate/ExistsPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/predicate/ExistsPredicate.java @@ -15,10 +15,12 @@ import org.hibernate.sql.ast.tree.select.QueryPart; */ public class ExistsPredicate implements Predicate { + private final boolean negated; private final QueryPart expression; private final JdbcMappingContainer expressionType; - public ExistsPredicate(QueryPart expression, JdbcMappingContainer expressionType) { + public ExistsPredicate(QueryPart expression, boolean negated, JdbcMappingContainer expressionType) { + this.negated = negated; this.expression = expression; this.expressionType = expressionType; } @@ -27,6 +29,10 @@ public class ExistsPredicate implements Predicate { return expression; } + public boolean isNegated() { + return negated; + } + @Override public boolean isEmpty() { return false; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java index 333304768b..c3e080d568 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java @@ -6,8 +6,10 @@ */ package org.hibernate.sql.exec.internal; +import java.io.Serializable; import java.sql.PreparedStatement; import java.util.List; +import java.util.Set; import java.util.Spliterator; import java.util.Spliterators; import java.util.concurrent.TimeUnit; @@ -23,6 +25,7 @@ import org.hibernate.cache.spi.QueryResultsCache; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.query.TupleTransformer; import org.hibernate.query.internal.ScrollableResultsIterator; import org.hibernate.query.spi.ScrollableResultsImplementor; @@ -44,6 +47,7 @@ import org.hibernate.sql.results.jdbc.internal.ResultSetAccess; import org.hibernate.sql.results.jdbc.spi.JdbcValues; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; import org.hibernate.sql.results.spi.ListResultsConsumer; import org.hibernate.sql.results.spi.ResultsConsumer; @@ -51,6 +55,8 @@ import org.hibernate.sql.results.spi.RowReader; import org.hibernate.sql.results.spi.RowTransformer; import org.hibernate.sql.results.spi.ScrollableResultsConsumer; import org.hibernate.stat.spi.StatisticsImplementor; +import org.hibernate.type.BasicType; +import org.hibernate.type.descriptor.java.JavaType; /** * @author Steve Ebersole @@ -305,18 +311,22 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor { final SessionFactoryImplementor factory = session.getFactory(); final boolean queryCacheEnabled = factory.getSessionFactoryOptions().isQueryCacheEnabled(); - final List cachedResults; - - + final List cachedResults; final CacheMode cacheMode = JdbcExecHelper.resolveCacheMode( executionContext ); final JdbcValuesMappingProducer mappingProducer = jdbcSelect.getJdbcValuesMappingProducer(); - final JdbcValuesMapping jdbcValuesMapping = mappingProducer.resolve( resultSetAccess, factory ); - + final boolean cacheable = queryCacheEnabled && canBeCached; final QueryKey queryResultsCacheKey; - if ( queryCacheEnabled && cacheMode.isGetEnabled() && canBeCached ) { + if ( cacheable && cacheMode.isGetEnabled() ) { SqlExecLogger.INSTANCE.debugf( "Reading Query result cache data per CacheMode#isGetEnabled [%s]", cacheMode.name() ); + final Set querySpaces = jdbcSelect.getAffectedTableNames(); + if ( querySpaces == null || querySpaces.size() == 0 ) { + SqlExecLogger.INSTANCE.tracev( "Unexpected querySpaces is {0}", ( querySpaces == null ? querySpaces : "empty" ) ); + } + else { + SqlExecLogger.INSTANCE.tracev( "querySpaces is {0}", querySpaces ); + } final QueryResultsCache queryCache = factory .getCache() @@ -340,7 +350,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor { queryResultsCacheKey, // todo (6.0) : `querySpaces` and `session` make perfect sense as args, but its odd passing those into this method just to pass along // atm we do not even collect querySpaces, but we need to - jdbcSelect.getAffectedTableNames(), + querySpaces, session ); @@ -367,7 +377,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor { cacheMode.name() ); cachedResults = null; - if ( queryCacheEnabled && canBeCached ) { + if ( cacheable && cacheMode.isPutEnabled() ) { queryResultsCacheKey = QueryKey.from( jdbcSelect.getSql(), executionContext.getQueryOptions().getLimit(), @@ -381,18 +391,174 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor { } if ( cachedResults == null ) { + final JdbcValuesMetadata metadataForCache; + final JdbcValuesMapping jdbcValuesMapping; + if ( queryResultsCacheKey == null ) { + jdbcValuesMapping = mappingProducer.resolve( resultSetAccess, factory ); + metadataForCache = null; + } + else { + // If we need to put the values into the cache, we need to be able to capture the JdbcValuesMetadata + final CapturingJdbcValuesMetadata capturingMetadata = new CapturingJdbcValuesMetadata( resultSetAccess ); + jdbcValuesMapping = mappingProducer.resolve( capturingMetadata, factory ); + metadataForCache = capturingMetadata.resolveMetadataForCache(); + } return new JdbcValuesResultSetImpl( resultSetAccess, queryResultsCacheKey, queryIdentifier, executionContext.getQueryOptions(), jdbcValuesMapping, + metadataForCache, executionContext ); } else { + final JdbcValuesMapping jdbcValuesMapping; + if ( cachedResults.isEmpty() || !( cachedResults.get( 0 ) instanceof JdbcValuesMetadata ) ) { + jdbcValuesMapping = mappingProducer.resolve( resultSetAccess, factory ); + } + else { + jdbcValuesMapping = mappingProducer.resolve( (JdbcValuesMetadata) cachedResults.get( 0 ), factory ); + } return new JdbcValuesCacheHit( cachedResults, jdbcValuesMapping ); } } + private static class CapturingJdbcValuesMetadata implements JdbcValuesMetadata { + private final ResultSetAccess resultSetAccess; + private String[] columnNames; + private BasicType[] types; + + public CapturingJdbcValuesMetadata(ResultSetAccess resultSetAccess) { + this.resultSetAccess = resultSetAccess; + } + + private void initializeArrays() { + final int columnCount = resultSetAccess.getColumnCount(); + columnNames = new String[columnCount]; + types = new BasicType[columnCount]; + } + + @Override + public int getColumnCount() { + if ( columnNames == null ) { + initializeArrays(); + } + return columnNames.length; + } + + @Override + public int resolveColumnPosition(String columnName) { + if ( columnNames == null ) { + initializeArrays(); + } + int position; + if ( columnNames == null ) { + position = resultSetAccess.resolveColumnPosition( columnName ); + columnNames[position - 1] = columnName; + } + else if ( ( position = ArrayHelper.indexOf( columnNames, columnName ) + 1 ) == 0 ) { + position = resultSetAccess.resolveColumnPosition( columnName ); + columnNames[position - 1] = columnName; + } + return position; + } + + @Override + public String resolveColumnName(int position) { + if ( columnNames == null ) { + initializeArrays(); + } + String name; + if ( columnNames == null ) { + name = resultSetAccess.resolveColumnName( position ); + columnNames[position - 1] = name; + } + else if ( ( name = columnNames[position - 1] ) == null ) { + name = resultSetAccess.resolveColumnName( position ); + columnNames[position - 1] = name; + } + return name; + } + + @Override + public BasicType resolveType( + int position, + JavaType explicitJavaTypeDescriptor, + SessionFactoryImplementor sessionFactory) { + if ( columnNames == null ) { + initializeArrays(); + } + final BasicType basicType = resultSetAccess.resolveType( + position, + explicitJavaTypeDescriptor, + sessionFactory + ); + types[position - 1] = basicType; + return basicType; + } + + public JdbcValuesMetadata resolveMetadataForCache() { + if ( columnNames == null ) { + return null; + } + return new CachedJdbcValuesMetadata( columnNames, types ); + } + } + + private static class CachedJdbcValuesMetadata implements JdbcValuesMetadata, Serializable { + private final String[] columnNames; + private final BasicType[] types; + + public CachedJdbcValuesMetadata(String[] columnNames, BasicType[] types) { + this.columnNames = columnNames; + this.types = types; + } + + @Override + public int getColumnCount() { + return columnNames.length; + } + + @Override + public int resolveColumnPosition(String columnName) { + final int position = ArrayHelper.indexOf( columnNames, columnName ) + 1; + if ( position == 0 ) { + throw new IllegalStateException( "Unexpected resolving of unavailable column: " + columnName ); + } + return position; + } + + @Override + public String resolveColumnName(int position) { + final String name = columnNames[position - 1]; + if ( name == null ) { + throw new IllegalStateException( "Unexpected resolving of unavailable column at position: " + position ); + } + return name; + } + + @Override + public BasicType resolveType( + int position, + JavaType explicitJavaTypeDescriptor, + SessionFactoryImplementor sessionFactory) { + final BasicType type = types[position - 1]; + if ( type == null ) { + throw new IllegalStateException( "Unexpected resolving of unavailable column at position: " + position ); + } + if ( explicitJavaTypeDescriptor == null || type.getJavaTypeDescriptor() == explicitJavaTypeDescriptor ) { + //noinspection unchecked + return (BasicType) type; + } + else { + return sessionFactory.getTypeConfiguration().getBasicTypeRegistry().resolve( + explicitJavaTypeDescriptor, + type.getJdbcTypeDescriptor() + ); + } + } + + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/caching/internal/QueryCachePutManagerEnabledImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/caching/internal/QueryCachePutManagerEnabledImpl.java index 58b19c75af..2beaa41077 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/caching/internal/QueryCachePutManagerEnabledImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/caching/internal/QueryCachePutManagerEnabledImpl.java @@ -14,6 +14,7 @@ import org.hibernate.cache.spi.QueryKey; import org.hibernate.cache.spi.QueryResultsCache; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.sql.results.caching.QueryCachePutManager; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.stat.spi.StatisticsImplementor; /** @@ -27,17 +28,21 @@ public class QueryCachePutManagerEnabledImpl implements QueryCachePutManager { private final StatisticsImplementor statistics; private final QueryKey queryKey; private final String queryIdentifier; - private final List dataToCache = new ArrayList<>(); + private final List dataToCache = new ArrayList<>(); public QueryCachePutManagerEnabledImpl( QueryResultsCache queryCache, StatisticsImplementor statistics, QueryKey queryKey, - String queryIdentifier) { + String queryIdentifier, + JdbcValuesMetadata metadataForCache) { this.queryCache = queryCache; this.statistics = statistics; this.queryKey = queryKey; this.queryIdentifier = queryIdentifier; + if ( metadataForCache != null ) { + dataToCache.add( metadataForCache ); + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiation.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiation.java index 7cc5ec625d..b9bfeb5437 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/instantiation/internal/DynamicInstantiation.java @@ -63,6 +63,10 @@ public class DynamicInstantiation implements DomainResultProducer { throw new ConversionException( "Unexpected call to DynamicInstantiation#addAgument after previously complete" ); } + if ( arguments == null ) { + arguments = new ArrayList<>(); + } + if ( List.class.equals( getTargetJavaTypeDescriptor().getJavaTypeClass() ) ) { // really should not have an alias... if ( alias != null && log.isDebugEnabled() ) { @@ -75,21 +79,12 @@ public class DynamicInstantiation implements DomainResultProducer { } } else if ( Map.class.equals( getTargetJavaTypeDescriptor().getJavaTypeClass() ) ) { - // must have an alias... + // Retain the default alias we also used in 5.x which is the position if ( alias == null ) { - log.warnf( - "Argument [%s] for dynamic Map instantiation did not declare an 'injection alias', " + - "but such aliases are needed for dynamic Map instantiations; " + - "will likely cause problems later processing query results", - argumentResultProducer.toString() - ); + alias = Integer.toString( arguments.size() ); } } - if ( arguments == null ) { - arguments = new ArrayList<>(); - } - arguments.add( new DynamicInstantiationArgument<>( argumentResultProducer, alias ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesCacheHit.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesCacheHit.java index c0ea4c27fc..b0b300fd20 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesCacheHit.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesCacheHit.java @@ -12,6 +12,7 @@ import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.sql.results.ResultsLogger; import org.hibernate.sql.results.caching.internal.QueryCachePutManagerDisabledImpl; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; /** @@ -39,19 +40,30 @@ public class JdbcValuesCacheHit extends AbstractJdbcValues { this.resolvedMapping = resolvedMapping; } - public JdbcValuesCacheHit(List cachedResults, JdbcValuesMapping resolvedMapping) { + public JdbcValuesCacheHit(List cachedResults, JdbcValuesMapping resolvedMapping) { this( extractData( cachedResults ), resolvedMapping ); } - private static Object[][] extractData(List cachedResults) { + private static Object[][] extractData(List cachedResults) { if ( CollectionHelper.isEmpty( cachedResults ) ) { return NO_DATA; } - final Object[][] data = new Object[cachedResults.size()][]; - for ( int i = 0; i < cachedResults.size(); i++ ) { - final Object[] row = cachedResults.get( i ); - data[ i ] = row; + final Object[][] data; + if ( cachedResults.get( 0 ) instanceof JdbcValuesMetadata ) { + final int end = cachedResults.size() - 1; + data = new Object[end][]; + for ( int i = 0; i < end; i++ ) { + final Object[] row = (Object[]) cachedResults.get( i + 1 ); + data[i] = row; + } + } + else { + data = new Object[cachedResults.size()][]; + for ( int i = 0; i < cachedResults.size(); i++ ) { + final Object[] row = (Object[]) cachedResults.get( i ); + data[i] = row; + } } return data; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesResultSetImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesResultSetImpl.java index 45a38a2bea..407274ee85 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesResultSetImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesResultSetImpl.java @@ -9,7 +9,6 @@ package org.hibernate.sql.results.jdbc.internal; import java.sql.SQLException; import java.util.Arrays; -import org.hibernate.CacheMode; import org.hibernate.HibernateException; import org.hibernate.cache.spi.QueryKey; import org.hibernate.cache.spi.QueryResultsCache; @@ -17,12 +16,12 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.spi.QueryOptions; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.exec.ExecutionException; -import org.hibernate.sql.exec.internal.JdbcExecHelper; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.results.caching.QueryCachePutManager; import org.hibernate.sql.results.caching.internal.QueryCachePutManagerDisabledImpl; import org.hibernate.sql.results.caching.internal.QueryCachePutManagerEnabledImpl; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; /** @@ -45,8 +44,9 @@ public class JdbcValuesResultSetImpl extends AbstractJdbcValues { String queryIdentifier, QueryOptions queryOptions, JdbcValuesMapping valuesMapping, + JdbcValuesMetadata metadataForCache, ExecutionContext executionContext) { - super( resolveQueryCachePutManager( executionContext, queryOptions, queryCacheKey, queryIdentifier ) ); + super( resolveQueryCachePutManager( executionContext, queryOptions, queryCacheKey, queryIdentifier, metadataForCache ) ); this.resultSetAccess = resultSetAccess; this.valuesMapping = valuesMapping; this.executionContext = executionContext; @@ -59,22 +59,18 @@ public class JdbcValuesResultSetImpl extends AbstractJdbcValues { ExecutionContext executionContext, QueryOptions queryOptions, QueryKey queryCacheKey, - String queryIdentifier) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final boolean queryCacheEnabled = factory - .getSessionFactoryOptions() - .isQueryCacheEnabled(); - final CacheMode cacheMode = JdbcExecHelper.resolveCacheMode( executionContext ); - - if ( queryCacheEnabled && cacheMode.isPutEnabled() ) { - final QueryResultsCache queryCache = factory - .getCache() + String queryIdentifier, + JdbcValuesMetadata metadataForCache) { + if ( queryCacheKey != null ) { + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + final QueryResultsCache queryCache = factory.getCache() .getQueryResultsCache( queryOptions.getResultCacheRegionName() ); return new QueryCachePutManagerEnabledImpl( queryCache, factory.getStatistics(), queryCacheKey, - queryIdentifier + queryIdentifier, + metadataForCache ); } else { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/ResultSetAccess.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/ResultSetAccess.java index 074ef1230f..efc476ae9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/ResultSetAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/ResultSetAccess.java @@ -72,7 +72,10 @@ public interface ResultSetAccess extends JdbcValuesMetadata { } @Override - default BasicType resolveType(int position, JavaType explicitJavaTypeDescriptor) { + default BasicType resolveType( + int position, + JavaType explicitJavaTypeDescriptor, + SessionFactoryImplementor sessionFactory) { final TypeConfiguration typeConfiguration = getFactory().getTypeConfiguration(); final JdbcServices jdbcServices = getFactory().getJdbcServices(); try { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/JdbcValuesMetadata.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/JdbcValuesMetadata.java index 2367e7a17d..ce7a78f7aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/JdbcValuesMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/JdbcValuesMetadata.java @@ -6,6 +6,7 @@ */ package org.hibernate.sql.results.jdbc.spi; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.java.JavaType; @@ -32,6 +33,9 @@ public interface JdbcValuesMetadata { /** * The basic type of a particular result value by position */ - BasicType resolveType(int position, JavaType explicitJavaTypeDescriptor); + BasicType resolveType( + int position, + JavaType explicitJavaTypeDescriptor, + SessionFactoryImplementor sessionFactory); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigDecimalJavaTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigDecimalJavaTypeDescriptor.java index 815d5d8312..d90c486e68 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigDecimalJavaTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigDecimalJavaTypeDescriptor.java @@ -92,6 +92,24 @@ public class BigDecimalJavaTypeDescriptor extends AbstractClassJavaTypeDescripto throw unknownWrap( value.getClass() ); } + @Override + public boolean isWider(JavaType javaType) { + switch ( javaType.getJavaType().getTypeName() ) { + case "byte": + case "java.lang.Byte": + case "short": + case "java.lang.Short": + case "int": + case "java.lang.Integer": + case "long": + case "java.lang.Long": + case "java.math.BigInteger": + return true; + default: + return false; + } + } + @Override public long getDefaultSqlLength(Dialect dialect, JdbcType jdbcType) { return getDefaultSqlPrecision( dialect, jdbcType ) + 2; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigIntegerJavaTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigIntegerJavaTypeDescriptor.java index f744681cc4..93c22da0bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigIntegerJavaTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BigIntegerJavaTypeDescriptor.java @@ -96,6 +96,23 @@ public class BigIntegerJavaTypeDescriptor extends AbstractClassJavaTypeDescripto throw unknownWrap( value.getClass() ); } + @Override + public boolean isWider(JavaType javaType) { + switch ( javaType.getJavaType().getTypeName() ) { + case "byte": + case "java.lang.Byte": + case "short": + case "java.lang.Short": + case "int": + case "java.lang.Integer": + case "long": + case "java.lang.Long": + return true; + default: + return false; + } + } + @Override public long getDefaultSqlLength(Dialect dialect, JdbcType jdbcType) { return getDefaultSqlPrecision( dialect, jdbcType )+1; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoubleJavaTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoubleJavaTypeDescriptor.java index 61da55b9a9..808345be4f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoubleJavaTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoubleJavaTypeDescriptor.java @@ -96,6 +96,27 @@ public class DoubleJavaTypeDescriptor extends AbstractClassJavaTypeDescriptor javaType) { + switch ( javaType.getJavaType().getTypeName() ) { + case "byte": + case "java.lang.Byte": + case "short": + case "java.lang.Short": + case "int": + case "java.lang.Integer": + case "long": + case "java.lang.Long": + case "float": + case "java.lang.Float": + case "java.math.BigInteger": + case "java.math.BigDecimal": + return true; + default: + return false; + } + } + @SuppressWarnings("rawtypes") @Override public Class getPrimitiveClass() { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatTypeDescriptor.java index 898d7b6646..52c49d4f3e 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatTypeDescriptor.java @@ -95,6 +95,25 @@ public class FloatTypeDescriptor extends AbstractClassJavaTypeDescriptor throw unknownWrap( value.getClass() ); } + @Override + public boolean isWider(JavaType javaType) { + switch ( javaType.getJavaType().getTypeName() ) { + case "byte": + case "java.lang.Byte": + case "short": + case "java.lang.Short": + case "int": + case "java.lang.Integer": + case "long": + case "java.lang.Long": + case "java.math.BigInteger": + case "java.math.BigDecimal": + return true; + default: + return false; + } + } + @Override public Class getPrimitiveClass() { return float.class; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerJavaTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerJavaTypeDescriptor.java index 29bc5b12e9..7ed8c7ad5f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerJavaTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerJavaTypeDescriptor.java @@ -92,6 +92,19 @@ public class IntegerJavaTypeDescriptor extends AbstractClassJavaTypeDescriptor javaType) { + switch ( javaType.getJavaType().getTypeName() ) { + case "byte": + case "java.lang.Byte": + case "short": + case "java.lang.Short": + return true; + default: + return false; + } + } + @Override public Class getPrimitiveClass() { return int.class; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java index ee25a88682..d99e53534a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java @@ -220,6 +220,14 @@ public interface JavaType extends Serializable { */ T wrap(X value, WrapperOptions options); + /** + * Returns whether this java type is wider than the given type + * i.e. if the given type can be widened to this java type. + */ + default boolean isWider(JavaType javaType) { + return false; + } + interface CoercionContext { TypeConfiguration getTypeConfiguration(); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LongJavaTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LongJavaTypeDescriptor.java index a6e0995033..92b1938d23 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LongJavaTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LongJavaTypeDescriptor.java @@ -92,6 +92,21 @@ public class LongJavaTypeDescriptor extends AbstractClassJavaTypeDescriptor javaType) { + switch ( javaType.getJavaType().getTypeName() ) { + case "byte": + case "java.lang.Byte": + case "short": + case "java.lang.Short": + case "int": + case "java.lang.Integer": + return true; + default: + return false; + } + } + @Override public Long coerce(X value, CoercionContext coercionContext) { if ( value == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ShortJavaTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ShortJavaTypeDescriptor.java index 95ed4b0754..2565f3f431 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ShortJavaTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ShortJavaTypeDescriptor.java @@ -38,6 +38,17 @@ public class ShortJavaTypeDescriptor extends AbstractClassJavaTypeDescriptor javaType) { + switch ( javaType.getJavaType().getTypeName() ) { + case "byte": + case "java.lang.Byte": + return true; + default: + return false; + } + } + @SuppressWarnings({ "unchecked" }) @Override public X unwrap(Short value, Class type, WrapperOptions options) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/StringJavaTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/StringJavaTypeDescriptor.java index 553500a57c..dc09d1be33 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/StringJavaTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/StringJavaTypeDescriptor.java @@ -99,4 +99,17 @@ public class StringJavaTypeDescriptor extends AbstractClassJavaTypeDescriptor javaType) { + switch ( javaType.getJavaType().getTypeName() ) { + case "char": + case "char[]": + case "java.lang.Character": + case "java.lang.Character[]": + return true; + default: + return false; + } + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/QuotedIdentifierTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/QuotedIdentifierTest.java new file mode 100644 index 0000000000..ccc5f83dd3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/QuotedIdentifierTest.java @@ -0,0 +1,121 @@ +/* + * 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 . + */ +package org.hibernate.orm.test.hql; + +import java.util.List; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Tuple; +import jakarta.persistence.TypedQuery; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * @author Christian Beikov + */ +public class QuotedIdentifierTest extends BaseCoreFunctionalTestCase { + + private Person person; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class + }; + } + + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + person = new Person(); + person.setName( "Chuck" ); + person.setSurname( "Norris" ); + session.persist( person ); + } ); + } + + @Test + public void testQuotedIdentifier() { + doInHibernate( this::sessionFactory, session -> { + TypedQuery query = session.createQuery( + "select `the person`.`name` as `The person name` " + + "from `The Person` `the person`", + Tuple.class + ); + List resultList = query.getResultList(); + assertEquals( 1, resultList.size() ); + assertEquals( "Chuck", resultList.get( 0 ).get( "The person name" ) ); + } ); + } + + @Entity(name = "The Person") + public static class Person { + + @Id + @GeneratedValue + private Integer id; + + @Column(name = "the name") + private String name; + + @Column + private String surname; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSurname() { + return surname; + } + + public void setSurname(String surname) { + this.surname = surname; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !( o instanceof Person ) ) { + return false; + } + + Person person = (Person) o; + + return id != null ? id.equals( person.id ) : person.id == null; + + } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/temporals/ProposedGeneratedTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/temporals/ProposedGeneratedTests.java index 2f8e507516..0c6e934288 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/temporals/ProposedGeneratedTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/temporals/ProposedGeneratedTests.java @@ -35,7 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat; @SkipForDialect( dialectClass = SybaseASEDialect.class, matchSubTypes = true, reason = "CURRENT_TIMESTAMP not supported in insert/update in Sybase ASE. Also see https://groups.google.com/g/comp.databases.sybase/c/j-RxPnF3img" ) public class ProposedGeneratedTests { @Test - public void test(SessionFactoryScope scope) { + public void test(SessionFactoryScope scope) throws InterruptedException { final GeneratedInstantEntity created = scope.fromTransaction( (session) -> { final GeneratedInstantEntity entity = new GeneratedInstantEntity( 1, "tsifr" ); session.persist( entity ); @@ -48,6 +48,9 @@ public class ProposedGeneratedTests { created.name = "first"; + // Precision is in milliseconds, so sleep a bit to ensure the TS changes + Thread.sleep( 10L ); + // then changing final GeneratedInstantEntity merged = scope.fromTransaction( (session) -> { return (GeneratedInstantEntity) session.merge( created ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytomany/ManyToManyHqlMemberOfQueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytomany/ManyToManyHqlMemberOfQueryTest.java index d454ee236b..c476d95f5c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytomany/ManyToManyHqlMemberOfQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytomany/ManyToManyHqlMemberOfQueryTest.java @@ -144,7 +144,8 @@ public class ManyToManyHqlMemberOfQueryTest { List persons = session.createQuery( "select p " + "from Person p " + - "where :call member of p.phones.calls", Person.class ) + "join p.phones phone " + + "where :call member of phone.calls", Person.class ) .setParameter( "call", call ) .getResultList(); assertEquals( 1, persons.size() ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetomany/OneToManyBidirectionalTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetomany/OneToManyBidirectionalTest.java index 37168edee7..ed656611ea 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetomany/OneToManyBidirectionalTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetomany/OneToManyBidirectionalTest.java @@ -623,7 +623,6 @@ public class OneToManyBidirectionalTest { } @Test - @FailureExpected(reason = "It should throw an exception because query specified join fetching, but the owner of the fetched association was not present in the select list") public void testItemJoinWithFetchJoin(SessionFactoryScope scope) { SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); Assertions.assertThrows( IllegalArgumentException.class, () -> diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetomany/OneToManyHqlMemberOfQueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetomany/OneToManyHqlMemberOfQueryTest.java index 28a8a4f077..b8e02af9e8 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetomany/OneToManyHqlMemberOfQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetomany/OneToManyHqlMemberOfQueryTest.java @@ -134,7 +134,8 @@ public class OneToManyHqlMemberOfQueryTest { List persons = session.createQuery( "select p " + "from Person p " + - "where :call member of p.phones.calls", Person.class ) + "join p.phones phone " + + "where :call member of phone.calls", Person.class ) .setParameter( "call", call ) .getResultList(); assertEquals( 1, persons.size() ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/InsertUpdateTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/InsertUpdateTests.java index 21288bd693..3eac7df8c9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/InsertUpdateTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/InsertUpdateTests.java @@ -49,7 +49,7 @@ public class InsertUpdateTests { ); } - @Test @FailureExpected(reason = "update broken for embedded") + @Test public void testUpdateEmbedded(SessionFactoryScope scope) { scope.inTransaction( session -> { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java index c560ce3d72..e93dde207c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java @@ -86,7 +86,7 @@ public class QueryAndSQLTest extends BaseCoreFunctionalTestCase { @Test @SkipForDialect(value = AbstractHANADialect.class, comment = "invalid name of function or procedure: SYSDATE") public void testNativeQueryWithFormulaAttributeWithoutAlias() { - String sql = "select TABLE_NAME , current_date() from ALL_TABLES where TABLE_NAME = 'AUDIT_ACTIONS' "; + String sql = "select TABLE_NAME , " + getDialect().currentDate() + ", null as daysOld from ALL_TABLES where TABLE_NAME = 'AUDIT_ACTIONS' "; Session s = openSession(); s.beginTransaction(); s.createNativeQuery( sql ).addEntity( "t", AllTables.class ).list(); @@ -119,13 +119,13 @@ public class QueryAndSQLTest extends BaseCoreFunctionalTestCase { s.flush(); s.clear(); - List chaoses = s.createQuery( "from Chaos where chaos_size is null or chaos_size = :chaos_size" ) - .setParameter( "chaos_size", null ) + List chaoses = s.createQuery( "from Chaos where size is null or size = :size" ) + .setParameter( "size", null ) .list(); assertEquals( 1, chaoses.size() ); - chaoses = s.createQuery( "from Chaos where chaos_size = :chaos_size" ) - .setParameter( "chaos_size", null ) + chaoses = s.createQuery( "from Chaos where size = :size" ) + .setParameter( "size", null ) .list(); // should be no results because null != null assertEquals( 0, chaoses.size() ); @@ -159,13 +159,13 @@ public class QueryAndSQLTest extends BaseCoreFunctionalTestCase { s.flush(); s.clear(); - List chaoses = s.createQuery( "from Chaos where chaos_size is null or chaos_size = :chaos_size" ) - .setParameter( "chaos_size", null, StandardBasicTypes.LONG ) + List chaoses = s.createQuery( "from Chaos where size is null or size = :size" ) + .setParameter( "size", null, StandardBasicTypes.LONG ) .list(); assertEquals( 1, chaoses.size() ); - chaoses = s.createQuery( "from Chaos where chaos_size = :chaos_size" ) - .setParameter( "chaos_size", null, StandardBasicTypes.LONG ) + chaoses = s.createQuery( "from Chaos where size = :size" ) + .setParameter( "size", null, StandardBasicTypes.LONG ) .list(); // should be no results because null != null assertEquals( 0, chaoses.size() ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java index 34d5d3d8d4..8ffe9fffb0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java @@ -36,25 +36,22 @@ import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.Oracle8iDialect; -import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.dialect.PostgreSQLDialect; -import org.hibernate.dialect.SQLServer2008Dialect; import org.hibernate.dialect.SQLServerDialect; -import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.dialect.SybaseDialect; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.internal.util.StringHelper; import org.hibernate.loader.MultipleBagFetchException; -import org.hibernate.metamodel.model.domain.EmbeddableDomainType; import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource; import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource; import org.hibernate.orm.test.any.hbm.IntegerPropertyValue; import org.hibernate.orm.test.any.hbm.PropertySet; import org.hibernate.orm.test.any.hbm.PropertyValue; import org.hibernate.orm.test.any.hbm.StringPropertyValue; import org.hibernate.query.Query; -import org.hibernate.query.SemanticException; import org.hibernate.query.spi.QueryImplementor; +import org.hibernate.query.sqm.ParsingException; import org.hibernate.query.sqm.SqmExpressable; import org.hibernate.query.sqm.internal.QuerySqmImpl; import org.hibernate.query.sqm.tree.domain.SqmPath; @@ -290,8 +287,6 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { @Test @TestForIssue(jiraKey = "HHH-8699") - // For now, restrict to H2. Selecting w/ predicate functions cause issues for too many dialects. - @RequiresDialect(value = H2Dialect.class, jiraKey = "HHH-9052") public void testBooleanPredicate() { final Constructor created = fromTransaction( (session) -> { @@ -307,7 +302,15 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { (session) -> { final String qry = "select new Constructor( c.id, c.id is not null, c.id = c.id, c.id + 1, concat( c.id, 'foo' ) ) from Constructor c where c.id = :id"; final Constructor result = (Constructor) session.createQuery(qry ).setParameter( "id", created.getId() ).uniqueResult(); - assertEquals( created, result ); + assertEquals( 1, Constructor.getConstructorExecutionCount() ); + Constructor expected = new Constructor( + created.getId(), + true, + true, + created.getId() + 1, + created.getId() + "foo" + ); + assertEquals( expected, result ); } ); } @@ -336,7 +339,7 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { assertEquals( 1, selections.size() ); SqmSelection typeSelection = selections.get( 0 ); // always integer for joined - assertEquals( Integer.class, typeSelection.getNodeJavaTypeDescriptor().getJavaTypeClass() ); + assertEquals( Class.class, typeSelection.getNodeJavaTypeDescriptor().getJavaTypeClass() ); // test query = session.createQuery( "select type(a) from Animal a where type(a) = Dog" ); @@ -1063,8 +1066,7 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { if ( getDialect() instanceof AbstractHANADialect ) { s.createQuery( "from Animal where abs(cast(1 as double) - cast(:param as double)) = 1.0" ).setParameter( "param", 1 ).list(); } - else if ( !( getDialect() instanceof PostgreSQLDialect || getDialect() instanceof PostgreSQL81Dialect - || getDialect() instanceof MySQLDialect ) ) { + else if ( !( getDialect() instanceof PostgreSQLDialect || getDialect() instanceof MySQLDialect ) ) { s.createQuery( "from Animal where abs(cast(1 as float) - cast(:param as float)) = 1.0" ).setParameter( "param", 1 ).list(); } @@ -1580,7 +1582,7 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { assertEquals( 1, sqmStatement.getQuerySpec().getSelectClause().getSelections().size() ); final SqmSelection selection = sqmStatement.getQuerySpec().getSelectClause().getSelections().get( 0 ); final SqmExpressable selectionType = selection.getSelectableNode().getNodeType(); - assertThat( selectionType, CoreMatchers.instanceOf( EmbeddableDomainType.class ) ); + assertThat( selectionType, CoreMatchers.instanceOf( EmbeddedSqmPathSource.class ) ); assertEquals( Name.class, selection.getNodeJavaTypeDescriptor().getJavaTypeClass() ); @@ -1861,7 +1863,6 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { final SqmExpressable selectionType = selection.getSelectableNode().getNodeType(); assertThat( selectionType, instanceOf( EntityDomainType.class ) ); assertThat( selectionType.getExpressableJavaTypeDescriptor().getJavaTypeClass(), equalTo( Animal.class ) ); - assertThat( selection.getAlias(), is( "a" ) ); } ); Session s = openSession(); @@ -2235,14 +2236,13 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { verifyAnimalZooSelection( q ); List zoos = (List) q.list(); - assertEquals( 3, zoos.size() ); + assertEquals( 2, zoos.size() ); assertEquals( otherZoo.getName(), zoos.get( 0 ).getName() ); assertEquals( 2, zoos.get( 0 ).getMammals().size() ); assertEquals( 2, zoos.get( 0 ).getAnimals().size() ); - assertSame( zoos.get( 0 ), zoos.get( 1 ) ); - assertEquals( zoo.getName(), zoos.get( 2 ).getName() ); - assertEquals( 1, zoos.get( 2 ).getMammals().size() ); - assertEquals( 1, zoos.get( 2 ).getAnimals().size() ); + assertEquals( zoo.getName(), zoos.get( 1 ).getName() ); + assertEquals( 1, zoos.get( 1 ).getMammals().size() ); + assertEquals( 1, zoos.get( 1 ).getAnimals().size() ); s.clear(); s.delete(plat); s.delete( zebra ); @@ -2685,7 +2685,7 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { public void testIndexParams() { Session s = openSession(); Transaction t = s.beginTransaction(); - s.createQuery( "from Zoo zoo where zoo.mammals[:name] = :id" ) + s.createQuery( "from Zoo zoo where zoo.mammals[:name].id = :id" ) .setParameter( "name", "Walrus" ) .setParameter( "id", Long.valueOf( 123 ) ) .list(); @@ -2713,7 +2713,6 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { } @Test - @SkipForDialect( value = SybaseASE15Dialect.class, jiraKey = "HHH-6424") public void testAggregation() { Session s = openSession(); s.beginTransaction(); @@ -2757,8 +2756,7 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { assertEquals( 3L, results[2] ); // avg() should return a double assertTrue( Double.class.isInstance( results[3] ) ); - if (getDialect() instanceof SQLServer2008Dialect) assertEquals( 1.0D, results[3] ); - else assertEquals( 1.5D, results[3] ); + assertEquals( 1.5D, results[3] ); s.delete(h); s.delete(h2); s.getTransaction().commit(); @@ -2798,10 +2796,7 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { product.setProductId( "4321" ); s.save( product ); - List list = s.createQuery("from java.lang.Comparable").list(); - assertEquals( list.size(), 0 ); - - list = s.createQuery("from java.lang.Object").list(); + List list = s.createQuery("from java.lang.Object").list(); assertEquals( list.size(), 1 ); s.delete(product); @@ -2851,23 +2846,23 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { // No idea why teradata doesn't support this // if ( ! ( getDialect() instanceof SybaseDialect ) && ! ( getDialect() instanceof SQLServerDialect || getDialect() instanceof TeradataDialect ) ) { - if ( ! ( getDialect() instanceof SybaseDialect ) && ! ( getDialect() instanceof SQLServerDialect ) ) { - // In TransactSQL (the variant spoken by Sybase and SQLServer), the str() function - // is explicitly intended for numeric values only... - String dateStr1 = (String) session.createQuery("select str(current_date) from Animal").uniqueResult(); - String dateStr2 = (String) session.createQuery("select str(year(current_date))||'-'||str(month(current_date))||'-'||str(day(current_date)) from Animal").uniqueResult(); - System.out.println(dateStr1 + '=' + dateStr2); - if ( ! ( getDialect() instanceof Oracle8iDialect ) ) { //Oracle renders the name of the month :( - String[] dp1 = StringHelper.split("-", dateStr1); - String[] dp2 = StringHelper.split( "-", dateStr2 ); - for (int i=0; i<3; i++) { - if ( dp1[i].startsWith( "0" ) ) { - dp1[i] = dp1[i].substring( 1 ); - } - assertEquals( dp1[i], dp2[i] ); +// if ( ! ( getDialect() instanceof SybaseDialect ) && ! ( getDialect() instanceof SQLServerDialect ) ) { + // In TransactSQL (the variant spoken by Sybase and SQLServer), the str() function + // is explicitly intended for numeric values only... + String dateStr1 = (String) session.createQuery("select str(current_date) from Animal").uniqueResult(); + String dateStr2 = (String) session.createQuery("select str(year(current_date))||'-'||str(month(current_date))||'-'||str(day(current_date)) from Animal").uniqueResult(); + System.out.println(dateStr1 + '=' + dateStr2); + if ( ! ( getDialect() instanceof Oracle8iDialect ) ) { //Oracle renders the name of the month :( + String[] dp1 = StringHelper.split("-", dateStr1); + String[] dp2 = StringHelper.split( "-", dateStr2 ); + for (int i=0; i<3; i++) { + if ( dp1[i].startsWith( "0" ) ) { + dp1[i] = dp1[i].substring( 1 ); } + assertEquals( dp1[i], dp2[i] ); } } +// } session.delete(an); txn.commit(); session.close(); @@ -3169,7 +3164,7 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { session = openSession(); txn = session.beginTransaction(); - List results = session.createQuery( "from Zoo z join z.mammals m" ).list(); + List results = session.createQuery( "select z, m from Zoo z join z.mammals m" ).list(); assertEquals( "Incorrect result size", 1, results.size() ); assertTrue( "Incorrect result return type", results.get( 0 ) instanceof Object[] ); Object[] resultObjects = ( Object[] ) results.get( 0 ); @@ -3541,36 +3536,23 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { } @Test - public void testIllegalMixedTransformerQueries() { + public void testSelectNewTransformerQueries() { + createTestBaseData(); Session session = openSession(); Transaction t = session.beginTransaction(); - try { - getSelectNewQuery( session ).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).list(); - fail("'select new' together with a resulttransformer should result in error!"); - } - catch (IllegalArgumentException e) { - assertTyping( QueryException.class, e.getCause() ); - } - catch(QueryException he) { - assertTrue(he.getMessage().indexOf("ResultTransformer")==0); - } - - try { - getSelectNewQuery( session ).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).scroll(); - fail("'select new' together with a resulttransformer should result in error!"); - } - catch (IllegalArgumentException e) { - assertTyping( QueryException.class, e.getCause() ); - } - catch(HibernateException he) { - assertTrue(he.getMessage().indexOf("ResultTransformer")==0); - } + List list = session.createQuery( "select new Animal(an.description, an.bodyWeight) as animal from Animal an order by an.description" ) + .setResultTransformer( Transformers.ALIAS_TO_ENTITY_MAP ) + .list(); + assertEquals( 2, list.size() ); + Map m1 = (Map) list.get( 0 ); + Map m2 = (Map) list.get( 1 ); + assertEquals( 1, m1.size() ); + assertEquals( 1, m2.size() ); + assertEquals( "Mammal #1", m1.get( "animal" ).getDescription() ); + assertEquals( "Mammal #2", m2.get( "animal" ).getDescription() ); t.commit(); session.close(); - } - - private Query getSelectNewQuery(Session session) { - return session.createQuery( "select new Animal(an.description, an.bodyWeight) from Animal an" ); + destroyTestBaseData(); } @Test @@ -3739,7 +3721,7 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { * PostgreSQL >= 8.3.7 typecasts are no longer automatically allowed * http://www.postgresql.org/docs/current/static/release-8-3.html */ - if ( getDialect() instanceof PostgreSQLDialect || getDialect() instanceof PostgreSQL81Dialect + if ( getDialect() instanceof PostgreSQLDialect || getDialect() instanceof HSQLDialect || getDialect() instanceof CockroachDialect ) { hql = "from Animal a where bit_length(str(a.bodyWeight)) = 24"; @@ -3749,7 +3731,7 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { } session.createQuery(hql).list(); - if ( getDialect() instanceof PostgreSQLDialect || getDialect() instanceof PostgreSQL81Dialect + if ( getDialect() instanceof PostgreSQLDialect || getDialect() instanceof HSQLDialect || getDialect() instanceof CockroachDialect ) { hql = "select bit_length(str(a.bodyWeight)) from Animal a"; @@ -3798,8 +3780,8 @@ public class ASTParserLoadingTest extends BaseCoreFunctionalTestCase { } catch (IllegalArgumentException e) { final Throwable cause = e.getCause(); - assertThat( cause, instanceOf( SemanticException.class ) ); - assertTrue( cause.getMessage().contains( "expecting EOF, found ')'" ) ); + assertThat( cause, instanceOf( ParsingException.class ) ); + assertTrue( cause.getMessage().contains( "mismatched input ')' expecting {" ) ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/BulkManipulationTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/BulkManipulationTest.java index c9cbfcc8bd..b418fcfafc 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/BulkManipulationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/BulkManipulationTest.java @@ -232,19 +232,8 @@ public class BulkManipulationTest extends BaseCoreFunctionalTestCase { @Test public void testSelectWithNamedParamProjection() { Session s = openSession(); - try { - s.createQuery( "select :someParameter, id from Car" ); - fail( "Should throw an unsupported exception" ); - } - catch (IllegalArgumentException e) { - assertTyping( QueryException.class, e.getCause() ); - } - catch (QueryException q) { - // allright - } - finally { - s.close(); - } + // Ensure this works by executing it + s.createQuery( "select :someParameter, id from Car" ).setParameter( "someParameter", 1 ).getResultList(); } @Test @@ -315,19 +304,12 @@ public class BulkManipulationTest extends BaseCoreFunctionalTestCase { t.commit(); t = s.beginTransaction(); - try { - Query q1 = s.createQuery( - "insert into Pickup (id, owner, vin) select :id, (select :description from Animal a where a.description = :description), :vin from Car" ); - fail( "Unsupported exception should have been thrown" ); - } - catch (IllegalArgumentException e) { - assertTyping( QueryException.class, e.getCause() ); - } - catch (QueryException e) { - assertTrue( e.getMessage() - .indexOf( - "Use of parameters in subqueries of INSERT INTO DML statements is not supported." ) > -1 ); - } + Query q1 = s.createQuery( + "insert into Pickup (id, owner, vin) select :id, (select :description from Animal a where a.description = :description), :vin from Car" ); + q1.setParameter( "id", 10L ); + q1.setParameter( "description", "Frog" ); + q1.setParameter( "vin", "some" ); + q1.executeUpdate(); t.commit(); t = s.beginTransaction(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/FunctionNameAsColumnTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/FunctionNameAsColumnTest.java index e3439cc09b..a95b72f745 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/FunctionNameAsColumnTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/FunctionNameAsColumnTest.java @@ -18,12 +18,12 @@ import org.hibernate.Session; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.dialect.PostgresPlusDialect; -import org.hibernate.dialect.SybaseASE15Dialect; +import org.hibernate.dialect.SybaseASEDialect; import org.hibernate.testing.SkipForDialect; -import org.hibernate.testing.SkipLog; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.After; +import org.junit.Assume; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -35,7 +35,7 @@ import static org.junit.Assert.assertTrue; * * @author Gail Badner */ -@SkipForDialect(value = SybaseASE15Dialect.class, jiraKey = "HHH-6426") +@SkipForDialect(value = SybaseASEDialect.class, jiraKey = "HHH-6426") @SkipForDialect(value = PostgresPlusDialect.class, comment = "Almost all of the tests result in 'ambiguous column' errors.") public class FunctionNameAsColumnTest extends BaseCoreFunctionalTestCase { @Override @@ -167,8 +167,8 @@ public class FunctionNameAsColumnTest extends BaseCoreFunctionalTestCase { CriteriaQuery criteria = criteriaBuilder.createQuery( EntityWithFunctionAsColumnHolder.class ); Root root = criteria.from( EntityWithFunctionAsColumnHolder.class ); - root.fetch( "entityWithArgFunctionAsColumns", JoinType.LEFT ) - .fetch( "nextHolder", JoinType.LEFT ) + root.fetch( "entityWithArgFunctionAsColumns", JoinType.LEFT ); + root.fetch( "nextHolder", JoinType.LEFT ) .fetch( "entityWithArgFunctionAsColumns", JoinType.LEFT ); criteria.where( criteriaBuilder.isNotNull( root.get( "nextHolder" ) ) ); @@ -199,11 +199,10 @@ public class FunctionNameAsColumnTest extends BaseCoreFunctionalTestCase { @Test public void testGetMultiColumnSameNameAsNoArgFunctionHQL() { -// SQLFunction function = sessionFactory().getSqlFunctionRegistry().findSQLFunction( "current_date" ); -// if ( function == null || function.hasParenthesesIfNoArguments() ) { -// SkipLog.reportSkip( "current_date reuires ()", "tests noarg function that does not require ()" ); -// return; -// } + Assume.assumeFalse( + "current_date requires () but test is for noarg function that does not require ()", + sessionFactory().getJdbcServices().getDialect().currentDate().contains( "(" ) + ); inTransaction( s -> { @@ -231,7 +230,6 @@ public class FunctionNameAsColumnTest extends BaseCoreFunctionalTestCase { assertTrue( Hibernate.isInitialized( holder1.getNextHolder() ) ); assertTrue( Hibernate.isInitialized( holder1.getNextHolder().getEntityWithNoArgFunctionAsColumns() ) ); assertEquals( 1, holder1.getEntityWithNoArgFunctionAsColumns().size() ); - s.getTransaction().commit(); s.close(); EntityWithNoArgFunctionAsColumn e1 = (EntityWithNoArgFunctionAsColumn) holder1.getEntityWithNoArgFunctionAsColumns().iterator().next(); @@ -255,11 +253,10 @@ public class FunctionNameAsColumnTest extends BaseCoreFunctionalTestCase { @Test public void testGetMultiColumnSameNameAsNoArgFunctionCriteria() { -// SQLFunction function = sessionFactory().getSqlFunctionRegistry().findSQLFunction( "current_date" ); -// if ( function == null || function.hasParenthesesIfNoArguments() ) { -// SkipLog.reportSkip( "current_date reuires ()", "tests noarg function that does not require ()" ); -// return; -// } + Assume.assumeFalse( + "current_date requires () but test is for noarg function that does not require ()", + sessionFactory().getJdbcServices().getDialect().currentDate().contains( "(" ) + ); EntityWithFunctionAsColumnHolder holder1 = new EntityWithFunctionAsColumnHolder(); EntityWithFunctionAsColumnHolder holder2 = new EntityWithFunctionAsColumnHolder(); @@ -283,9 +280,9 @@ public class FunctionNameAsColumnTest extends BaseCoreFunctionalTestCase { CriteriaQuery criteria = criteriaBuilder.createQuery( EntityWithFunctionAsColumnHolder.class ); Root root = criteria.from( EntityWithFunctionAsColumnHolder.class ); - root.fetch( "entityWithArgFunctionAsColumns", JoinType.LEFT ) - .fetch( "nextHolder", JoinType.LEFT ) - .fetch( "entityWithArgFunctionAsColumns", JoinType.LEFT ); + root.fetch( "entityWithNoArgFunctionAsColumns", JoinType.LEFT ); + root.fetch( "nextHolder", JoinType.LEFT ) + .fetch( "entityWithNoArgFunctionAsColumns", JoinType.LEFT ); criteria.where( criteriaBuilder.isNotNull( root.get( "nextHolder" ) ) ); EntityWithFunctionAsColumnHolder holder = s.createQuery( criteria ).uniqueResult(); // holder1 = ( EntityWithFunctionAsColumnHolder ) s.createCriteria( EntityWithFunctionAsColumnHolder.class ) @@ -315,11 +312,10 @@ public class FunctionNameAsColumnTest extends BaseCoreFunctionalTestCase { @Test public void testNoArgFcnAndColumnSameNameAsNoArgFunctionHQL() { -// SQLFunction function = sessionFactory().getSqlFunctionRegistry().findSQLFunction( "current_date" ); -// if ( function == null || function.hasParenthesesIfNoArguments() ) { -// SkipLog.reportSkip( "current_date reuires ()", "tests noarg function that does not require ()" ); -// return; -// } + Assume.assumeFalse( + "current_date requires () but test is for noarg function that does not require ()", + sessionFactory().getJdbcServices().getDialect().currentDate().contains( "(" ) + ); EntityWithNoArgFunctionAsColumn e1 = new EntityWithNoArgFunctionAsColumn(); EntityWithNoArgFunctionAsColumn e2 = new EntityWithNoArgFunctionAsColumn(); @@ -358,11 +354,6 @@ public class FunctionNameAsColumnTest extends BaseCoreFunctionalTestCase { @After public void cleanup() { -// SQLFunction function = sessionFactory().getSqlFunctionRegistry().findSQLFunction( "current_date" ); -// if ( function == null || function.hasParenthesesIfNoArguments() ) { -// SkipLog.reportSkip( "current_date reuires ()", "tests noarg function that does not require ()" ); -// return; -// } inTransaction( s -> { s.createQuery( "delete from EntityWithArgFunctionAsColumn" ).executeUpdate(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/size/WhereClauseOrderBySizeTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/size/WhereClauseOrderBySizeTest.java index e0be5b20fa..cb1abf1001 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/size/WhereClauseOrderBySizeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/size/WhereClauseOrderBySizeTest.java @@ -26,7 +26,7 @@ import org.hibernate.annotations.ResultCheckStyle; import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; import org.hibernate.dialect.H2Dialect; -import org.hibernate.dialect.PostgreSQL82Dialect; +import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.RequiresDialect; @@ -34,7 +34,7 @@ import org.hibernate.testing.TestForIssue; import org.junit.Test; @TestForIssue(jiraKey = "HHH-14585") -@RequiresDialect(value = PostgreSQL82Dialect.class, comment = "Other databases may not support boolean data types") +@RequiresDialect(value = PostgreSQLDialect.class, comment = "Other databases may not support boolean data types") @RequiresDialect(value = H2Dialect.class, comment = "Other databases may not support boolean data types") public class WhereClauseOrderBySizeTest extends BaseEntityManagerFunctionalTestCase { @@ -63,15 +63,9 @@ public class WhereClauseOrderBySizeTest extends BaseEntityManagerFunctionalTestC "SELECT p FROM Person p ORDER BY size(p.books) DESC", Person.class ); - final TypedQuery orderByWorking = entityManager.createQuery( - "SELECT p FROM Person p ORDER BY p.books.size DESC", - Person.class - ); List dbPeopleBroken = orderByBroken.getResultList(); - List dbPeopleWorking = orderByWorking.getResultList(); - assertEquals( Arrays.asList( alice, bob ), dbPeopleWorking ); - assertEquals( dbPeopleWorking, dbPeopleBroken ); + assertEquals( Arrays.asList( alice, bob ), dbPeopleBroken ); // add 2 books to Bob final Book book2 = new Book(); @@ -83,9 +77,7 @@ public class WhereClauseOrderBySizeTest extends BaseEntityManagerFunctionalTestC entityManager.persist( book3 ); dbPeopleBroken = orderByBroken.getResultList(); - dbPeopleWorking = orderByWorking.getResultList(); - assertEquals( Arrays.asList( bob, alice ), dbPeopleWorking ); - assertEquals( dbPeopleWorking, dbPeopleBroken ); + assertEquals( Arrays.asList( bob, alice ), dbPeopleBroken ); // remove (soft-deleting) both Bob's books entityManager.remove( book2 ); @@ -93,9 +85,7 @@ public class WhereClauseOrderBySizeTest extends BaseEntityManagerFunctionalTestC // result lists are not equal anymore dbPeopleBroken = orderByBroken.getResultList(); - dbPeopleWorking = orderByWorking.getResultList(); - assertEquals( Arrays.asList( alice, bob ), dbPeopleWorking ); - assertEquals( dbPeopleWorking, dbPeopleBroken ); + assertEquals( Arrays.asList( alice, bob ), dbPeopleBroken ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/size/filter/WhereAnnotatedOneToManySizeTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/size/filter/WhereAnnotatedOneToManySizeTest.java index 6cc68af2f1..be90221ca6 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/size/filter/WhereAnnotatedOneToManySizeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/size/filter/WhereAnnotatedOneToManySizeTest.java @@ -84,7 +84,7 @@ public class WhereAnnotatedOneToManySizeTest extends BaseCoreFunctionalTestCase @After public void after() { inTransaction( session -> { - session.createQuery( "DELETE FROM City c" ).executeUpdate(); + session.createNativeQuery( "DELETE FROM City" ).executeUpdate(); session.createQuery( "DELETE FROM Region c" ).executeUpdate(); } ); } @@ -103,20 +103,6 @@ public class WhereAnnotatedOneToManySizeTest extends BaseCoreFunctionalTestCase } ); } - @Test - @SkipForDialect(value = DB2Dialect.class, comment = "DB2 does not support correlated subqueries in the ORDER BY clause") - @SkipForDialect(value = AbstractHANADialect.class, comment = "HANA db does not support correlated subqueries in the ORDER BY clause") - @SkipForDialect(value = TiDBDialect.class, comment = "TiDB db does not support correlated subqueries in the ORDER BY clause") - public void orderBy_dotSize() { - inSession( session -> { - QueryImplementor query = session.createQuery( - "select r, r.cities.size from Region r order by r.cities.size desc" ); - List result = query.getResultList(); - assertThat( result ).extracting( f -> f[0] ).extracting( "name" ).containsExactly( "Lombardy", "Lazio" ); - assertThat( result ).extracting( f -> f[1] ).containsExactly( 2, 1 ); - } ); - } - @Test public void project_sizeOf() { inSession( session -> { @@ -127,13 +113,4 @@ public class WhereAnnotatedOneToManySizeTest extends BaseCoreFunctionalTestCase } ); } - @Test - public void project_dotSize() { - inSession( session -> { - QueryImplementor query = session.createQuery( - "SELECT r.cities.size FROM Region r", Integer.class ); - List cityCounts = query.getResultList(); - assertThat( cityCounts ).containsExactlyInAnyOrder( 1, 2 ); - } ); - } } diff --git a/migration-guide.adoc b/migration-guide.adoc index ccb2976b04..82092436af 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -92,6 +92,44 @@ List postAndComments = entityManager.createStoredProcedureQuery( "fn_p * Functions * Multi-table bulk manipulation HQL/Criteria query handling +=== Remove support for special plural attribute properties + +Prior to 6.0, it was possible to de-reference special properties on plural attributes like `size` which was dropped. +The special properties lead to confusion and were sometimes ambiguous. The replacement is the function syntax. + +size:: +The collection size can be determined by using the `size( pluralAttribute )` function instead + +elements:: +The collection elements can be referred to by using the `value( pluralAttribute )` function instead + +indices:: +The collection indices can be referred to by using the `index( pluralAttribute )` or `key( pluralAttribute )` function instead + +index:: +The collection index can be referred to by using the `index( pluralAttribute )` or `key( pluralAttribute )` function instead + +maxindex:: +The collection maximum index can be determined by using the `maxindex( pluralAttribute )` function instead + +minindex:: +The collection minimum index can be determined by using the `minindex( pluralAttribute )` function instead + +maxelement:: +The collection maximum element can be determined by using the `maxelement( pluralAttribute )` function instead + +minelement:: +The collection minimum element can be determined by using the `minelement( pluralAttribute )` function instead + +=== Remove support for comparing association against FK value + +Previously Hibernate did allow comparing an association with an FK value like `... where alias.association = 1` +or `... where alias.association = alias.association.id` or even `... where alias.association = :param` where `param` +is bound to an integer `1`. This was supported prior to Hibernate 6.0 if the foreign key for the association is an integer. + +The right way to do this is de-referencing the association by the FK attribute `... where alias.association.id = 1` +which is guaranteed to not produce a join, or use an entity reference for `... where alias.association = :param` +where `param` is bound to `entityManager.getReference(EntityClass.class, 1)`. === Removals