From 4efffca23bb7d3f9046a949f20a3de93ab60951c Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Thu, 17 Dec 2020 18:16:09 +0100 Subject: [PATCH] Add support for FETCH clause, CTEs and set operations --- ci/build.sh | 10 +- docker_db.sh | 28 +- gradle/databases.gradle | 56 +- gradle/java-module.gradle | 4 +- .../org/hibernate/grammars/hql/HqlLexer.g4 | 11 +- .../org/hibernate/grammars/hql/HqlParser.g4 | 48 +- .../org/hibernate/CteSearchClauseKind.java | 23 + .../java/org/hibernate/FetchClauseType.java | 31 + .../main/java/org/hibernate/LockOptions.java | 34 + .../main/java/org/hibernate/NullOrdering.java | 34 + .../java/org/hibernate/NullPrecedence.java | 42 +- .../main/java/org/hibernate/SetOperator.java | 49 + .../dialect/AbstractHANADialect.java | 21 +- .../dialect/AbstractTransactSQLDialect.java | 11 +- .../org/hibernate/dialect/CUBRIDDialect.java | 22 +- .../dialect/CUBRIDSqlAstTranslator.java | 41 + .../org/hibernate/dialect/CacheDialect.java | 17 + .../dialect/CacheSqlAstTranslator.java | 72 + .../hibernate/dialect/CockroachDialect.java | 22 +- .../dialect/CockroachSqlAstTranslator.java | 72 + .../org/hibernate/dialect/DB2Dialect.java | 71 +- .../dialect/DB2SqlAstTranslator.java | 143 ++ .../org/hibernate/dialect/DB2iDialect.java | 22 +- .../dialect/DB2iSqlAstTranslator.java | 46 + .../org/hibernate/dialect/DB2zDialect.java | 17 + .../dialect/DB2zSqlAstTranslator.java | 46 + .../org/hibernate/dialect/DerbyDialect.java | 22 +- .../dialect/DerbySqlAstTranslator.java | 99 + .../java/org/hibernate/dialect/Dialect.java | 49 +- .../hibernate/dialect/FirebirdDialect.java | 22 +- .../dialect/FirebirdSqlAstTranslator.java | 108 + .../java/org/hibernate/dialect/H2Dialect.java | 56 +- .../hibernate/dialect/H2SqlAstTranslator.java | 67 + .../dialect/HANASqlAstTranslator.java | 72 + .../org/hibernate/dialect/HSQLDialect.java | 22 +- .../dialect/HSQLSqlAstTranslator.java | 51 + .../hibernate/dialect/InformixDialect.java | 17 + .../dialect/InformixSqlAstTranslator.java | 99 + .../org/hibernate/dialect/IngresDialect.java | 17 + .../dialect/IngresSqlAstTranslator.java | 99 + .../org/hibernate/dialect/MariaDBDialect.java | 49 +- .../dialect/MariaDBSqlAstTranslator.java | 74 + .../org/hibernate/dialect/MaxDBDialect.java | 24 + .../dialect/MaxDBSqlAstTranslator.java | 41 + .../hibernate/dialect/MimerSQLDialect.java | 17 + .../dialect/MimerSQLSqlAstTranslator.java | 42 + .../org/hibernate/dialect/MySQLDialect.java | 63 +- .../dialect/MySQLSqlAstTranslator.java | 75 + .../org/hibernate/dialect/OracleDialect.java | 22 +- .../dialect/OracleSqlAstTranslator.java | 83 + .../hibernate/dialect/PostgreSQLDialect.java | 45 +- .../dialect/PostgreSQLSqlAstTranslator.java | 103 + .../dialect/RDBMSOS2200SqlAstTranslator.java | 68 + .../hibernate/dialect/RDMSOS2200Dialect.java | 24 +- .../hibernate/dialect/SQLServerDialect.java | 17 + .../dialect/SQLServerSqlAstTranslator.java | 188 ++ .../org/hibernate/dialect/SpannerDialect.java | 24 +- .../dialect/SpannerSqlAstTranslator.java | 42 + .../hibernate/dialect/SybaseASEDialect.java | 17 + .../dialect/SybaseASESqlAstTranslator.java | 104 + .../dialect/SybaseAnywhereDialect.java | 17 + .../SybaseAnywhereSqlAstTranslator.java | 84 + .../org/hibernate/dialect/SybaseDialect.java | 17 + .../dialect/SybaseSqlAstTranslator.java | 55 + .../hibernate/dialect/TeradataDialect.java | 17 + .../dialect/TeradataSqlAstTranslator.java | 66 + .../hibernate/dialect/TimesTenDialect.java | 22 +- .../dialect/TimesTenSqlAstTranslator.java | 58 + .../java/org/hibernate/dialect/TypeNames.java | 14 +- .../pagination/AbstractLimitHandler.java | 138 ++ .../AbstractNoOffsetLimitHandler.java | 20 +- .../AbstractSimpleLimitHandler.java | 13 +- .../pagination/LegacyDB2LimitHandler.java | 28 +- .../pagination/LegacyLimitHandler.java | 17 + .../pagination/LegacyOracleLimitHandler.java | 36 + .../dialect/pagination/LimitHandler.java | 62 + .../dialect/pagination/NoopLimitHandler.java | 89 + .../pagination/OffsetFetchLimitHandler.java | 47 + .../pagination/SQLServer2005LimitHandler.java | 79 + .../pagination/SQLServer2012LimitHandler.java | 21 +- .../pagination/SkipFirstLimitHandler.java | 35 + .../sequence/MariaDBSequenceSupport.java | 22 + .../hibernate/engine/spi/RowSelection.java | 2 + .../util/collections/CollectionHelper.java | 12 + .../ast/internal/AbstractNaturalIdLoader.java | 14 +- .../CollectionElementLoaderByIndex.java | 8 +- .../internal/CollectionLoaderBatchKey.java | 5 +- .../internal/CollectionLoaderSingleKey.java | 8 +- .../CollectionLoaderSubSelectFetch.java | 3 +- .../internal/DatabaseSnapshotExecutor.java | 5 +- .../ast/internal/LoaderSelectBuilder.java | 3 +- .../internal/LoaderSqlAstCreationState.java | 19 +- .../ast/internal/MultiIdLoaderStandard.java | 6 +- .../MultiNaturalIdLoadingBatcher.java | 8 +- .../ast/internal/SimpleNaturalIdLoader.java | 13 +- .../SingleIdEntityLoaderDynamicBatch.java | 6 +- .../loader/ast/internal/SingleIdLoadPlan.java | 7 +- .../SingleUniqueKeyEntityLoaderStandard.java | 10 +- .../mapping/EntityValuedModelPart.java | 13 + .../BasicEntityIdentifierMappingImpl.java | 65 +- .../internal/BasicValuedCollectionPart.java | 10 + .../BasicValuedSingularAttributeMapping.java | 32 +- .../internal/EmbeddedAttributeMapping.java | 13 +- .../internal/EmbeddedCollectionPart.java | 19 + .../internal/EntityCollectionPart.java | 15 +- .../internal/EntityVersionMappingImpl.java | 52 +- .../internal/PluralAttributeMappingImpl.java | 17 + .../metamodel/model/domain/JpaMetamodel.java | 6 + .../domain/internal/JpaMetamodelImpl.java | 12 +- .../entity/AbstractEntityPersister.java | 86 +- .../entity/JoinedSubclassEntityPersister.java | 63 - .../main/java/org/hibernate/query/Limit.java | 39 +- .../query/criteria/JpaCollectionJoin.java | 2 - .../org/hibernate/query/criteria/JpaFrom.java | 2 - .../org/hibernate/query/criteria/JpaJoin.java | 3 - .../hibernate/query/criteria/JpaListJoin.java | 3 - .../hibernate/query/criteria/JpaMapJoin.java | 3 - .../query/criteria/JpaQueryStructure.java | 16 +- .../hibernate/query/criteria/JpaSetJoin.java | 2 - .../hibernate/query/criteria/JpaSubQuery.java | 15 + .../internal/QualifiedJoinPathConsumer.java | 20 + .../query/hql/internal/QuerySplitter.java | 31 +- .../hql/internal/SemanticQueryBuilder.java | 441 +++- .../query/spi/DelegatingQueryOptions.java | 131 ++ .../query/spi/SqlOmittingQueryOptions.java | 94 + .../internal/NativeSelectQueryPlanImpl.java | 7 + .../query/sqm/SemanticQueryWalker.java | 12 +- .../sqm/StrictJpaComplianceViolation.java | 3 + .../AggregatedSelectQueryPlanImpl.java | 8 + .../internal/ConcreteSqmSelectQueryPlan.java | 211 +- .../query/sqm/internal/QuerySqmImpl.java | 71 +- .../sqm/internal/SimpleDeleteQueryPlan.java | 79 +- .../sqm/internal/SimpleInsertQueryPlan.java | 81 +- .../sqm/internal/SimpleUpdateQueryPlan.java | 82 +- .../sqm/internal/SqmInterpretationsKey.java | 7 +- .../query/sqm/internal/SqmTreePrinter.java | 32 +- .../internal/MatchingIdSelectionHelper.java | 30 +- .../MultiTableSqmMutationConverter.java | 25 +- .../internal/SqmMutationStrategyHelper.java | 13 +- .../cte/AbstractCteMutationHandler.java | 220 +- .../internal/cte/CteDeleteHandler.java | 261 +-- .../mutation/internal/cte/CteStrategy.java | 46 +- .../internal/cte/CteUpdateHandler.java | 168 +- .../idtable/ExecuteWithIdTableHelper.java | 13 +- .../RestrictedDeleteExecutionDelegate.java | 14 +- .../idtable/TableBasedUpdateHandler.java | 38 +- .../UnrestrictedDeleteExecutionDelegate.java | 18 +- .../idtable/UpdateExecutionDelegate.java | 12 +- .../internal/inline/InlineDeleteHandler.java | 20 +- .../sqm/spi/BaseSemanticQueryWalker.java | 48 +- .../sqm/sql/BaseSqmToSqlAstConverter.java | 1783 ++++++++++++++--- .../query/sqm/sql/FromClauseIndex.java | 6 +- .../sqm/sql/SimpleSqmDeleteTranslator.java | 17 - .../sqm/sql/SimpleSqmUpdateTranslation.java | 39 - .../sqm/sql/SimpleSqmUpdateTranslator.java | 17 - .../query/sqm/sql/SqmInsertTranslation.java | 37 - .../query/sqm/sql/SqmInsertTranslator.java | 18 - .../sqm/sql/SqmQuerySpecTranslation.java | 37 - .../query/sqm/sql/SqmSelectTranslation.java | 44 - .../query/sqm/sql/SqmSelectTranslator.java | 20 - .../query/sqm/sql/SqmTranslation.java | 8 +- .../query/sqm/sql/SqmTranslator.java | 10 +- .../query/sqm/sql/SqmTranslatorFactory.java | 37 +- ...ation.java => StandardSqmTranslation.java} | 17 +- .../sqm/sql/StandardSqmTranslatorFactory.java | 66 +- ...> SqlAstQueryPartProcessingStateImpl.java} | 31 +- .../internal/StandardSqmDeleteTranslator.java | 137 -- .../internal/StandardSqmInsertTranslator.java | 237 --- .../internal/StandardSqmSelectTranslator.java | 632 ------ .../sql/internal/StandardSqmTranslator.java | 35 + .../internal/StandardSqmUpdateTranslator.java | 293 --- .../sqm/tree/AbstractSqmDmlStatement.java | 34 + .../query/sqm/tree/SqmDmlStatement.java | 4 +- .../query/sqm/tree/cte/SqmCteConsumer.java | 15 - .../query/sqm/tree/cte/SqmCteContainer.java | 27 + .../query/sqm/tree/cte/SqmCteStatement.java | 98 +- .../query/sqm/tree/cte/SqmCteTable.java | 24 + .../query/sqm/tree/cte/SqmCteTableColumn.java | 15 +- .../cte/SqmSearchClauseSpecification.java | 37 + .../sqm/tree/delete/SqmDeleteStatement.java | 3 +- .../tree/domain/AbstractSqmAttributeJoin.java | 2 + .../domain/AbstractSqmCorrelatedFrom.java | 47 - .../sqm/tree/domain/AbstractSqmFrom.java | 22 +- .../query/sqm/tree/domain/SqmBagJoin.java | 5 +- .../sqm/tree/domain/SqmCorrelatedBagJoin.java | 87 + .../tree/domain/SqmCorrelatedCrossJoin.java | 76 + .../tree/domain/SqmCorrelatedEntityJoin.java | 80 + .../tree/domain/SqmCorrelatedListJoin.java | 87 + .../sqm/tree/domain/SqmCorrelatedMapJoin.java | 87 + .../sqm/tree/domain/SqmCorrelatedRoot.java | 20 +- .../tree/domain/SqmCorrelatedRootJoin.java | 80 + .../sqm/tree/domain/SqmCorrelatedSetJoin.java | 87 + .../domain/SqmCorrelatedSingularJoin.java | 87 + .../query/sqm/tree/domain/SqmCorrelation.java | 4 +- .../query/sqm/tree/domain/SqmListJoin.java | 21 +- .../query/sqm/tree/domain/SqmMapJoin.java | 35 +- .../query/sqm/tree/domain/SqmSetJoin.java | 17 +- .../sqm/tree/domain/SqmSingularJoin.java | 16 + .../sqm/tree/domain/SqmTreatedCrossJoin.java | 2 +- .../query/sqm/tree/from/SqmCrossJoin.java | 20 +- .../query/sqm/tree/from/SqmEntityJoin.java | 20 +- .../query/sqm/tree/from/SqmFromClause.java | 10 +- .../query/sqm/tree/from/SqmRoot.java | 23 + .../tree/insert/SqmInsertSelectStatement.java | 19 +- .../tree/select/AbstractSqmSelectQuery.java | 88 +- .../query/sqm/tree/select/SqmQueryGroup.java | 70 + .../query/sqm/tree/select/SqmQueryPart.java | 136 ++ .../query/sqm/tree/select/SqmQuerySpec.java | 140 +- .../query/sqm/tree/select/SqmSelectQuery.java | 2 + .../sqm/tree/select/SqmSelectStatement.java | 11 +- .../query/sqm/tree/select/SqmSubQuery.java | 125 +- .../sqm/tree/update/SqmUpdateStatement.java | 3 +- .../java/org/hibernate/sql/ast/Clause.java | 4 +- .../sql/ast/SqlAstDeleteTranslator.java | 22 - .../sql/ast/SqlAstInsertTranslator.java | 17 - .../sql/ast/SqlAstSelectTranslator.java | 27 - .../hibernate/sql/ast/SqlAstTranslator.java | 14 +- .../sql/ast/SqlAstTranslatorFactory.java | 21 +- .../sql/ast/SqlAstUpdateTranslator.java | 21 - .../org/hibernate/sql/ast/SqlAstWalker.java | 19 +- .../sql/ast/spi/AbstractSqlAstTranslator.java | 548 ++++- .../sql/ast/spi/AbstractSqlAstWalker.java | 1386 ++++++++++++- .../ast/spi/SimpleFromClauseAccessImpl.java | 13 +- ...va => SqlAstQueryPartProcessingState.java} | 8 +- .../spi/SqlAstToJdbcOperationConverter.java | 26 - .../spi/StandardSqlAstDeleteTranslator.java | 154 -- .../spi/StandardSqlAstInsertTranslator.java | 180 -- .../spi/StandardSqlAstSelectTranslator.java | 112 -- .../sql/ast/spi/StandardSqlAstTranslator.java | 26 + .../spi/StandardSqlAstTranslatorFactory.java | 36 +- .../spi/StandardSqlAstUpdateTranslator.java | 147 -- .../ast/tree/AbstractMutationStatement.java | 47 + .../sql/ast/tree/AbstractStatement.java | 53 + .../sql/ast/tree/MutationStatement.java | 7 + .../org/hibernate/sql/ast/tree/Statement.java | 3 + .../{CteConsumer.java => CteContainer.java} | 15 +- .../sql/ast/tree/cte/CteStatement.java | 72 +- .../hibernate/sql/ast/tree/cte/CteTable.java | 23 +- .../sql/ast/tree/cte/CteTableGroup.java | 3 +- .../tree/cte/SearchClauseSpecification.java | 37 + .../sql/ast/tree/delete/DeleteStatement.java | 40 +- .../sql/ast/tree/expression/Any.java | 8 +- .../ast/tree/expression/ColumnReference.java | 16 + .../sql/ast/tree/expression/Every.java | 8 +- .../ast/tree/from/CorrelatedTableGroup.java | 96 + .../sql/ast/tree/from/TableGroup.java | 10 + .../sql/ast/tree/insert/InsertStatement.java | 37 +- .../ast/tree/predicate/ExistsPredicate.java | 7 +- .../tree/predicate/InSubQueryPredicate.java | 8 +- .../sql/ast/tree/select/QueryGroup.java | 70 + .../sql/ast/tree/select/QueryPart.java | 100 + .../sql/ast/tree/select/QuerySpec.java | 61 +- .../sql/ast/tree/select/SelectStatement.java | 39 +- .../sql/ast/tree/update/UpdateStatement.java | 42 +- .../internal/DelegatingExecutionContext.java | 82 + .../hibernate/sql/exec/spi/JdbcOperation.java | 15 + .../hibernate/sql/exec/spi/JdbcSelect.java | 129 +- .../entity/AbstractEntityInitializer.java | 2 +- .../internal/DeferredResultSetAccess.java | 112 +- .../test}/dialect/DB2390DialectTestCase.java | 5 +- .../test}/dialect/DB2DialectTestCase.java | 3 +- .../SQLServerDialectPaginationTest.java | 2 +- .../functional/SQLServerDialectTest.java | 10 +- .../cache/SQLFunctionsInterSystemsTest.java | 6 +- .../ast/CriteriaEntityGraphTest.java | 10 +- .../entitygraph/ast/HqlEntityGraphTest.java | 9 +- .../criteria/query}/LimitExpressionTest.java | 2 +- .../test/jpa}/query/LimitExpressionTest.java | 9 +- .../test/pagination/DataMetaPoint.java | 2 +- .../{ => orm}/test/pagination/DataPoint.java | 2 +- .../test/pagination/DistinctSelectTest.java | 7 +- .../{ => orm}/test/pagination/Entry.java | 2 +- .../pagination/FetchClausePaginationTest.java | 83 + .../test/pagination/OraclePaginationTest.java | 6 +- .../test/pagination/PaginationTest.java | 23 +- .../pagination/SubqueryPaginationTest.java | 96 + .../{ => orm}/test/pagination/Tag.java | 2 +- .../test/pagination/hhh9965/HHH9965Test.java | 2 +- .../test/pagination/hhh9965/Product.java | 2 +- .../test/pagination/hhh9965/Shop.java | 2 +- .../test/query/hql/AliasCollisionTest.java | 6 +- .../test/query/hql/AttributePathTests.java | 4 +- .../orm/test/query/hql/FromClauseTests.java | 8 +- .../orm/test/query/hql/InsertUpdateTests.java | 2 +- .../orm/test/query/hql/SelectClauseTests.java | 6 +- .../query/sql/NativeQueryParameterTests.java | 6 +- .../sql/NativeQueryResultBuilderTests.java | 6 +- .../test/query/sqm/exec/CrossJoinTest.java | 2 +- .../orm/test/set/SetOperationTest.java | 91 + .../orm/test/sql/ast/SmokeTests.java | 29 +- .../test/sql/results/AbstractResultTests.java | 8 +- .../SQLServerDialectCollationTest.java | 3 +- .../test/legacy/LimitExpressionTest.java | 68 - .../test/type/TimeAndTimestampTest.java | 6 +- .../orm}/test/pagination/DataPoint.hbm.xml | 2 +- .../orm}/test/pagination/EntryTag.hbm.xml | 4 +- .../LimitWithExpreesionAndFetchJoinTest.java | 2 +- .../testing/orm/domain/contacts/Contact.java | 2 + .../testing/orm/domain/helpdesk/Ticket.java | 2 + .../orm/junit/DialectFeatureChecks.java | 58 + 300 files changed, 12093 insertions(+), 4462 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/CteSearchClauseKind.java create mode 100644 hibernate-core/src/main/java/org/hibernate/FetchClauseType.java create mode 100644 hibernate-core/src/main/java/org/hibernate/NullOrdering.java create mode 100644 hibernate-core/src/main/java/org/hibernate/SetOperator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/CUBRIDSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/CacheSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/CockroachSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/DB2SqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/DB2iSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/DB2zSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/FirebirdSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/HANASqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/HSQLSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/InformixSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/IngresSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/MariaDBSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/MaxDBSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/MySQLSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/RDBMSOS2200SqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/SQLServerSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/SpannerSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/SybaseAnywhereSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/SybaseSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/TeradataSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/TimesTenSqlAstTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/pagination/NoopLimitHandler.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/sequence/MariaDBSequenceSupport.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/spi/DelegatingQueryOptions.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/spi/SqlOmittingQueryOptions.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SimpleSqmDeleteTranslator.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SimpleSqmUpdateTranslation.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SimpleSqmUpdateTranslator.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmInsertTranslation.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmInsertTranslator.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmQuerySpecTranslation.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmSelectTranslation.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmSelectTranslator.java rename hibernate-core/src/main/java/org/hibernate/query/sqm/sql/{SimpleSqmDeleteTranslation.java => StandardSqmTranslation.java} (82%) rename hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/{SqlAstQuerySpecProcessingStateImpl.java => SqlAstQueryPartProcessingStateImpl.java} (77%) delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmDeleteTranslator.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmInsertTranslator.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmSelectTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmTranslator.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmUpdateTranslator.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteConsumer.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteContainer.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmSearchClauseSpecification.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmCorrelatedFrom.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryGroup.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryPart.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstDeleteTranslator.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstInsertTranslator.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstSelectTranslator.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstUpdateTranslator.java rename hibernate-core/src/main/java/org/hibernate/sql/ast/spi/{SqlAstQuerySpecProcessingState.java => SqlAstQueryPartProcessingState.java} (67%) delete mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstToJdbcOperationConverter.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstDeleteTranslator.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstInsertTranslator.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstSelectTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstTranslator.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstUpdateTranslator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/tree/AbstractMutationStatement.java create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/tree/AbstractStatement.java rename hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/{CteConsumer.java => CteContainer.java} (57%) create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/SearchClauseSpecification.java create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/CorrelatedTableGroup.java create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryGroup.java create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryPart.java create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/exec/internal/DelegatingExecutionContext.java rename hibernate-core/src/test/java/org/hibernate/{ => orm/test}/dialect/DB2390DialectTestCase.java (95%) rename hibernate-core/src/test/java/org/hibernate/{ => orm/test}/dialect/DB2DialectTestCase.java (96%) rename hibernate-core/src/test/java/org/hibernate/{ => orm}/test/dialect/functional/SQLServerDialectPaginationTest.java (98%) rename hibernate-core/src/test/java/org/hibernate/{ => orm}/test/dialect/functional/SQLServerDialectTest.java (97%) rename hibernate-core/src/test/java/org/hibernate/{ => orm}/test/dialect/functional/cache/SQLFunctionsInterSystemsTest.java (99%) rename hibernate-core/src/test/java/org/hibernate/{jpa/test/criteria/limitExpression => orm/test/jpa/criteria/query}/LimitExpressionTest.java (96%) rename hibernate-core/src/test/java/org/hibernate/{jpa/test => orm/test/jpa}/query/LimitExpressionTest.java (84%) rename hibernate-core/src/test/java/org/hibernate/{ => orm}/test/pagination/DataMetaPoint.java (97%) rename hibernate-core/src/test/java/org/hibernate/{ => orm}/test/pagination/DataPoint.java (98%) mode change 100755 => 100644 rename hibernate-core/src/test/java/org/hibernate/{ => orm}/test/pagination/DistinctSelectTest.java (93%) rename hibernate-core/src/test/java/org/hibernate/{ => orm}/test/pagination/Entry.java (96%) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/pagination/FetchClausePaginationTest.java rename hibernate-core/src/test/java/org/hibernate/{ => orm}/test/pagination/OraclePaginationTest.java (97%) rename hibernate-core/src/test/java/org/hibernate/{ => orm}/test/pagination/PaginationTest.java (90%) mode change 100755 => 100644 create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/pagination/SubqueryPaginationTest.java rename hibernate-core/src/test/java/org/hibernate/{ => orm}/test/pagination/Tag.java (93%) rename hibernate-core/src/test/java/org/hibernate/{ => orm}/test/pagination/hhh9965/HHH9965Test.java (96%) rename hibernate-core/src/test/java/org/hibernate/{ => orm}/test/pagination/hhh9965/Product.java (92%) rename hibernate-core/src/test/java/org/hibernate/{ => orm}/test/pagination/hhh9965/Shop.java (93%) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/set/SetOperationTest.java delete mode 100644 hibernate-core/src/test/java/org/hibernate/test/legacy/LimitExpressionTest.java rename hibernate-core/src/test/{java/org/hibernate => resources/org/hibernate/orm}/test/pagination/DataPoint.hbm.xml (96%) mode change 100755 => 100644 rename hibernate-core/src/test/{java/org/hibernate => resources/org/hibernate/orm}/test/pagination/EntryTag.hbm.xml (84%) mode change 100755 => 100644 diff --git a/ci/build.sh b/ci/build.sh index 7da99bd1af..5a72ba2a45 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -4,15 +4,15 @@ goal= if [ "$RDBMS" == "derby" ]; then goal="-Pdb=derby" elif [ "$RDBMS" == "mariadb" ]; then - goal="-Pdb=mariadb" + goal="-Pdb=mariadb_ci" elif [ "$RDBMS" == "postgresql" ]; then - goal="-Pdb=pgsql" + goal="-Pdb=pgsql_ci" elif [ "$RDBMS" == "oracle" ]; then - goal="-Pdb=oracle -Dhibernate.connection.url=jdbc:oracle:thin:@localhost:1521:XE -Dhibernate.connection.username=SYSTEM -Dhibernate.connection.password=Oracle18" + goal="-Pdb=oracle_ci" elif [ "$RDBMS" == "db2" ]; then - goal="-Pdb=db2 -Dhibernate.connection.url=jdbc:db2://localhost:50000/orm_test -Dhibernate.connection.username=orm_test -Dhibernate.connection.password=orm_test" + goal="-Pdb=db2_ci" elif [ "$RDBMS" == "mssql" ]; then - goal="-Pdb=mssql -Dhibernate.connection.url=jdbc:sqlserver://localhost:1433;databaseName= -Dhibernate.connection.username=sa -Dhibernate.connection.password=Hibernate_orm_test" + goal="-Pdb=mssql_ci" fi exec ./gradlew check ${goal} -Plog-test-progress=true --stacktrace \ No newline at end of file diff --git a/docker_db.sh b/docker_db.sh index 72fa0f4adc..2ab4d92e4c 100755 --- a/docker_db.sh +++ b/docker_db.sh @@ -12,7 +12,7 @@ mysql_8_0() { mariadb() { docker rm -f mariadb || true - docker run --name mariadb -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ALLOW_EMPTY_PASSWORD=true -p3306:3306 -d mariadb:10.5.8 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + docker run --name mariadb -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d mariadb:10.5.8 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci } postgresql_9_5() { @@ -20,12 +20,17 @@ postgresql_9_5() { docker run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 -d postgres:9.5 } +postgresql_13() { + docker rm -f postgres || true + docker run --name postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -p5432:5432 -d postgres:13.0 +} + db2() { docker rm -f db2 || true - docker run --name db2 --privileged -e DB2INSTANCE=orm_test -e DB2INST1_PASSWORD=orm_test -e DBNAME=orm_test -e LICENSE=accept -p 50000:50000 -d ibmcom/db2:11.5.0.0a + docker run --name db2 --privileged -e DB2INSTANCE=orm_test -e DB2INST1_PASSWORD=orm_test -e DBNAME=orm_test -e LICENSE=accept -e AUTOCONFIG=false -e ARCHIVE_LOGS=false -e TO_CREATE_SAMPLEDB=false -e REPODB=false -p 50000:50000 -d ibmcom/db2:11.5.5.0 # Give the container some time to start OUTPUT= - while [[ $OUTPUT != *"Setup has completed"* ]]; do + while [[ $OUTPUT != *"INSTANCE"* ]]; do echo "Waiting for DB2 to start..." sleep 10 OUTPUT=$(docker logs db2) @@ -36,6 +41,22 @@ db2() { mssql() { docker rm -f mssql || true docker run --name mssql -d -p 1433:1433 -e "SA_PASSWORD=Hibernate_orm_test" -e ACCEPT_EULA=Y microsoft/mssql-server-linux:2017-CU13 + sleep 5 + n=0 + until [ "$n" -ge 5 ] + do + # We need a database that uses a non-lock based MVCC approach + # https://github.com/microsoft/homebrew-mssql-release/issues/2#issuecomment-682285561 + docker exec mssql bash -c 'echo "create database hibernate_orm_test collate SQL_Latin1_General_CP1_CI_AS; alter database hibernate_orm_test set READ_COMMITTED_SNAPSHOT ON" | /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Hibernate_orm_test -i /dev/stdin' && break + echo "Waiting for SQL Server to start..." + n=$((n+1)) + sleep 5 + done + if [ "$n" -ge 5 ]; then + echo "SQL Server failed to start and configure after 25 seconds" + else + echo "SQL Server successfully started" + fi } oracle() { @@ -52,6 +73,7 @@ if [ -z ${1} ]; then echo -e "\tmysql_8_0" echo -e "\tmariadb" echo -e "\tpostgresql_9_5" + echo -e "\tpostgresql_13" echo -e "\tdb2" echo -e "\tmssql" echo -e "\toracle" diff --git a/gradle/databases.gradle b/gradle/databases.gradle index 485808a979..c95e113e75 100644 --- a/gradle/databases.gradle +++ b/gradle/databases.gradle @@ -10,6 +10,7 @@ import org.apache.tools.ant.filters.ReplaceTokens // build a map of the database settings to use. ext { db = project.hasProperty('db') ? project.getProperty('db') : 'h2' + def dbHost = System.getProperty( 'dbHost', 'localhost' ) dbBundle = [ h2 : [ 'db.dialect' : 'org.hibernate.dialect.H2Dialect', @@ -26,47 +27,61 @@ ext { 'jdbc.url' : 'jdbc:hsqldb:mem:test' ], derby : [ - 'db.dialect' : 'org.hibernate.dialect.DerbyTenSevenDialect', + 'db.dialect' : 'org.hibernate.dialect.DerbyDialect', 'jdbc.driver': 'org.apache.derby.jdbc.EmbeddedDriver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', 'jdbc.url' : 'jdbc:derby:target/tmp/derby/hibernate_orm_test;databaseName=hibernate_orm_test;create=true' ], pgsql : [ - 'db.dialect' : 'org.hibernate.dialect.PostgreSQL95Dialect', + 'db.dialect' : 'org.hibernate.dialect.PostgreSQLDialect', 'jdbc.driver': 'org.postgresql.Driver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', 'jdbc.url' : 'jdbc:postgresql:hibernate_orm_test' ], pgsql_docker : [ - 'db.dialect' : 'org.hibernate.dialect.PostgreSQL95Dialect', + 'db.dialect' : 'org.hibernate.dialect.PostgreSQLDialect', 'jdbc.driver': 'org.postgresql.Driver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', 'jdbc.url' : 'jdbc:postgresql://127.0.0.1/hibernate_orm_test' ], + pgsql_ci : [ + 'db.dialect' : 'org.hibernate.dialect.PostgreSQLDialect', + 'jdbc.driver': 'org.postgresql.Driver', + 'jdbc.user' : 'hibernate_orm_test', + 'jdbc.pass' : 'hibernate_orm_test', + 'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test' + ], mysql : [ - 'db.dialect' : 'org.hibernate.dialect.MySQL57Dialect', + 'db.dialect' : 'org.hibernate.dialect.MySQLDialect', 'jdbc.driver': 'com.mysql.jdbc.Driver', 'jdbc.user' : 'hibernateormtest', 'jdbc.pass' : 'hibernateormtest', 'jdbc.url' : 'jdbc:mysql://localhost/hibernate_orm_test' ], mysql_docker : [ - 'db.dialect' : 'org.hibernate.dialect.MySQL57Dialect', + 'db.dialect' : 'org.hibernate.dialect.MySQLDialect', 'jdbc.driver': 'com.mysql.jdbc.Driver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', 'jdbc.url' : 'jdbc:mysql://127.0.0.1/hibernate_orm_test?useSSL=false' ], mariadb : [ - 'db.dialect' : 'org.hibernate.dialect.MariaDB103Dialect', + 'db.dialect' : 'org.hibernate.dialect.MariaDBDialect', 'jdbc.driver': 'org.mariadb.jdbc.Driver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', 'jdbc.url' : 'jdbc:mariadb://127.0.0.1/hibernate_orm_test' ], + mariadb_ci : [ + 'db.dialect' : 'org.hibernate.dialect.MariaDBDialect', + 'jdbc.driver': 'org.mariadb.jdbc.Driver', + 'jdbc.user' : 'root', + 'jdbc.pass' : 'hibernate_orm_test', + 'jdbc.url' : 'jdbc:mariadb://' + dbHost + '/hibernate_orm_test' + ], postgis : [ 'db.dialect' : 'org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect', 'jdbc.driver': 'org.postgresql.Driver', @@ -75,7 +90,7 @@ ext { 'jdbc.url' : 'jdbc:postgresql:hibernate_orm_test' ], oracle : [ - 'db.dialect' : 'org.hibernate.dialect.Oracle10gDialect', + 'db.dialect' : 'org.hibernate.dialect.OracleDialect', 'jdbc.driver': 'oracle.jdbc.OracleDriver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', @@ -85,19 +100,33 @@ ext { // After registering to get access (see instructions at above link), start it for testing with: // docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name ORCLCDB -p 1521:1521 store/oracle/database-enterprise:12.2.0.1-slim oracle_docker : [ - 'db.dialect' : 'org.hibernate.dialect.Oracle12cDialect', + 'db.dialect' : 'org.hibernate.dialect.OracleDialect', 'jdbc.driver': 'oracle.jdbc.OracleDriver', 'jdbc.user' : 'sys as sysdba', 'jdbc.pass' : 'Oradoc_db1', 'jdbc.url' : 'jdbc:oracle:thin:@localhost:1521:ORCLCDB' ], + oracle_ci : [ + 'db.dialect' : 'org.hibernate.dialect.OracleDialect', + 'jdbc.driver': 'oracle.jdbc.OracleDriver', + 'jdbc.user' : 'SYSTEM', + 'jdbc.pass' : 'Oracle18', + 'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521:XE' + ], mssql : [ - 'db.dialect' : 'org.hibernate.dialect.SQLServer2012Dialect', + 'db.dialect' : 'org.hibernate.dialect.SQLServerDialect', 'jdbc.driver': 'com.microsoft.sqlserver.jdbc.SQLServerDriver', 'jdbc.user' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test', 'jdbc.url' : 'jdbc:sqlserver://localhost;instance=SQLEXPRESS;databaseName=hibernate_orm_test' ], + mssql_ci : [ + 'db.dialect' : 'org.hibernate.dialect.SQLServerDialect', + 'jdbc.driver': 'com.microsoft.sqlserver.jdbc.SQLServerDriver', + 'jdbc.user' : 'sa', + 'jdbc.pass' : 'Hibernate_orm_test', + 'jdbc.url' : 'jdbc:sqlserver://' + dbHost + ';databaseName=hibernate_orm_test' + ], informix : [ 'db.dialect' : 'org.hibernate.dialect.InformixDialect', 'jdbc.driver': 'com.informix.jdbc.IfxDriver', @@ -112,6 +141,13 @@ ext { 'jdbc.pass' : 'db2inst1-pwd', 'jdbc.url' : 'jdbc:db2://127.0.0.1:50000/hibern8' ], + db2_ci : [ + 'db.dialect' : 'org.hibernate.dialect.DB2Dialect', + 'jdbc.driver': 'com.ibm.db2.jcc.DB2Driver', + 'jdbc.user' : 'orm_test', + 'jdbc.pass' : 'orm_test', + 'jdbc.url' : 'jdbc:db2://' + dbHost + ':50000/orm_test' + ], hana : [ 'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect', 'jdbc.driver': 'com.sap.db.jdbc.Driver', @@ -134,7 +170,7 @@ ext { 'jdbc.url' : 'jdbc:sap://localhost:39015/' ], cockroachdb : [ - 'db.dialect' : 'org.hibernate.dialect.CockroachDB192Dialect', + 'db.dialect' : 'org.hibernate.dialect.CockroachDialect', // CockroachDB uses the same pgwire protocol as PostgreSQL, so the driver is the same. 'jdbc.driver': 'org.postgresql.Driver', 'jdbc.user' : 'root', diff --git a/gradle/java-module.gradle b/gradle/java-module.gradle index b66cb7104c..7a3d0ad7be 100644 --- a/gradle/java-module.gradle +++ b/gradle/java-module.gradle @@ -111,10 +111,10 @@ dependencies { testRuntime( libraries.hana ) testRuntime( libraries.cockroachdb ) - if ( db.equalsIgnoreCase( 'oracle' ) || db.equalsIgnoreCase( 'oracle_docker' ) ) { + if ( db.startsWith( 'oracle' ) ) { testRuntime( libraries.oracle ) } - else if ( db.equalsIgnoreCase( 'db2' ) ) { + else if ( db.startsWith( 'db2' ) ) { testRuntime( libraries.db2 ) } else if ( db.equalsIgnoreCase( 'hana' ) ) { 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 5ce71985bc..5a62f1a83d 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 @@ -121,7 +121,7 @@ PLUS : '+'; MINUS : '-'; ASTERISK : '*'; SLASH : '/'; -PERCENT : '%'; +PERCENT_OP : '%'; AMPERSAND : '&'; SEMICOLON : ';'; COLON : ':'; @@ -176,6 +176,7 @@ END : [eE] [nN] [dD]; ENTRY : [eE] [nN] [tT] [rR] [yY]; ESCAPE : [eE] [sS] [cC] [aA] [pP] [eE]; EVERY : [eE] [vV] [eE] [rR] [yY]; +EXCEPT : [eE] [xX] [cC] [eE] [pP] [tT]; EXISTS : [eE] [xX] [iI] [sS] [tT] [sS]; EXP : [eE] [xX] [pP]; EXTRACT : [eE] [xX] [tT] [rR] [aA] [cC] [tT]; @@ -197,6 +198,7 @@ INDEX : [iI] [nN] [dD] [eE] [xX]; INNER : [iI] [nN] [nN] [eE] [rR]; INSERT : [iI] [nN] [sS] [eE] [rR] [tT]; INSTANT : [iI] [nN] [sS] [tT] [aA] [nN] [tT]; +INTERSECT : [iI] [nN] [tT] [eE] [rR] [sS] [eE] [cC] [tT]; INTO : [iI] [nN] [tT] [oO]; IS : [iI] [sS]; JOIN : [jJ] [oO] [iI] [nN]; @@ -230,6 +232,7 @@ MINUTE : [mM] [iI] [nN] [uU] [tT] [eE]; MOD : [mM] [oO] [dD]; MONTH : [mM] [oO] [nN] [tT] [hH]; NANOSECOND : [nN] [aA] [nN] [oO] [sS] [eE] [cC] [oO] [nN] [dD]; +NEXT : [nN] [eE] [xX] [tT]; NEW : [nN] [eE] [wW]; NOT : [nN] [oO] [tT]; NULLIF : [nN] [uU] [lL] [lL] [iI] [fF]; @@ -239,11 +242,13 @@ OF : [oO] [fF]; OFFSET : [oO] [fF] [fF] [sS] [eE] [tT]; OFFSET_DATETIME : [oO] [fF] [fF] [sS] [eE] [tT] '_' [dD] [aA] [tT] [eE] [tT] [iI] [mM] [eE]; ON : [oO] [nN]; +ONLY : [oO] [nN] [lL] [yY]; OR : [oO] [rR]; ORDER : [oO] [rR] [dD] [eE] [rR]; OUTER : [oO] [uU] [tT] [eE] [rR]; OVERLAY : [oO] [vV] [eE] [rR] [lL] [aA] [yY]; PAD : [pP] [aA] [dD]; +PERCENT : [pP] [eE] [rR] [cC] [eE] [nN] [tT]; PLACING : [pP] [lL] [aA] [cC] [iI] [nN] [gG]; POSITION : [pP] [oO] [sS] [iI] [tT] [iI] [oO] [nN]; POWER : [pP] [oO] [wW] [eE] [rR]; @@ -252,6 +257,8 @@ REPLACE : [rR] [eE] [pP] [lL] [aA] [cC] [eE]; RIGHT : [rR] [iI] [gG] [hH] [tT]; ROLLUP : [rR] [oO] [lL] [lL] [uU] [pP]; ROUND : [rR] [oO] [uU] [nN] [dD]; +ROWS : [rR] [oO] [wW] [sS]; +ROW : [rR] [oO] [wW]; SECOND : [sS] [eE] [cC] [oO] [nN] [dD]; SELECT : [sS] [eE] [lL] [eE] [cC] [tT]; SET : [sS] [eE] [tT]; @@ -263,6 +270,7 @@ STR : [sS] [tT] [rR]; SUBSTRING : [sS] [uU] [bB] [sS] [tT] [rR] [iI] [nN] [gG]; SUM : [sS] [uU] [mM]; THEN : [tT] [hH] [eE] [nN]; +TIES : [tT] [iI] [eE] [sS]; TIME : [tT] [iI] [mM] [eE]; TIMESTAMP : [tT] [iI] [mM] [eE] [sS] [tT] [aA] [mM] [pP]; TIMEZONE_HOUR : [tT] [iI] [mM] [eE] [zZ] [oO] [nN] [eE] '_' [hH] [oO] [uU] [rR]; @@ -271,6 +279,7 @@ TRAILING : [tT] [rR] [aA] [iI] [lL] [iI] [nN] [gG]; TREAT : [tT] [rR] [eE] [aA] [tT]; TRIM : [tT] [rR] [iI] [mM]; TYPE : [tT] [yY] [pP] [eE]; +UNION : [uU] [nN] [iI] [oO] [nN]; UPDATE : [uU] [pP] [dD] [aA] [tT] [eE]; UPPER : [uU] [pP] [pP] [eE] [rR]; VALUE : [vV] [aA] [lL] [uU] [eE]; 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 aef5e7f86a..5d31292959 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 @@ -28,11 +28,11 @@ statement ; selectStatement - : querySpec + : queryExpression ; subQuery - : querySpec + : queryExpression ; dmlTarget @@ -56,7 +56,7 @@ assignment ; insertStatement - : INSERT INTO? dmlTarget targetFieldsSpec (querySpec | valuesList) + : INSERT INTO? dmlTarget targetFieldsSpec (queryExpression | valuesList) ; targetFieldsSpec @@ -74,9 +74,30 @@ values // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // QUERY SPEC - general structure of root sqm or sub sqm +queryExpression + : queryGroup queryOrder? + ; + +queryGroup + : querySpec # QuerySpecQueryGroup + | LEFT_PAREN queryGroup RIGHT_PAREN # NestedQueryGroup + | queryGroup queryOrder? (setOperator (querySpec | LEFT_PAREN queryGroup RIGHT_PAREN))+ # SetQueryGroup + ; + +setOperator + : UNION ALL? + | INTERSECT ALL? + | EXCEPT ALL? + ; + +queryOrder + : orderByClause limitClause? offsetClause? fetchClause? + ; + querySpec - : selectClause fromClause? whereClause? ( groupByClause havingClause? )? orderByClause? limitClause? offsetClause? - | fromClause whereClause? ( groupByClause havingClause? )? selectClause? orderByClause? limitClause? offsetClause? +// TODO: add with clause + : selectClause fromClause? whereClause? ( groupByClause havingClause? )? + | fromClause whereClause? ( groupByClause havingClause? )? selectClause? ; @@ -340,16 +361,27 @@ orderingSpecification // LIMIT/OFFSET clause limitClause - : LIMIT parameterOrNumberLiteral + : LIMIT parameterOrIntegerLiteral ; offsetClause - : OFFSET parameterOrNumberLiteral + : OFFSET parameterOrIntegerLiteral (ROW | ROWS)? + ; + +fetchClause + : FETCH (FIRST | NEXT) (parameterOrIntegerLiteral | parameterOrNumberLiteral PERCENT) (ROW | ROWS) (ONLY | WITH TIES) + ; + +parameterOrIntegerLiteral + : parameter + | INTEGER_LITERAL ; parameterOrNumberLiteral : parameter | INTEGER_LITERAL + | FLOAT_LITERAL + | DOUBLE_LITERAL ; @@ -427,7 +459,7 @@ primaryExpression multiplicativeOperator : SLASH - | PERCENT + | PERCENT_OP | ASTERISK ; diff --git a/hibernate-core/src/main/java/org/hibernate/CteSearchClauseKind.java b/hibernate-core/src/main/java/org/hibernate/CteSearchClauseKind.java new file mode 100644 index 0000000000..c1cc137c3f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/CteSearchClauseKind.java @@ -0,0 +1,23 @@ +/* + * 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; + +/** + * The kind of CTE search clause. + * + * @author Christian Beikov + */ +public enum CteSearchClauseKind { + /** + * Use depth first for a recursive CTE. + */ + DEPTH_FIRST, + /** + * Use breadth first for a recursive CTE. + */ + BREADTH_FIRST; +} diff --git a/hibernate-core/src/main/java/org/hibernate/FetchClauseType.java b/hibernate-core/src/main/java/org/hibernate/FetchClauseType.java new file mode 100644 index 0000000000..8f71d1bd0e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/FetchClauseType.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** + * The kind of fetch to use for the FETCH clause. + * + * @author Christian Beikov + */ +public enum FetchClauseType { + /** + * Exact row count like for LIMIT clause or FETCH FIRST x ROWS ONLY. + */ + ROWS_ONLY, + /** + * Also fetches ties if the last value is not unique FETCH FIRST x ROWS WITH TIES. + */ + ROWS_WITH_TIES, + /** + * Row count in percent FETCH FIRST x PERCENT ROWS ONLY. + */ + PERCENT_ONLY, + /** + * Also fetches ties if the last value is not unique FETCH FIRST x PERCENT ROWS WITH TIES. + */ + PERCENT_WITH_TIES; +} diff --git a/hibernate-core/src/main/java/org/hibernate/LockOptions.java b/hibernate-core/src/main/java/org/hibernate/LockOptions.java index 68023426b2..b77774d368 100644 --- a/hibernate-core/src/main/java/org/hibernate/LockOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/LockOptions.java @@ -75,6 +75,14 @@ public class LockOptions implements Serializable { this.lockMode = lockMode; } + /** + * Returns whether the lock options are empty. + * + * @return If the lock options is equivalent to {@link LockOptions#NONE}. + */ + public boolean isEmpty() { + return lockMode == LockMode.NONE && timeout == WAIT_FOREVER && followOnLocking == null && !scope && !hasAliasSpecificLockModes(); + } /** * Retrieve the overall lock mode in effect for this set of options. @@ -329,4 +337,30 @@ public class LockOptions implements Serializable { destination.setFollowOnLocking( source.getFollowOnLocking() ); return destination; } + + public boolean isCompatible(LockOptions that) { + if ( that == null ) { + return isEmpty(); + } + else if ( this == that ) { + return true; + } + + if ( timeout != that.timeout ) { + return false; + } + if ( scope != that.scope ) { + return false; + } + if ( lockMode != that.lockMode ) { + return false; + } + if ( aliasSpecificLockModes != null ? + !aliasSpecificLockModes.equals( that.aliasSpecificLockModes ) : + that.aliasSpecificLockModes != null ) { + return false; + } + return followOnLocking != null ? followOnLocking.equals( that.followOnLocking ) : that.followOnLocking == null; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/NullOrdering.java b/hibernate-core/src/main/java/org/hibernate/NullOrdering.java new file mode 100644 index 0000000000..3b99c9d46f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/NullOrdering.java @@ -0,0 +1,34 @@ +/* + * 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; + +/** + * The order of null. + * + * @author Christian Beikov + */ +public enum NullOrdering { + /** + * Null is treated as the smallest value. + */ + SMALLEST, + + /** + * Null is treated as the greatest value. + */ + GREATEST, + + /** + * Null is always ordered first. + */ + FIRST, + + /** + * Null is always ordered last. + */ + LAST; +} diff --git a/hibernate-core/src/main/java/org/hibernate/NullPrecedence.java b/hibernate-core/src/main/java/org/hibernate/NullPrecedence.java index 4d9d60590a..d4d14a5806 100644 --- a/hibernate-core/src/main/java/org/hibernate/NullPrecedence.java +++ b/hibernate-core/src/main/java/org/hibernate/NullPrecedence.java @@ -15,17 +15,53 @@ public enum NullPrecedence { /** * Null precedence not specified. Relies on the RDBMS implementation. */ - NONE, + NONE { + @Override + public boolean isDefaultOrdering(SortOrder sortOrder, NullOrdering nullOrdering) { + return true; + } + }, /** * Null values appear at the beginning of the sorted collection. */ - FIRST, + FIRST { + @Override + public boolean isDefaultOrdering(SortOrder sortOrder, NullOrdering nullOrdering) { + switch ( nullOrdering ) { + case FIRST: + return true; + case SMALLEST: + return sortOrder == SortOrder.ASCENDING; + case GREATEST: + return sortOrder == SortOrder.DESCENDING; + } + return false; + } + }, /** * Null values appear at the end of the sorted collection. */ - LAST; + LAST { + @Override + public boolean isDefaultOrdering(SortOrder sortOrder, NullOrdering nullOrdering) { + switch ( nullOrdering ) { + case LAST: + return true; + case SMALLEST: + return sortOrder == SortOrder.DESCENDING; + case GREATEST: + return sortOrder == SortOrder.ASCENDING; + } + return false; + } + }; + + /** + * Is this null precedence the default for the given sort order and null ordering. + */ + public abstract boolean isDefaultOrdering(SortOrder sortOrder, NullOrdering nullOrdering); /** * Interprets a string representation of a NullPrecedence, returning {@code null} by default. For diff --git a/hibernate-core/src/main/java/org/hibernate/SetOperator.java b/hibernate-core/src/main/java/org/hibernate/SetOperator.java new file mode 100644 index 0000000000..188a3bb641 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/SetOperator.java @@ -0,0 +1,49 @@ +/* + * 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; + +/** + * The SQL set operators. + * + * @author Christian Beikov + */ +public enum SetOperator { + /** + * Union of sets that removes duplicate rows. + */ + UNION("union"), + /** + * Union of bags that retains all elements. + */ + UNION_ALL("union all"), + /** + * Intersection of sets that removes duplicate rows. + */ + INTERSECT("intersect"), + /** + * Intersection of bags that retains duplicate matches. + */ + INTERSECT_ALL("intersect all"), + /** + * Exclusion of set elements of the set on the right-hand side. + */ + EXCEPT("except"), + /** + * Exclusion of bag elements of the bag on the right-hand side that retains duplicates. + */ + EXCEPT_ALL("except all"); + + private final String sqlString; + + private SetOperator(String sqlString) { + this.sqlString = sqlString; + } + + public String sqlString() { + return sqlString; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java index 274c9bd6e8..1f6b07971b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java @@ -25,6 +25,7 @@ import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jdbc.*; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.engine.jdbc.env.spi.*; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.LockAcquisitionException; import org.hibernate.exception.LockTimeoutException; @@ -39,6 +40,10 @@ import org.hibernate.procedure.spi.CallableStatementSupport; import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; import org.hibernate.service.ServiceRegistry; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorHANADatabaseImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.tool.schema.internal.StandardTableExporter; @@ -821,6 +826,17 @@ public abstract class AbstractHANADialect extends Dialect { CommonFunctionFactory.currentUtcdatetimetimestamp( queryEngine ); } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, org.hibernate.sql.ast.tree.Statement statement) { + return new HANASqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + /** * HANA has no extract() function, but we can emulate * it using the appropriate named functions instead of @@ -1158,11 +1174,6 @@ public abstract class AbstractHANADialect extends Dialect { return true; } - @Override - public boolean supportsUnionAll() { - return true; - } - @Override public boolean dropConstraints() { return false; 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 8463e822ac..ab40d93b1b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java @@ -8,6 +8,7 @@ package org.hibernate.dialect; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.NullOrdering; import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.cfg.Environment; import org.hibernate.dialect.function.CommonFunctionFactory; @@ -223,6 +224,11 @@ abstract class AbstractTransactSQLDialect extends Dialect { return GroupByConstantRenderingStrategy.COLUMN_REFERENCE; } + @Override + public NullOrdering getNullOrdering() { + return NullOrdering.SMALLEST; + } + @Override public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType entityDescriptor, @@ -254,11 +260,6 @@ abstract class AbstractTransactSQLDialect extends Dialect { return false; } - @Override - public boolean supportsUnionAll() { - return true; - } - @Override public boolean supportsExistsInSelect() { return false; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CUBRIDDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/CUBRIDDialect.java index 5586084254..1ef0aefafc 100755 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CUBRIDDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CUBRIDDialect.java @@ -16,9 +16,15 @@ import org.hibernate.dialect.pagination.LimitLimitHandler; import org.hibernate.dialect.sequence.CUBRIDSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.engine.jdbc.Size; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.SemanticException; import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorCUBRIDDatabaseImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; @@ -238,11 +244,6 @@ public class CUBRIDDialect extends Dialect { return ""; } - @Override - public boolean supportsUnionAll() { - return true; - } - @Override public boolean supportsCurrentTimestampSelection() { return true; @@ -273,6 +274,17 @@ public class CUBRIDDialect extends Dialect { return false; } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new CUBRIDSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + @Override public LimitHandler getLimitHandler() { return LimitLimitHandler.INSTANCE; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CUBRIDSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/CUBRIDSqlAstTranslator.java new file mode 100644 index 0000000000..9fdce87239 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CUBRIDSqlAstTranslator.java @@ -0,0 +1,41 @@ +/* + * 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; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for CUBRID. + * + * @author Christian Beikov + */ +public class CUBRIDSqlAstTranslator extends AbstractSqlAstTranslator { + + public CUBRIDSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + renderCombinedLimitClause( queryPart ); + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // CUBRID does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // CUBRID does not support this, but it can be emulated + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CacheDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/CacheDialect.java index 13104fef24..498df4bc7e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CacheDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CacheDialect.java @@ -16,6 +16,7 @@ import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.TopLimitHandler; import org.hibernate.dialect.sequence.CacheSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.DataException; import org.hibernate.exception.spi.SQLExceptionConversionDelegate; @@ -27,6 +28,11 @@ import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; import org.hibernate.sql.CacheJoinFragment; import org.hibernate.sql.JoinFragment; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.type.StandardBasicTypes; import java.sql.CallableStatement; @@ -277,6 +283,17 @@ public class CacheDialect extends Dialect { } } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new CacheSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + // LIMIT support (ala TOP) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CacheSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/CacheSqlAstTranslator.java new file mode 100644 index 0000000000..a47c4e3920 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CacheSqlAstTranslator.java @@ -0,0 +1,72 @@ +/* + * 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; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for Cache. + * + * @author Christian Beikov + */ +public class CacheSqlAstTranslator extends AbstractSqlAstTranslator { + + public CacheSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + protected boolean needsRowsToSkip() { + return true; + } + + @Override + protected void visitSqlSelections(SelectClause selectClause) { + renderTopClause( (QuerySpec) getQueryPartStack().getCurrent(), true ); + super.visitSqlSelections( selectClause ); + } + + @Override + protected void renderTopClause(QuerySpec querySpec, boolean addOffset) { + assertRowsOnlyFetchClauseType( querySpec ); + super.renderTopClause( querySpec, addOffset ); + } + + @Override + protected void renderFetchPlusOffsetExpression( + Expression fetchClauseExpression, + Expression offsetClauseExpression, + int offset) { + renderFetchPlusOffsetExpressionAsSingleParameter( fetchClauseExpression, offsetClauseExpression, offset ); + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + // Cache only supports the TOP clause + if ( !queryPart.isRoot() && queryPart.getOffsetClauseExpression() != null ) { + throw new IllegalArgumentException( "Can't emulate offset clause in subquery" ); + } + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // Cache does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // Cache does not support this, but it can be emulated + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java index 60710ba0f0..d26b5bc44a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java @@ -12,8 +12,14 @@ import org.hibernate.dialect.pagination.OffsetFetchLimitHandler; import org.hibernate.dialect.sequence.PostgreSQLSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.type.StandardBasicTypes; import java.sql.Types; @@ -169,11 +175,6 @@ public class CockroachDialect extends Dialect { return true; } - @Override - public boolean supportsUnionAll() { - return true; - } - @Override public String getNoColumnsInsertString() { return "default values"; @@ -209,6 +210,17 @@ public class CockroachDialect extends Dialect { return "select sequence_name, sequence_schema, sequence_catalog, start_value, minimum_value, maximum_value, increment from information_schema.sequences"; } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new CockroachSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + @Override public boolean supportsNationalizedTypes() { return false; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachSqlAstTranslator.java new file mode 100644 index 0000000000..3be79b6138 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachSqlAstTranslator.java @@ -0,0 +1,72 @@ +/* + * 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; + +import org.hibernate.FetchClauseType; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.select.QueryGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for Cockroach. + * + * @author Christian Beikov + */ +public class CockroachSqlAstTranslator extends AbstractSqlAstTranslator { + + public CockroachSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + protected boolean shouldEmulateFetchClause(QueryPart queryPart) { + // Check if current query part is already row numbering to avoid infinite recursion + return useOffsetFetchClause( queryPart ) && getQueryPartForRowNumbering() != queryPart + && !isRowsOnlyFetchClauseType( queryPart ); + } + + @Override + public void visitQueryGroup(QueryGroup queryGroup) { + if ( shouldEmulateFetchClause( queryGroup ) ) { + emulateFetchOffsetWithWindowFunctions( queryGroup, true ); + } + else { + super.visitQueryGroup( queryGroup ); + } + } + + @Override + public void visitQuerySpec(QuerySpec querySpec) { + if ( shouldEmulateFetchClause( querySpec ) ) { + emulateFetchOffsetWithWindowFunctions( querySpec, true ); + } + else { + super.visitQuerySpec( querySpec ); + } + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + if ( !isRowNumberingCurrentQueryPart() ) { + renderLimitOffsetClause( queryPart ); + } + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // Cockroach does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // Cockroach does not support this, but it can be emulated + } +} 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 6fb8cf3808..d3eb763e57 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -22,6 +22,7 @@ import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.dialect.unique.DB2UniqueDelegate; import org.hibernate.dialect.unique.UniqueDelegate; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.LockTimeoutException; import org.hibernate.exception.spi.SQLExceptionConversionDelegate; import org.hibernate.internal.util.JdbcExceptionHelper; @@ -30,11 +31,13 @@ import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.CastType; import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; -import org.hibernate.query.sqm.mutation.internal.idtable.AfterUseAction; -import org.hibernate.query.sqm.mutation.internal.idtable.GlobalTemporaryTableStrategy; -import org.hibernate.query.sqm.mutation.internal.idtable.IdTable; -import org.hibernate.query.sqm.mutation.internal.idtable.TempIdTableExporter; +import org.hibernate.query.sqm.mutation.internal.cte.CteStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorDB2DatabaseImpl; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; @@ -92,7 +95,7 @@ public class DB2Dialect extends Dialect { // registerColumnType( Types.DECIMAL, "decimal($p,$s)" ); registerColumnType( Types.NUMERIC, "decimal($p,$s)" ); - if ( getVersion()<1100 ) { + if ( getVersion() < 1100 ) { registerColumnType( Types.BINARY, "varchar($l) for bit data" ); //should use 'binary' since version 11 registerColumnType( Types.BINARY, 254, "char($l) for bit data" ); //should use 'binary' since version 11 registerColumnType( Types.VARBINARY, "varchar($l) for bit data" ); //should use 'varbinary' since version 11 @@ -407,11 +410,6 @@ public class DB2Dialect extends Dialect { return "nullif(" + literal + ", " + literal + ')'; } - @Override - public boolean supportsUnionAll() { - return true; - } - @Override public int registerResultSetOutParameter(CallableStatement statement, int col) throws SQLException { return col; @@ -437,47 +435,7 @@ public class DB2Dialect extends Dialect { public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - - if ( getVersion() >= 970 ) { - // Starting in DB2 9.7, "real" global temporary tables that can be shared between sessions - // are supported; (obviously) data is not shared between sessions. - return new GlobalTemporaryTableStrategy( - new IdTable( rootEntityDescriptor, name -> "HT_" + name, this ), - () -> new TempIdTableExporter( false, this::getTypeName ) { - @Override - protected String getCreateOptions() { - return "not logged"; - } - }, - AfterUseAction.CLEAN, - runtimeModelCreationContext.getSessionFactory() - ); - } - - return super.getFallbackSqmMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); -// // Prior to DB2 9.7, "real" global temporary tables that can be shared between sessions -// // are *not* supported; even though the DB2 command says to declare a "global" temp table -// // Hibernate treats it as a "local" temp table. -// return new LocalTemporaryTableBulkIdStrategy( -// new IdTableSupportStandardImpl() { -// @Override -// public String generateIdTableName(String baseName) { -// return "session." + super.generateIdTableName( baseName ); -// } -// -// @Override -// public String getCreateIdTableCommand() { -// return "declare global temporary table"; -// } -// -// @Override -// public String getCreateIdTableStatementOptions() { -// return "not logged"; -// } -// }, -// AfterUseAction.DROP, -// null -// ); + return new CteStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override @@ -639,6 +597,17 @@ public class DB2Dialect extends Dialect { return false; } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new DB2SqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + /** * Handle DB2 "support" for null precedence... * diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2SqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2SqlAstTranslator.java new file mode 100644 index 0000000000..0c1dfcc218 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2SqlAstTranslator.java @@ -0,0 +1,143 @@ +/* + * 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; + +import java.util.List; + +import org.hibernate.FetchClauseType; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.Literal; +import org.hibernate.sql.ast.tree.insert.InsertStatement; +import org.hibernate.sql.ast.tree.select.QueryGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for DB2. + * + * @author Christian Beikov + */ +public class DB2SqlAstTranslator extends AbstractSqlAstTranslator { + + public DB2SqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + protected boolean shouldEmulateFetchClause(QueryPart queryPart) { + // Percent fetches or ties fetches aren't supported in DB2 + // According to LegacyDB2LimitHandler, variable limit also isn't supported before 11.1 + // Check if current query part is already row numbering to avoid infinite recursion + return getQueryPartForRowNumbering() != queryPart && ( + useOffsetFetchClause( queryPart ) && !isRowsOnlyFetchClauseType( queryPart ) + || getDialect().getVersion() < 1110 && ( queryPart.isRoot() && hasLimit() || !( queryPart.getFetchClauseExpression() instanceof Literal ) ) + ); + } + + protected boolean supportsOffsetClause() { + return getDialect().getVersion() >= 1110; + } + + @Override + public void visitQueryGroup(QueryGroup queryGroup) { + final boolean emulateFetchClause = shouldEmulateFetchClause( queryGroup ); + if ( emulateFetchClause || hasOffset( queryGroup ) && !supportsOffsetClause() ) { + emulateFetchOffsetWithWindowFunctions( queryGroup, emulateFetchClause ); + } + else { + super.visitQueryGroup( queryGroup ); + } + } + + @Override + public void visitQuerySpec(QuerySpec querySpec) { + final boolean emulateFetchClause = shouldEmulateFetchClause( querySpec ); + if ( emulateFetchClause || hasOffset( querySpec ) && !supportsOffsetClause() ) { + emulateFetchOffsetWithWindowFunctions( querySpec, emulateFetchClause ); + } + else { + super.visitQuerySpec( querySpec ); + } + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + if ( !isRowNumberingCurrentQueryPart() ) { + if ( !hasOffset( queryPart ) || supportsOffsetClause() ) { + renderOffsetFetchClause( queryPart, true ); + } + else if ( queryPart.isRoot() && hasLimit() ) { + renderFetch( getLimitParameter(), null, FetchClauseType.ROWS_ONLY ); + } + else if ( queryPart.getFetchClauseExpression() != null ) { + renderFetch( queryPart.getFetchClauseExpression(), null, queryPart.getFetchClauseType() ); + } + } + } + + @Override + protected void visitDeleteStatementOnly(DeleteStatement statement) { + final boolean closeWrapper = renderReturningClause( statement ); + super.visitDeleteStatementOnly( statement ); + if ( closeWrapper ) { + appendSql( ')' ); + } + } + + @Override + protected void visitUpdateStatementOnly(UpdateStatement statement) { + final boolean closeWrapper = renderReturningClause( statement ); + super.visitUpdateStatementOnly( statement ); + if ( closeWrapper ) { + appendSql( ')' ); + } + } + + @Override + protected void visitInsertStatementOnly(InsertStatement statement) { + final boolean closeWrapper = renderReturningClause( statement ); + super.visitInsertStatementOnly( statement ); + if ( closeWrapper ) { + appendSql( ')' ); + } + } + + protected boolean renderReturningClause(MutationStatement statement) { + final List returningColumns = statement.getReturningColumns(); + final int size = returningColumns.size(); + if ( size == 0 ) { + return false; + } + appendSql( "select " ); + String separator = ""; + for ( int i = 0; i < size; i++ ) { + appendSql( separator ); + appendSql( returningColumns.get( i ).getColumnExpression() ); + separator = ", "; + } + if ( statement instanceof DeleteStatement ) { + appendSql( " from old table (" ); + } + else { + appendSql( " from final table (" ); + } + return true; + } + + @Override + protected void visitReturningColumns(MutationStatement mutationStatement) { + // For DB2 we use #renderReturningClause to render a wrapper around the DML statement + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2iDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2iDialect.java index 6cee7aab4e..0eec2ae8f6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2iDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2iDialect.java @@ -15,10 +15,15 @@ import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.sequence.DB2iSequenceSupport; import org.hibernate.dialect.sequence.NoSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; -import org.hibernate.dialect.unique.DB2UniqueDelegate; import org.hibernate.dialect.unique.DefaultUniqueDelegate; import org.hibernate.dialect.unique.UniqueDelegate; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; /** * An SQL dialect for DB2 for iSeries previously known as DB2/400. @@ -90,10 +95,10 @@ public class DB2iDialect extends DB2Dialect { @Override public LimitHandler getLimitHandler() { if ( getIVersion() >= 730) { - return LegacyDB2LimitHandler.INSTANCE; + return FetchLimitHandler.INSTANCE; } else { - return FetchLimitHandler.INSTANCE; + return LegacyDB2LimitHandler.INSTANCE; } } @@ -106,4 +111,15 @@ public class DB2iDialect extends DB2Dialect { return new DB2390IdentityColumnSupport(); } } + + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new DB2iSqlAstTranslator<>( sessionFactory, statement, version ); + } + }; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2iSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2iSqlAstTranslator.java new file mode 100644 index 0000000000..0fd8866625 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2iSqlAstTranslator.java @@ -0,0 +1,46 @@ +/* + * 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; + +import org.hibernate.FetchClauseType; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.expression.Literal; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for DB2i. + * + * @author Christian Beikov + */ +public class DB2iSqlAstTranslator extends DB2SqlAstTranslator { + + private final int version; + + public DB2iSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement, int version) { + super( sessionFactory, statement ); + this.version = version; + } + + @Override + protected boolean shouldEmulateFetchClause(QueryPart queryPart) { + // Percent fetches or ties fetches aren't supported in DB2 iSeries + // According to LegacyDB2LimitHandler, variable limit also isn't supported before 7.1 + return getQueryPartForRowNumbering() != queryPart && ( + useOffsetFetchClause( queryPart ) && !isRowsOnlyFetchClauseType( queryPart ) + || version < 710 && ( queryPart.isRoot() && hasLimit() || !( queryPart.getFetchClauseExpression() instanceof Literal ) ) + ); + } + + @Override + protected boolean supportsOffsetClause() { + return version >= 710; + } + + +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2zDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2zDialect.java index 070daa2930..c01ddce3fb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2zDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2zDialect.java @@ -18,7 +18,13 @@ import org.hibernate.dialect.sequence.DB2390SequenceSupport; import org.hibernate.dialect.sequence.NoSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.TemporalUnit; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; /** * An SQL dialect for DB2 for z/OS, previously known as known as Db2 UDB for z/OS and Db2 UDB for z/OS and OS/390. @@ -128,4 +134,15 @@ public class DB2zDialect extends DB2Dialect { pattern.append(")"); return pattern.toString(); } + + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new DB2zSqlAstTranslator<>( sessionFactory, statement, version ); + } + }; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2zSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2zSqlAstTranslator.java new file mode 100644 index 0000000000..26b31bf025 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2zSqlAstTranslator.java @@ -0,0 +1,46 @@ +/* + * 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; + +import org.hibernate.FetchClauseType; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.expression.Literal; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for DB2z. + * + * @author Christian Beikov + */ +public class DB2zSqlAstTranslator extends DB2SqlAstTranslator { + + private final int version; + + public DB2zSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement, int version) { + super( sessionFactory, statement ); + this.version = version; + } + + @Override + protected boolean shouldEmulateFetchClause(QueryPart queryPart) { + // Percent fetches or ties fetches aren't supported in DB2 z/OS + // Also, variable limit isn't supported before 12.0 + return getQueryPartForRowNumbering() != queryPart && ( + useOffsetFetchClause( queryPart ) && !isRowsOnlyFetchClauseType( queryPart ) + || version < 1200 && queryPart.isRoot() && hasLimit() + || version < 1200 && queryPart.getFetchClauseExpression() != null && !( queryPart.getFetchClauseExpression() instanceof Literal ) + ); + } + + @Override + protected boolean supportsOffsetClause() { + return version >= 1200; + } + +} 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 18ce0e2b53..d9686a2657 100755 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java @@ -23,6 +23,7 @@ import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.LockTimeoutException; import org.hibernate.exception.spi.SQLExceptionConversionDelegate; import org.hibernate.internal.util.JdbcExceptionHelper; @@ -39,6 +40,11 @@ import org.hibernate.query.sqm.mutation.internal.idtable.TempIdTableExporter; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.sql.CaseFragment; import org.hibernate.sql.DerbyCaseFragment; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorDerbyDatabaseImpl; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; @@ -202,6 +208,17 @@ public class DerbyDialect extends Dialect { .register(); } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new DerbySqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + /** * Derby doesn't have an extract() function, and has * no functions at all for calendaring, but we can @@ -410,11 +427,6 @@ public class DerbyDialect extends Dialect { return false; } - @Override - public boolean supportsUnionAll() { - return true; - } - @Override public boolean supportsCurrentTimestampSelection() { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java new file mode 100644 index 0000000000..b8b9f7b161 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java @@ -0,0 +1,99 @@ +/* + * 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; + +import org.hibernate.FetchClauseType; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteContainer; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for Derby. + * + * @author Christian Beikov + */ +public class DerbySqlAstTranslator extends AbstractSqlAstTranslator { + + public DerbySqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + public void visitCteContainer(CteContainer cteContainer) { + if ( cteContainer.isWithRecursive() ) { + throw new IllegalArgumentException( "Recursive CTEs can't be emulated" ); + } + super.visitCteContainer( cteContainer ); + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // Derby does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // Derby does not support this, but it can be emulated + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + // Derby only supports the OFFSET and FETCH clause with ROWS + assertRowsOnlyFetchClauseType( queryPart ); + if ( supportsOffsetFetchClause() ) { + renderOffsetFetchClause( queryPart, true ); + } + else if ( !getClauseStack().isEmpty() ) { + throw new IllegalArgumentException( "Can't render offset and fetch clause for subquery" ); + } + } + + @Override + protected void renderFetchExpression(Expression fetchExpression) { + if ( supportsParameterOffsetFetchExpression() ) { + super.renderFetchExpression( fetchExpression ); + } + else { + renderExpressionAsLiteral( fetchExpression, getJdbcParameterBindings() ); + } + } + + @Override + protected void renderOffsetExpression(Expression offsetExpression) { + if ( supportsParameterOffsetFetchExpression() ) { + super.renderOffsetExpression( offsetExpression ); + } + else { + renderExpressionAsLiteral( offsetExpression, getJdbcParameterBindings() ); + } + } + + @Override + protected boolean needsRowsToSkip() { + return !supportsOffsetFetchClause(); + } + + @Override + protected boolean needsMaxRows() { + return !supportsOffsetFetchClause(); + } + + private boolean supportsParameterOffsetFetchExpression() { + return getDialect().getVersion() >= 1060; + } + + private boolean supportsOffsetFetchClause() { + // Before version 10.5 Derby didn't support OFFSET and FETCH + return getDialect().getVersion() >= 1050; + } + +} 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 148fbc10e7..c1f7ba18f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -11,6 +11,7 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.MappingException; import org.hibernate.NotYetImplementedFor6Exception; +import org.hibernate.NullOrdering; import org.hibernate.NullPrecedence; import org.hibernate.ScrollMode; import org.hibernate.boot.model.TypeContributions; @@ -1877,16 +1878,14 @@ public abstract class Dialect implements ConversionContext { } /** - * Does this dialect support UNION ALL, which is generally a faster - * variant of UNION? + * Does this dialect support UNION ALL. * * @return True if UNION ALL is supported; false otherwise. */ public boolean supportsUnionAll() { - return false; + return true; } - // miscellaneous support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2690,6 +2689,15 @@ public abstract class Dialect implements ConversionContext { return true; } + /** + * Returns the ordering of null. + * + * @since 6.0.0 + */ + public NullOrdering getNullOrdering() { + return NullOrdering.GREATEST; + } + public boolean supportsNullPrecedence() { return true; } @@ -2705,7 +2713,9 @@ public abstract class Dialect implements ConversionContext { * if expression has not been explicitly specified. * @param nulls Nulls precedence. Default value: {@link NullPrecedence#NONE}. * @return Renders single element of {@code ORDER BY} clause. + * @deprecated todo (6.0): remove? */ + @Deprecated public String renderOrderByElement(String expression, String collation, String order, NullPrecedence nulls) { final StringBuilder orderByElement = new StringBuilder( expression ); if ( collation != null ) { @@ -2772,7 +2782,7 @@ public abstract class Dialect implements ConversionContext { * false otherwise. * @since 3.2 */ - public boolean supportsSubselectAsInPredicateLHS() { + public boolean supportsSubselectAsInPredicateLHS() { return true; } @@ -2980,7 +2990,9 @@ public abstract class Dialect implements ConversionContext { * @param expression The expression to negate * * @return The negated expression + * @deprecated todo (6.0): Remove */ + @Deprecated public String getNotExpression(String expression) { return "not " + expression; } @@ -3053,7 +3065,7 @@ public abstract class Dialect implements ConversionContext { */ public String getQueryHintString(String query, List hintList) { final String hints = String.join( ", ", hintList ); - return StringHelper.isEmpty( hints) ? query : getQueryHintString( query, hints); + return StringHelper.isEmpty( hints ) ? query : getQueryHintString( query, hints); } /** @@ -3215,14 +3227,6 @@ public abstract class Dialect implements ConversionContext { return String.format( "\'%s\'", escapeLiteral( literal ) ); } - /** - * Appends the collate clause for the given collation name to the SQL appender. - */ - public void appendCollate(SqlAppender sqlAppender, String collationName) { - sqlAppender.appendSql( " collate " ); - sqlAppender.appendSql( collationName ); - } - /** * Check whether the JDBC {@link java.sql.Connection} supports creating LOBs via {@link Connection#createBlob()}, * {@link Connection#createNClob()} or {@link Connection#createClob()}. @@ -3249,17 +3253,16 @@ public abstract class Dialect implements ConversionContext { */ public String addSqlHintOrComment( String sql, -// QueryParameters parameters, + QueryOptions queryOptions, boolean commentsEnabled) { - // Keep this here, rather than moving to Select. Some Dialects may need the hint to be appended to the very // end or beginning of the finalized SQL statement, so wait until everything is processed. -// if ( parameters.getQueryHints() != null && parameters.getQueryHints().size() > 0 ) { -// sql = getQueryHintString( sql, parameters.getQueryHints() ); -// } -// if ( commentsEnabled && parameters.getComment() != null ){ -// sql = prependComment( sql, parameters.getComment() ); -// } + if ( queryOptions.getDatabaseHints() != null && queryOptions.getDatabaseHints().size() > 0 ) { + sql = getQueryHintString( sql, queryOptions.getDatabaseHints() ); + } + if ( commentsEnabled && queryOptions.getComment() != null ) { + sql = prependComment( sql, queryOptions.getComment() ); + } return sql; } @@ -3296,7 +3299,7 @@ public abstract class Dialect implements ConversionContext { * Note that {@link SessionFactoryOptions#getCustomSqmTranslatorFactory()} has higher * precedence as it comes directly from the user config * - * @see org.hibernate.query.sqm.sql.internal.StandardSqmSelectTranslator + * @see org.hibernate.query.sqm.sql.internal.StandardSqmTranslator * @see QueryEngine#getSqmTranslatorFactory() */ public SqmTranslatorFactory getSqmTranslatorFactory() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/FirebirdDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/FirebirdDialect.java index 7a2e5306c6..7b3498c615 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/FirebirdDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/FirebirdDialect.java @@ -22,6 +22,7 @@ import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.LockAcquisitionException; import org.hibernate.exception.LockTimeoutException; @@ -38,6 +39,11 @@ import org.hibernate.query.sqm.mutation.internal.idtable.GlobalTemporaryTableStr import org.hibernate.query.sqm.mutation.internal.idtable.IdTable; import org.hibernate.query.sqm.mutation.internal.idtable.TempIdTableExporter; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorFirebirdDatabaseImpl; import org.hibernate.tool.schema.extract.internal.SequenceNameExtractorImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; @@ -196,6 +202,17 @@ public class FirebirdDialect extends Dialect { ).setArgumentListSignature("(pattern, string[, start])"); } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new FirebirdSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + /** * Firebird 2.5 doesn't have a real {@link java.sql.Types#BOOLEAN} * type, so... @@ -289,11 +306,6 @@ public class FirebirdDialect extends Dialect { return "add"; } - @Override - public boolean supportsUnionAll() { - return true; - } - @Override public String getNoColumnsInsertString() { return "default values"; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/FirebirdSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/FirebirdSqlAstTranslator.java new file mode 100644 index 0000000000..47350263e6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/FirebirdSqlAstTranslator.java @@ -0,0 +1,108 @@ +/* + * 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; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.collections.Stack; +import org.hibernate.sql.ast.Clause; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.select.QueryGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for Firebird. + * + * @author Christian Beikov + */ +public class FirebirdSqlAstTranslator extends AbstractSqlAstTranslator { + + public FirebirdSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + protected boolean shouldEmulateFetchClause(QueryPart queryPart) { + // Percent fetches or ties fetches aren't supported in Firebird + // Before 3.0 there was also no support for window functions + // Check if current query part is already row numbering to avoid infinite recursion + return useOffsetFetchClause( queryPart ) && getQueryPartForRowNumbering() != queryPart + && getDialect().getVersion() >= 300 && !isRowsOnlyFetchClauseType( queryPart ); + } + + @Override + public void visitQueryGroup(QueryGroup queryGroup) { + if ( shouldEmulateFetchClause( queryGroup ) ) { + emulateFetchOffsetWithWindowFunctions( queryGroup, true ); + } + else { + super.visitQueryGroup( queryGroup ); + } + } + + @Override + public void visitQuerySpec(QuerySpec querySpec) { + if ( shouldEmulateFetchClause( querySpec ) ) { + emulateFetchOffsetWithWindowFunctions( querySpec, true ); + } + else { + super.visitQuerySpec( querySpec ); + } + } + + @Override + public void visitSelectClause(SelectClause selectClause) { + Stack clauseStack = getClauseStack(); + clauseStack.push( Clause.SELECT ); + + try { + appendSql( "select " ); + visitSqlSelections( selectClause ); + } + finally { + clauseStack.pop(); + } + } + + @Override + protected void visitSqlSelections(SelectClause selectClause) { + if ( !supportsOffsetFetchClause() ) { + renderFirstSkipClause( (QuerySpec) getQueryPartStack().getCurrent() ); + } + if ( selectClause.isDistinct() ) { + appendSql( "distinct " ); + } + super.visitSqlSelections( selectClause ); + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + if ( supportsOffsetFetchClause() ) { + // Firebird only supports a FIRST and SKIP clause before 3.0 which is handled in visitSqlSelections + if ( !isRowNumberingCurrentQueryPart() ) { + renderOffsetFetchClause( queryPart, true ); + } + } + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // Firebird does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // Firebird does not support this, but it can be emulated + } + + private boolean supportsOffsetFetchClause() { + return getDialect().getVersion() >= 300; + } +} 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 72bfffbb1f..50e0a666ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -10,6 +10,7 @@ import java.sql.Types; import javax.persistence.TemporalType; +import org.hibernate.NullOrdering; import org.hibernate.PessimisticLockException; import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.cfg.AvailableSettings; @@ -23,6 +24,7 @@ import org.hibernate.dialect.pagination.OffsetFetchLimitHandler; import org.hibernate.dialect.sequence.H2SequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.LockAcquisitionException; import org.hibernate.exception.spi.SQLExceptionConversionDelegate; @@ -38,6 +40,11 @@ import org.hibernate.query.sqm.mutation.internal.idtable.AfterUseAction; import org.hibernate.query.sqm.mutation.internal.idtable.IdTable; import org.hibernate.query.sqm.mutation.internal.idtable.LocalTemporaryTableStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorH2DatabaseImpl; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl; @@ -72,31 +79,35 @@ public class H2Dialect extends Dialect { } public H2Dialect(int version, int buildId) { + this(version + buildId); + } + + public H2Dialect(int version) { super(); this.version = version; - //TODO: actually I think all builds of 1.4 support OFFSET FETCH - limitHandler = version > 140 || version == 140 && buildId >= 199 + // https://github.com/h2database/h2database/commit/b2cdf84e0b84eb8a482fa7dccdccc1ab95241440 + limitHandler = version >= 104195 ? OffsetFetchLimitHandler.INSTANCE : LimitOffsetLimitHandler.INSTANCE; //Note: H2 'bit' is a synonym for 'boolean', not a proper bit type // registerColumnType( Types.BIT, "bit" ); - final int majorVersion = version / 100; - final int minorVersion = version % 100 / 10; - if ( version < 120 || version == 120 && buildId < 139 ) { - + if ( version < 102139 ) { + final int majorVersion = version / 100000; + final int minorVersion = version % 100000 / 1000; + final int buildId = version % 1000; LOG.unsupportedMultiTableBulkHqlJpaql( majorVersion, minorVersion, buildId ); } - supportsTuplesInSubqueries = majorVersion > 1 || minorVersion > 4 || buildId >= 198; + supportsTuplesInSubqueries = version >= 104198; // Prior to 1.4.200 the 'cascade' in 'drop table' was implicit - cascadeConstraints = version > 140 || version == 140 && buildId >= 200; + cascadeConstraints = version >= 104200; getDefaultProperties().setProperty( AvailableSettings.STATEMENT_BATCH_SIZE, DEFAULT_BATCH_SIZE ); // http://code.google.com/p/h2database/issues/detail?id=235 getDefaultProperties().setProperty( AvailableSettings.NON_CONTEXTUAL_LOB_CREATION, "true" ); - if ( buildId >= 32 ) { - this.sequenceInformationExtractor = buildId >= 201 + if ( version >= 104032 ) { + this.sequenceInformationExtractor = version >= 104201 ? SequenceInformationExtractorLegacyImpl.INSTANCE : SequenceInformationExtractorH2DatabaseImpl.INSTANCE; this.querySequenceString = "select * from INFORMATION_SCHEMA.SEQUENCES"; @@ -115,8 +126,8 @@ public class H2Dialect extends Dialect { public H2Dialect(DialectResolutionInfo info) { this( - info.getDatabaseMajorVersion()*100 - + info.getDatabaseMinorVersion()*10, + info.getDatabaseMajorVersion() * 100000 + + info.getDatabaseMinorVersion() * 1000, parseBuildId( info ) ); } @@ -174,6 +185,17 @@ public class H2Dialect extends Dialect { CommonFunctionFactory.rownum( queryEngine ); } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new H2SqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + /** * In H2, the extract() function does not return * fractional seconds for the the field @@ -269,6 +291,11 @@ public class H2Dialect extends Dialect { return "from dual"; } + @Override + public NullOrdering getNullOrdering() { + return NullOrdering.FIRST; + } + @Override public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType entityDescriptor, @@ -342,11 +369,6 @@ public class H2Dialect extends Dialect { return "call current_timestamp()"; } - @Override - public boolean supportsUnionAll() { - return true; - } - // Overridden informational metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java new file mode 100644 index 0000000000..3bab4c2007 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java @@ -0,0 +1,67 @@ +/* + * 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; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for H2. + * + * @author Christian Beikov + */ +public class H2SqlAstTranslator extends AbstractSqlAstTranslator { + + public H2SqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + if ( isRowsOnlyFetchClauseType( queryPart ) ) { + if ( supportsOffsetFetchClause() ) { + renderOffsetFetchClause( queryPart, true ); + } + else { + renderLimitOffsetClause( queryPart ); + } + } + else { + if ( supportsOffsetFetchClausePercentWithTies() ) { + renderOffsetFetchClause( queryPart, true ); + } + else { + // FETCH PERCENT and WITH TIES were introduced along with window functions + throw new IllegalArgumentException( "Can't emulate fetch clause type: " + queryPart.getFetchClauseType() ); + } + } + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // H2 does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // H2 does not support this, but it can be emulated + } + + private boolean supportsOffsetFetchClause() { + return getDialect().getVersion() >= 104195; + } + + private boolean supportsOffsetFetchClausePercentWithTies() { + // Introduction of TIES clause https://github.com/h2database/h2database/commit/876e9fbe7baf11d01675bfe871aac2cf1b6104ce + // Introduction of PERCENT support https://github.com/h2database/h2database/commit/f45913302e5f6ad149155a73763c0c59d8205849 + return getDialect().getVersion() >= 104198; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HANASqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/HANASqlAstTranslator.java new file mode 100644 index 0000000000..beed673554 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HANASqlAstTranslator.java @@ -0,0 +1,72 @@ +/* + * 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; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.select.QueryGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for HANA. + * + * @author Christian Beikov + */ +public class HANASqlAstTranslator extends AbstractSqlAstTranslator { + + public HANASqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + protected boolean shouldEmulateFetchClause(QueryPart queryPart) { + // HANA only supports the LIMIT + OFFSET syntax but also window functions + // Check if current query part is already row numbering to avoid infinite recursion + return useOffsetFetchClause( queryPart ) && getQueryPartForRowNumbering() != queryPart + && !isRowsOnlyFetchClauseType( queryPart ); + } + + @Override + public void visitQueryGroup(QueryGroup queryGroup) { + if ( shouldEmulateFetchClause( queryGroup ) ) { + emulateFetchOffsetWithWindowFunctions( queryGroup, true ); + } + else { + super.visitQueryGroup( queryGroup ); + } + } + + @Override + public void visitQuerySpec(QuerySpec querySpec) { + if ( shouldEmulateFetchClause( querySpec ) ) { + emulateFetchOffsetWithWindowFunctions( querySpec, true ); + } + else { + super.visitQuerySpec( querySpec ); + } + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + if ( !isRowNumberingCurrentQueryPart() ) { + renderLimitOffsetClause( queryPart ); + } + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // HANA does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // HANA does not support this, but it can be emulated + } +} 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 ed2e0fbec1..cc5ce0a793 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -13,6 +13,7 @@ import javax.persistence.TemporalType; import org.hibernate.JDBCException; import org.hibernate.LockMode; +import org.hibernate.NullOrdering; import org.hibernate.StaleObjectStateException; import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.cfg.Environment; @@ -34,6 +35,7 @@ import org.hibernate.dialect.sequence.HSQLSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor; import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; @@ -52,6 +54,11 @@ 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.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorHSQLDBDatabaseImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; @@ -205,6 +212,17 @@ public class HSQLDialect extends Dialect { } } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new HSQLSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + @Override public String castPattern(CastType from, CastType to) { if ( from== BOOLEAN @@ -429,8 +447,8 @@ public class HSQLDialect extends Dialect { } @Override - public boolean supportsUnionAll() { - return true; + public NullOrdering getNullOrdering() { + return NullOrdering.FIRST; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLSqlAstTranslator.java new file mode 100644 index 0000000000..81c9fb526d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLSqlAstTranslator.java @@ -0,0 +1,51 @@ +/* + * 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; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for HSQL. + * + * @author Christian Beikov + */ +public class HSQLSqlAstTranslator extends AbstractSqlAstTranslator { + + public HSQLSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + if ( supportsOffsetFetchClause() ) { + assertRowsOnlyFetchClauseType( queryPart ); + renderOffsetFetchClause( queryPart, true ); + } + else { + renderLimitOffsetClause( queryPart ); + } + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // HSQL does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // HSQL does not support this, but it can be emulated + } + + private boolean supportsOffsetFetchClause() { + return getDialect().getVersion() >= 250; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java index 16423304fd..9a81a53215 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java @@ -18,6 +18,7 @@ import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.dialect.unique.InformixUniqueDelegate; import org.hibernate.dialect.unique.UniqueDelegate; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor; import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; import org.hibernate.internal.util.JdbcExceptionHelper; @@ -30,6 +31,11 @@ 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.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorInformixDatabaseImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.type.StandardBasicTypes; @@ -159,6 +165,17 @@ public class InformixDialect extends Dialect { //coalesce() and nullif() both supported since Informix 12 } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new InformixSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + /** * Informix has no extract() function, but we can * partially emulate it by using the appropriate diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/InformixSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/InformixSqlAstTranslator.java new file mode 100644 index 0000000000..9048d0809c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/InformixSqlAstTranslator.java @@ -0,0 +1,99 @@ +/* + * 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; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for Informix. + * + * @author Christian Beikov + */ +public class InformixSqlAstTranslator extends AbstractSqlAstTranslator { + + public InformixSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + protected void visitSqlSelections(SelectClause selectClause) { + if ( supportsSkipFirstClause() ) { + renderSkipFirstClause( (QuerySpec) getQueryPartStack().getCurrent() ); + } + else { + renderFirstClause( (QuerySpec) getQueryPartStack().getCurrent() ); + } + super.visitSqlSelections( selectClause ); + } + + @Override + protected boolean needsRowsToSkip() { + return !supportsSkipFirstClause(); + } + + @Override + protected void renderFetchPlusOffsetExpression( + Expression fetchClauseExpression, + Expression offsetClauseExpression, + int offset) { + renderFetchPlusOffsetExpressionAsSingleParameter( fetchClauseExpression, offsetClauseExpression, offset ); + } + + @Override + protected void renderFetchExpression(Expression fetchExpression) { + if ( supportsParameterOffsetFetchExpression() ) { + super.renderFetchExpression( fetchExpression ); + } + else { + renderExpressionAsLiteral( fetchExpression, getJdbcParameterBindings() ); + } + } + + @Override + protected void renderOffsetExpression(Expression offsetExpression) { + if ( supportsParameterOffsetFetchExpression() ) { + super.renderOffsetExpression( offsetExpression ); + } + else { + renderExpressionAsLiteral( offsetExpression, getJdbcParameterBindings() ); + } + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + // Informix only supports the SKIP clause in the top level query + if ( !queryPart.isRoot() && queryPart.getOffsetClauseExpression() != null ) { + throw new IllegalArgumentException( "Can't emulate offset clause in subquery" ); + } + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // Informix does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // Informix does not support this, but it can be emulated + } + + private boolean supportsParameterOffsetFetchExpression() { + return getDialect().getVersion() >= 11; + } + + private boolean supportsSkipFirstClause() { + return getDialect().getVersion() >= 11; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/IngresDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/IngresDialect.java index 4d8d45d4cc..8d6d83923e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/IngresDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/IngresDialect.java @@ -17,6 +17,7 @@ import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.sequence.ANSISequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.TemporalUnit; @@ -26,6 +27,11 @@ import org.hibernate.query.sqm.mutation.internal.idtable.GlobalTemporaryTableStr import org.hibernate.query.sqm.mutation.internal.idtable.IdTable; import org.hibernate.query.sqm.mutation.internal.idtable.TempIdTableExporter; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.extract.internal.SequenceNameExtractorImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.type.StandardBasicTypes; @@ -223,6 +229,17 @@ public class IngresDialect extends Dialect { } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new IngresSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + @Override public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType) { return "timestampadd(?1, ?2, ?3)"; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/IngresSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/IngresSqlAstTranslator.java new file mode 100644 index 0000000000..5536d8a87c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/IngresSqlAstTranslator.java @@ -0,0 +1,99 @@ +/* + * 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; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for Ingres. + * + * @author Christian Beikov + */ +public class IngresSqlAstTranslator extends AbstractSqlAstTranslator { + + public IngresSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + protected void renderFetchPlusOffsetExpression( + Expression fetchClauseExpression, + Expression offsetClauseExpression, + int offset) { + renderFetchPlusOffsetExpressionAsSingleParameter( fetchClauseExpression, offsetClauseExpression, offset ); + } + + @Override + protected void visitSqlSelections(SelectClause selectClause) { + if ( !supportsOffsetFetchClause() ) { + renderFirstClause( (QuerySpec) getQueryPartStack().getCurrent() ); + } + super.visitSqlSelections( selectClause ); + } + + @Override + protected void renderFetchExpression(Expression fetchExpression) { + if ( supportsParameterOffsetFetchExpression() ) { + super.renderFetchExpression( fetchExpression ); + } + else { + renderExpressionAsLiteral( fetchExpression, getJdbcParameterBindings() ); + } + } + + @Override + protected void renderOffsetExpression(Expression offsetExpression) { + if ( supportsParameterOffsetFetchExpression() ) { + super.renderOffsetExpression( offsetExpression ); + } + else { + renderExpressionAsLiteral( offsetExpression, getJdbcParameterBindings() ); + } + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + if ( supportsOffsetFetchClause() ) { + renderOffsetFetchClause( queryPart, false ); + } + else if ( !queryPart.isRoot() && queryPart.getOffsetClauseExpression() != null ) { + throw new IllegalArgumentException( "Can't emulate offset clause in subquery" ); + } + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // Ingres does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // Ingres does not support this, but it can be emulated + } + + @Override + protected boolean needsRowsToSkip() { + return !supportsOffsetFetchClause(); + } + + private boolean supportsParameterOffsetFetchExpression() { + return getDialect().getVersion() >= 930; + } + + private boolean supportsOffsetFetchClause() { + return getDialect().getVersion() >= 930; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java index 75503f7de5..fb23967059 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java @@ -6,11 +6,16 @@ */ package org.hibernate.dialect; -import org.hibernate.LockOptions; -import org.hibernate.dialect.sequence.ANSISequenceSupport; +import org.hibernate.dialect.sequence.MariaDBSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.spi.QueryEngine; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorMariaDBDatabaseImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.type.StandardBasicTypes; @@ -25,39 +30,51 @@ public class MariaDBDialect extends MySQLDialect { private final int version; - int getMariaVersion() { - return version; - } - public MariaDBDialect() { this(500); } - public MariaDBDialect(int version) { - super(version < 530 ? 500 : 570); - this.version = version; - } - public MariaDBDialect(DialectResolutionInfo info) { this( info.getDatabaseMajorVersion() * 100 + info.getDatabaseMinorVersion() * 10 ); } + public MariaDBDialect(int version) { + super( version < 530 ? 500 : 570 ); + this.version = version; + } + + @Override + public int getVersion() { + return version; + } + @Override public void initializeFunctionRegistry(QueryEngine queryEngine) { super.initializeFunctionRegistry(queryEngine); - if ( getMariaVersion() >= 1020 ) { + if ( getVersion() >= 1020 ) { queryEngine.getSqmFunctionRegistry().registerNamed("json_valid", StandardBasicTypes.NUMERIC_BOOLEAN); } } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new MariaDBSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + public boolean supportsRowValueConstructorSyntaxInInList() { return true; } @Override public boolean supportsColumnCheck() { - return getMariaVersion() >= 1020; + return getVersion() >= 1020; } @Override @@ -67,14 +84,14 @@ public class MariaDBDialect extends MySQLDialect { @Override public boolean supportsIfExistsBeforeConstraintName() { - return getMariaVersion() >= 1000; + return getVersion() >= 1000; } @Override public SequenceSupport getSequenceSupport() { - return getMariaVersion() < 1030 + return getVersion() < 1030 ? super.getSequenceSupport() - : ANSISequenceSupport.INSTANCE; + : MariaDBSequenceSupport.INSTANCE; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBSqlAstTranslator.java new file mode 100644 index 0000000000..2d1981dd24 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBSqlAstTranslator.java @@ -0,0 +1,74 @@ +/* + * 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; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.select.QueryGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for MariaDB. + * + * @author Christian Beikov + */ +public class MariaDBSqlAstTranslator extends AbstractSqlAstTranslator { + + public MariaDBSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + protected boolean shouldEmulateFetchClause(QueryPart queryPart) { + // Check if current query part is already row numbering to avoid infinite recursion + return useOffsetFetchClause( queryPart ) && getQueryPartForRowNumbering() != queryPart && supportsWindowFunctions() && !isRowsOnlyFetchClauseType( queryPart ); + } + + @Override + public void visitQueryGroup(QueryGroup queryGroup) { + if ( shouldEmulateFetchClause( queryGroup ) ) { + emulateFetchOffsetWithWindowFunctions( queryGroup, true ); + } + else { + super.visitQueryGroup( queryGroup ); + } + } + + @Override + public void visitQuerySpec(QuerySpec querySpec) { + if ( shouldEmulateFetchClause( querySpec ) ) { + emulateFetchOffsetWithWindowFunctions( querySpec, true ); + } + else { + super.visitQuerySpec( querySpec ); + } + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + if ( !isRowNumberingCurrentQueryPart() ) { + renderCombinedLimitClause( queryPart ); + } + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // MariaDB does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // MariaDB does not support this, but it can be emulated + } + + private boolean supportsWindowFunctions() { + return getDialect().getVersion() >= 1020; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MaxDBDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MaxDBDialect.java index 77eea6c16b..5f0ece7f1b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MaxDBDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MaxDBDialect.java @@ -9,8 +9,11 @@ package org.hibernate.dialect; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.cfg.Environment; import org.hibernate.dialect.function.CommonFunctionFactory; +import org.hibernate.dialect.pagination.LimitHandler; +import org.hibernate.dialect.pagination.LimitOffsetLimitHandler; import org.hibernate.dialect.sequence.MaxDBSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.TrimSpec; @@ -18,6 +21,11 @@ import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.sql.CaseFragment; import org.hibernate.sql.DecodeCaseFragment; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorSAPDBDatabaseImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.type.StandardBasicTypes; @@ -60,6 +68,11 @@ public class MaxDBDialect extends Dialect { return 0; } + @Override + public LimitHandler getLimitHandler() { + return LimitOffsetLimitHandler.INSTANCE; + } + @Override public void initializeFunctionRegistry(QueryEngine queryEngine) { super.initializeFunctionRegistry( queryEngine ); @@ -112,6 +125,17 @@ public class MaxDBDialect extends Dialect { ).setArgumentListSignature("(pattern, string[, start])"); } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new MaxDBSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + @Override public String trimPattern(TrimSpec specification, char character) { return AbstractTransactSQLDialect.replaceLtrimRtrim(specification, character); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MaxDBSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/MaxDBSqlAstTranslator.java new file mode 100644 index 0000000000..4fddddec0e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MaxDBSqlAstTranslator.java @@ -0,0 +1,41 @@ +/* + * 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; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for MaxDB. + * + * @author Christian Beikov + */ +public class MaxDBSqlAstTranslator extends AbstractSqlAstTranslator { + + public MaxDBSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + renderLimitOffsetClause( queryPart ); + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // MaxDB does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // MaxDB does not support this, but it can be emulated + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLDialect.java index bacca2e900..80f623b151 100755 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLDialect.java @@ -15,10 +15,16 @@ import org.hibernate.dialect.pagination.OffsetFetchLimitHandler; import org.hibernate.dialect.sequence.MimerSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.engine.jdbc.Size; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.CastType; import org.hibernate.query.SemanticException; import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorMimerSQLDatabaseImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; @@ -107,6 +113,17 @@ public class MimerSQLDialect extends Dialect { CommonFunctionFactory.localtimeLocaltimestamp( queryEngine ); } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new MimerSQLSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + /** * Mimer does have a real {@link java.sql.Types#BOOLEAN} * type, but it doesn't know how to cast to it. diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLSqlAstTranslator.java new file mode 100644 index 0000000000..b16abffc83 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLSqlAstTranslator.java @@ -0,0 +1,42 @@ +/* + * 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; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for MimerSQL. + * + * @author Christian Beikov + */ +public class MimerSQLSqlAstTranslator extends AbstractSqlAstTranslator { + + public MimerSQLSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + assertRowsOnlyFetchClauseType( queryPart ); + renderOffsetFetchClause( queryPart, true ); + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // MimerSQL does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // MimerSQL does not support this, but it can be emulated + } +} 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 68b3a1bba5..87145105ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -7,6 +7,7 @@ package org.hibernate.dialect; import org.hibernate.LockOptions; +import org.hibernate.NullOrdering; import org.hibernate.NullPrecedence; import org.hibernate.PessimisticLockException; import org.hibernate.boot.TempTableDdlTransactionHandling; @@ -22,6 +23,7 @@ import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.dialect.unique.MySQLUniqueDelegate; import org.hibernate.dialect.unique.UniqueDelegate; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.LockAcquisitionException; import org.hibernate.exception.LockTimeoutException; import org.hibernate.exception.spi.SQLExceptionConversionDelegate; @@ -39,6 +41,11 @@ 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.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.type.StandardBasicTypes; import java.sql.CallableStatement; @@ -59,15 +66,15 @@ import static org.hibernate.query.CastType.BOOLEAN; public class MySQLDialect extends Dialect { private final UniqueDelegate uniqueDelegate; - private MySQLStorageEngine storageEngine; - private int version; + private final MySQLStorageEngine storageEngine; + private final int version; public MySQLDialect(DialectResolutionInfo info) { this( info.getDatabaseMajorVersion() * 100 + info.getDatabaseMinorVersion() * 10 ); } public MySQLDialect() { - this(400); + this( 400 ); } public MySQLDialect(int version) { @@ -95,7 +102,7 @@ public class MySQLDialect extends Dialect { registerColumnType( Types.NUMERIC, "decimal($p,$s)" ); //it's just a synonym - if ( getVersion() < 570) { + if ( getMySQLVersion() < 570) { registerColumnType( Types.TIMESTAMP, "datetime" ); registerColumnType( Types.TIMESTAMP_WITH_TIMEZONE, "timestamp" ); } @@ -107,7 +114,7 @@ public class MySQLDialect extends Dialect { } // max length for VARCHAR changed in 5.0.3 - final int maxVarcharLen = getVersion() < 500 ? 255 : 65_535; + final int maxVarcharLen = getMySQLVersion() < 500 ? 255 : 65_535; registerColumnType( Types.VARCHAR, maxVarcharLen, "varchar($l)" ); registerColumnType( Types.VARBINARY, maxVarcharLen, "varbinary($l)" ); @@ -144,7 +151,7 @@ public class MySQLDialect extends Dialect { registerColumnType( Types.NCLOB, maxLobLen, "text" ); registerColumnType( Types.NCLOB, maxTinyLobLen, "tinytext" ); - if ( getVersion() >= 570) { + if ( getMySQLVersion() >= 570) { // MySQL 5.7 brings JSON native support with a dedicated datatype // https://dev.mysql.com/doc/refman/5.7/en/json.html registerColumnType(Types.JAVA_OBJECT, "json"); @@ -163,6 +170,10 @@ public class MySQLDialect extends Dialect { return version; } + public int getMySQLVersion() { + return version; + } + @Override public long getDefaultLobLength() { //max length for mediumblob or mediumtext @@ -237,7 +248,7 @@ public class MySQLDialect extends Dialect { CommonFunctionFactory.format_dateFormat( queryEngine ); CommonFunctionFactory.makedateMaketime( queryEngine ); - if ( getVersion() < 570 ) { + if ( getMySQLVersion() < 570 ) { CommonFunctionFactory.sysdateParens( queryEngine ); } else { @@ -247,6 +258,17 @@ public class MySQLDialect extends Dialect { } } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new MySQLSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + private void time(QueryEngine queryEngine) { queryEngine.getSqmFunctionRegistry().namedDescriptorBuilder( "time" ) .setExactArgumentCount( 1 ) @@ -266,7 +288,7 @@ public class MySQLDialect extends Dialect { */ @Override public String currentTimestamp() { - return getVersion() < 570 ? super.currentTimestamp() : "current_timestamp(6)"; + return getMySQLVersion() < 570 ? super.currentTimestamp() : "current_timestamp(6)"; } /** @@ -367,12 +389,12 @@ public class MySQLDialect extends Dialect { */ @Override public boolean supportsRowValueConstructorSyntaxInInList() { - return getVersion() >= 570; + return getMySQLVersion() >= 570; } @Override public boolean supportsUnionAll() { - return getVersion() >= 500; + return getMySQLVersion() >= 500; } @Override @@ -382,7 +404,7 @@ public class MySQLDialect extends Dialect { @Override public String getQueryHintString(String query, String hints) { - return getVersion() < 500 + return getMySQLVersion() < 500 ? super.getQueryHintString( query, hints ) : IndexQueryHintHandler.INSTANCE.addQueryHints( query, hints ); } @@ -396,7 +418,7 @@ public class MySQLDialect extends Dialect { } public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() { - return getVersion() < 500 ? super.getViolatedConstraintNameExtractor() : EXTRACTOR; + return getMySQLVersion() < 500 ? super.getViolatedConstraintNameExtractor() : EXTRACTOR; } private static final ViolatedConstraintNameExtractor EXTRACTOR = @@ -508,6 +530,11 @@ public class MySQLDialect extends Dialect { return " comment '" + comment + "'"; } + @Override + public NullOrdering getNullOrdering() { + return NullOrdering.SMALLEST; + } + @Override public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, @@ -723,7 +750,7 @@ public class MySQLDialect extends Dialect { @Override public String getTableTypeString() { - String engineKeyword = getVersion() < 500 ? "type" : "engine"; + String engineKeyword = getMySQLVersion() < 500 ? "type" : "engine"; return storageEngine.getTableTypeString( engineKeyword ); } @@ -738,7 +765,7 @@ public class MySQLDialect extends Dialect { } protected MySQLStorageEngine getDefaultMySQLStorageEngine() { - return getVersion() < 550 ? MyISAMStorageEngine.INSTANCE : InnoDBStorageEngine.INSTANCE; + return getMySQLVersion() < 550 ? MyISAMStorageEngine.INSTANCE : InnoDBStorageEngine.INSTANCE; } @Override @@ -900,12 +927,12 @@ public class MySQLDialect extends Dialect { @Override public boolean supportsSkipLocked() { - return getVersion() >= 800; + return getMySQLVersion() >= 800; } @Override public boolean supportsNoWait() { - return getVersion() >= 800; + return getMySQLVersion() >= 800; } public boolean supportsWait() { @@ -914,11 +941,11 @@ public class MySQLDialect extends Dialect { } boolean supportsForShare() { - return getVersion() >= 800; + return getMySQLVersion() >= 800; } boolean supportsAliasLocks() { - return getVersion() >= 800; + return getMySQLVersion() >= 800; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLSqlAstTranslator.java new file mode 100644 index 0000000000..f3a97ca260 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLSqlAstTranslator.java @@ -0,0 +1,75 @@ +/* + * 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; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.select.QueryGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for MySQL. + * + * @author Christian Beikov + */ +public class MySQLSqlAstTranslator extends AbstractSqlAstTranslator { + + public MySQLSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + protected boolean shouldEmulateFetchClause(QueryPart queryPart) { + // Check if current query part is already row numbering to avoid infinite recursion + return useOffsetFetchClause( queryPart ) && getQueryPartForRowNumbering() != queryPart + && supportsWindowFunctions() && !isRowsOnlyFetchClauseType( queryPart ); + } + + @Override + public void visitQueryGroup(QueryGroup queryGroup) { + if ( shouldEmulateFetchClause( queryGroup ) ) { + emulateFetchOffsetWithWindowFunctions( queryGroup, true ); + } + else { + super.visitQueryGroup( queryGroup ); + } + } + + @Override + public void visitQuerySpec(QuerySpec querySpec) { + if ( shouldEmulateFetchClause( querySpec ) ) { + emulateFetchOffsetWithWindowFunctions( querySpec, true ); + } + else { + super.visitQuerySpec( querySpec ); + } + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + if ( !isRowNumberingCurrentQueryPart() ) { + renderCombinedLimitClause( queryPart ); + } + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // MySQL does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // MySQL does not support this, but it can be emulated + } + + private boolean supportsWindowFunctions() { + return getDialect().getVersion() >= 802; + } +} 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 59fa5bcd97..922ba70241 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -22,6 +22,7 @@ import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.LockAcquisitionException; import org.hibernate.exception.LockTimeoutException; @@ -46,6 +47,11 @@ import org.hibernate.query.sqm.mutation.internal.idtable.TempIdTableExporter; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.service.ServiceRegistry; import org.hibernate.sql.*; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorOracleDatabaseImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.type.StandardBasicTypes; @@ -179,6 +185,17 @@ public class OracleDialect extends Dialect { ).setArgumentListSignature("(pattern, string[, start])"); } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new OracleSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + @Override public String currentDate() { return getVersion() < 9 ? currentTimestamp() : "current_date"; @@ -783,11 +800,6 @@ public class OracleDialect extends Dialect { return (ResultSet) ps.getObject( 1 ); } - @Override - public boolean supportsUnionAll() { - return true; - } - @Override public boolean supportsCommentOn() { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java new file mode 100644 index 0000000000..3a1c3cb770 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java @@ -0,0 +1,83 @@ +/* + * 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; + +import org.hibernate.FetchClauseType; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.select.QueryGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for Oracle. + * + * @author Christian Beikov + */ +public class OracleSqlAstTranslator extends AbstractSqlAstTranslator { + + public OracleSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + protected boolean shouldEmulateFetchClause(QueryPart queryPart) { + // Check if current query part is already row numbering to avoid infinite recursion + return getQueryPartForRowNumbering() != queryPart && !supportsOffsetFetchClause() && ( + queryPart.isRoot() && hasLimit() || queryPart.getFetchClauseExpression() != null || queryPart.getOffsetClauseExpression() != null + ); + } + + @Override + public void visitQueryGroup(QueryGroup queryGroup) { + if ( shouldEmulateFetchClause( queryGroup ) ) { + emulateFetchOffsetWithWindowFunctions( queryGroup, true ); + } + else { + super.visitQueryGroup( queryGroup ); + } + } + + @Override + public void visitQuerySpec(QuerySpec querySpec) { + if ( shouldEmulateFetchClause( querySpec ) ) { + emulateFetchOffsetWithWindowFunctions( querySpec, true ); + } + else { + super.visitQuerySpec( querySpec ); + } + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + if ( !isRowNumberingCurrentQueryPart() ) { + if ( supportsOffsetFetchClause() ) { + renderOffsetFetchClause( queryPart, true ); + } + else { + assertRowsOnlyFetchClauseType( queryPart ); + } + } + } + + @Override + protected void renderRowNumber(SelectClause selectClause, QueryPart queryPart) { + if ( supportsOffsetFetchClause() || selectClause.isDistinct() ) { + super.renderRowNumber( selectClause, queryPart ); + } + else { + appendSql( "rownum" ); + } + } + + private boolean supportsOffsetFetchClause() { + return getDialect().getVersion() >= 12; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java index 0933b21e74..ea5efce1ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -9,7 +9,6 @@ package org.hibernate.dialect; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.PessimisticLockException; -import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.boot.model.TypeContributions; import org.hibernate.cfg.Environment; import org.hibernate.dialect.function.CommonFunctionFactory; @@ -21,6 +20,7 @@ import org.hibernate.dialect.pagination.OffsetFetchLimitHandler; import org.hibernate.dialect.sequence.PostgreSQLSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.LockAcquisitionException; import org.hibernate.exception.spi.SQLExceptionConversionDelegate; import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor; @@ -33,12 +33,14 @@ import org.hibernate.procedure.spi.CallableStatementSupport; import org.hibernate.query.SemanticException; import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; -import org.hibernate.query.sqm.mutation.internal.idtable.AfterUseAction; -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.internal.cte.CteStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.service.ServiceRegistry; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; @@ -460,11 +462,6 @@ public class PostgreSQLDialect extends Dialect { return false; } - @Override - public boolean supportsUnionAll() { - return true; - } - /** * Workaround for postgres bug #1453 *

@@ -505,26 +502,22 @@ public class PostgreSQLDialect extends Dialect { return String.valueOf( bool ); } + @Override public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new LocalTemporaryTableStrategy( - new IdTable( rootEntityDescriptor, base -> "HT_" + base, this ), - () -> new TempIdTableExporter( true, this::getTypeName ) { - @Override - protected String getCreateCommand() { - return "create temporary table"; - } + return new CteStrategy( rootEntityDescriptor, runtimeModelCreationContext ); + } - @Override - protected String getCreateOptions() { - return "on commit drop"; - } - }, - AfterUseAction.CLEAN, - TempTableDdlTransactionHandling.NONE, - runtimeModelCreationContext.getSessionFactory() - ); + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new PostgreSQLSqlAstTranslator<>( sessionFactory, statement ); + } + }; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLSqlAstTranslator.java new file mode 100644 index 0000000000..17b2a7a7e7 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLSqlAstTranslator.java @@ -0,0 +1,103 @@ +/* + * 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; + +import org.hibernate.FetchClauseType; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.Literal; +import org.hibernate.sql.ast.tree.select.QueryGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for PostgreSQL. + * + * @author Christian Beikov + */ +public class PostgreSQLSqlAstTranslator extends AbstractSqlAstTranslator { + + public PostgreSQLSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + protected boolean shouldEmulateFetchClause(QueryPart queryPart) { + // Check if current query part is already row numbering to avoid infinite recursion + if ( getQueryPartForRowNumbering() == queryPart || isRowsOnlyFetchClauseType( queryPart ) ) { + return false; + } + final FetchClauseType fetchClauseType = queryPart.getFetchClauseType(); + switch ( fetchClauseType ) { + case PERCENT_ONLY: + case PERCENT_WITH_TIES: + return !supportsOffsetFetchClausePercent(); + case ROWS_WITH_TIES: + return !supportsOffsetFetchClauseWithTies(); + } + return false; + } + + @Override + public void visitQueryGroup(QueryGroup queryGroup) { + if ( shouldEmulateFetchClause( queryGroup ) ) { + emulateFetchOffsetWithWindowFunctions( queryGroup, true ); + } + else { + super.visitQueryGroup( queryGroup ); + } + } + + @Override + public void visitQuerySpec(QuerySpec querySpec) { + if ( shouldEmulateFetchClause( querySpec ) ) { + emulateFetchOffsetWithWindowFunctions( querySpec, true ); + } + else { + super.visitQuerySpec( querySpec ); + } + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + if ( !isRowNumberingCurrentQueryPart() ) { + if ( supportsOffsetFetchClause() ) { + renderOffsetFetchClause( queryPart, true ); + } + else { + renderLimitOffsetClause( queryPart ); + } + } + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // PostgreSQL does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // PostgreSQL does not support this, but it can be emulated + } + + private boolean supportsOffsetFetchClause() { + return getDialect().getVersion() >= 840; + } + + private boolean supportsOffsetFetchClauseWithTies() { + return getDialect().getVersion() >= 1300; + } + + private boolean supportsOffsetFetchClausePercent() { + // Currently, percent always has to be emulated + return false; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/RDBMSOS2200SqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/RDBMSOS2200SqlAstTranslator.java new file mode 100644 index 0000000000..62c22dd936 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/RDBMSOS2200SqlAstTranslator.java @@ -0,0 +1,68 @@ +/* + * 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; + +import org.hibernate.FetchClauseType; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.query.Limit; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for Unisys 2200. + * + * @author Christian Beikov + */ +public class RDBMSOS2200SqlAstTranslator extends AbstractSqlAstTranslator { + + public RDBMSOS2200SqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + if ( queryPart.isRoot() ) { + if ( hasLimit() ) { + prepareLimitOffsetParameters(); + renderFetch( getLimitParameter(), getOffsetParameter(), FetchClauseType.ROWS_ONLY ); + } + else if ( queryPart.getFetchClauseExpression() != null ) { + renderFetch( queryPart.getFetchClauseExpression(), queryPart.getOffsetClauseExpression(), queryPart.getFetchClauseType() ); + } + } + else if ( queryPart.getOffsetClauseExpression() != null ) { + throw new IllegalArgumentException( "Can't emulate offset clause in subquery" ); + } + } + + @Override + protected void renderFetchPlusOffsetExpression( + Expression fetchClauseExpression, + Expression offsetClauseExpression, + int offset) { + renderFetchPlusOffsetExpressionAsSingleParameter( fetchClauseExpression, offsetClauseExpression, offset ); + } + + @Override + protected boolean needsRowsToSkip() { + return true; + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // Unisys 2200 does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // Unisys 2200 does not support this, but it can be emulated + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/RDMSOS2200Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/RDMSOS2200Dialect.java index 36110bceb7..7d78dfeba0 100755 --- a/hibernate-core/src/main/java/org/hibernate/dialect/RDMSOS2200Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/RDMSOS2200Dialect.java @@ -13,6 +13,7 @@ import org.hibernate.dialect.pagination.FetchLimitHandler; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.sequence.RDMSSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.persister.entity.Lockable; import org.hibernate.query.TemporalUnit; @@ -20,6 +21,12 @@ import org.hibernate.query.TrimSpec; import org.hibernate.query.spi.QueryEngine; import org.hibernate.sql.CaseFragment; import org.hibernate.sql.DecodeCaseFragment; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; + import org.jboss.logging.Logger; import java.sql.Types; @@ -151,6 +158,17 @@ public class RDMSOS2200Dialect extends Dialect { CommonFunctionFactory.monthsBetween( queryEngine ); } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new RDBMSOS2200SqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + @Override public long getFractionalSecondPrecisionInNanos() { return 1_000; //microseconds @@ -290,12 +308,6 @@ public class RDMSOS2200Dialect extends Dialect { return FetchLimitHandler.INSTANCE; } - @Override - public boolean supportsUnionAll() { - // RDMS supports the UNION ALL clause. - return true; - } - @Override public String getFromDual() { return "from rdms.rdms_dummy where key_col = 1"; 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 1cd6300cde..446013f44c 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -18,11 +18,17 @@ import org.hibernate.dialect.sequence.ANSISequenceSupport; import org.hibernate.dialect.sequence.NoSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.LockTimeoutException; import org.hibernate.exception.spi.SQLExceptionConversionDelegate; import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.sql.SmallIntTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; @@ -175,6 +181,17 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { } } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new SQLServerSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + @Override public String currentTimestamp() { return "sysdatetime()"; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerSqlAstTranslator.java new file mode 100644 index 0000000000..29cb06e799 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerSqlAstTranslator.java @@ -0,0 +1,188 @@ +/* + * 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; + +import java.util.List; + +import org.hibernate.FetchClauseType; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.Clause; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.select.QueryGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.ast.tree.select.SortSpecification; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for SQL Server. + * + * @author Christian Beikov + */ +public class SQLServerSqlAstTranslator extends AbstractSqlAstTranslator { + + public SQLServerSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + protected OffsetFetchClauseMode getOffsetFetchClauseMode(QueryPart queryPart) { + final int version = getDialect().getVersion(); + final boolean hasLimit; + final boolean hasOffset; + if ( queryPart.isRoot() && hasLimit() ) { + hasLimit = getLimit().getMaxRowsJpa() != Integer.MAX_VALUE; + hasOffset = getLimit().getFirstRowJpa() != 0; + } + else { + hasLimit = queryPart.getFetchClauseExpression() != null; + hasOffset = queryPart.getOffsetClauseExpression() != null; + } + if ( version < 9 || !hasOffset ) { + return hasLimit ? OffsetFetchClauseMode.TOP_ONLY : null; + } + else if ( version < 11 || !isRowsOnlyFetchClauseType( queryPart ) ) { + return OffsetFetchClauseMode.EMULATED; + } + else { + return OffsetFetchClauseMode.STANDARD; + } + } + + protected boolean shouldEmulateFetchClause(QueryPart queryPart) { + // Check if current query part is already row numbering to avoid infinite recursion + return getQueryPartForRowNumbering() != queryPart && getOffsetFetchClauseMode( queryPart ) == OffsetFetchClauseMode.EMULATED; + } + + @Override + public void visitQueryGroup(QueryGroup queryGroup) { + if ( shouldEmulateFetchClause( queryGroup ) ) { + emulateFetchOffsetWithWindowFunctions( queryGroup, !isRowsOnlyFetchClauseType( queryGroup ) ); + } + else { + super.visitQueryGroup( queryGroup ); + } + } + + @Override + public void visitQuerySpec(QuerySpec querySpec) { + if ( shouldEmulateFetchClause( querySpec ) ) { + emulateFetchOffsetWithWindowFunctions( querySpec, !isRowsOnlyFetchClauseType( querySpec ) ); + } + else { + super.visitQuerySpec( querySpec ); + } + } + + @Override + protected boolean needsRowsToSkip() { + return getDialect().getVersion() < 9; + } + + @Override + protected void renderFetchPlusOffsetExpression( + Expression fetchClauseExpression, + Expression offsetClauseExpression, + int offset) { + renderFetchPlusOffsetExpressionAsSingleParameter( fetchClauseExpression, offsetClauseExpression, offset ); + } + + @Override + protected void visitSqlSelections(SelectClause selectClause) { + final QuerySpec querySpec = (QuerySpec) getQueryPartStack().getCurrent(); + final OffsetFetchClauseMode offsetFetchClauseMode = getOffsetFetchClauseMode( querySpec ); + if ( offsetFetchClauseMode == OffsetFetchClauseMode.TOP_ONLY ) { + renderTopClause( querySpec, true ); + } + else if ( offsetFetchClauseMode == OffsetFetchClauseMode.EMULATED ) { + renderTopClause( querySpec, isRowsOnlyFetchClauseType( querySpec ) ); + } + super.visitSqlSelections( selectClause ); + } + + @Override + protected void renderOrderBy(boolean addWhitespace, List sortSpecifications) { + if ( sortSpecifications != null && !sortSpecifications.isEmpty() ) { + super.renderOrderBy( addWhitespace, sortSpecifications ); + } + else if ( getClauseStack().getCurrent() == Clause.OVER ) { + if ( addWhitespace ) { + appendSql( ' ' ); + } + renderEmptyOrderBy(); + } + } + + protected void renderEmptyOrderBy() { + // Always need an order by clause: https://blog.jooq.org/2014/05/13/sql-server-trick-circumvent-missing-order-by-clause/ + appendSql( "order by @@version" ); + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + if ( !isRowNumberingCurrentQueryPart() ) { + if ( getDialect().getVersion() < 9 && !queryPart.isRoot() && queryPart.getOffsetClauseExpression() != null ) { + throw new IllegalArgumentException( "Can't emulate offset clause in subquery" ); + } + // Note that SQL Server is very strict i.e. it requires an order by clause for TOP or OFFSET + final OffsetFetchClauseMode offsetFetchClauseMode = getOffsetFetchClauseMode( queryPart ); + if ( offsetFetchClauseMode == OffsetFetchClauseMode.STANDARD ) { + if ( !queryPart.hasSortSpecifications() ) { + appendSql( ' ' ); + renderEmptyOrderBy(); + } + final Expression offsetExpression; + final Expression fetchExpression; + final FetchClauseType fetchClauseType; + if ( queryPart.isRoot() && hasLimit() ) { + prepareLimitOffsetParameters(); + offsetExpression = getOffsetParameter(); + fetchExpression = getLimitParameter(); + fetchClauseType = FetchClauseType.ROWS_ONLY; + } + else { + offsetExpression = queryPart.getOffsetClauseExpression(); + fetchExpression = queryPart.getFetchClauseExpression(); + fetchClauseType = queryPart.getFetchClauseType(); + } + if ( offsetExpression == null ) { + appendSql( " offset 0 rows" ); + } + else { + renderOffset( offsetExpression, true ); + } + + if ( fetchExpression != null ) { + renderFetch( fetchExpression, null, fetchClauseType ); + } + } + else if ( offsetFetchClauseMode == OffsetFetchClauseMode.TOP_ONLY && !queryPart.hasSortSpecifications() ) { + appendSql( ' ' ); + renderEmptyOrderBy(); + } + } + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // SQL Server does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // SQL Server does not support this, but it can be emulated + } + + enum OffsetFetchClauseMode { + STANDARD, + TOP_ONLY, + EMULATED; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java index 6a1d365438..4ba3eb871a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java @@ -19,6 +19,7 @@ import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.LimitOffsetLimitHandler; import org.hibernate.dialect.unique.UniqueDelegate; import org.hibernate.engine.jdbc.env.spi.SchemaNameResolver; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.mapping.Column; @@ -31,6 +32,11 @@ import org.hibernate.persister.entity.Lockable; import org.hibernate.query.SemanticException; import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.spi.Exporter; import org.hibernate.type.StandardBasicTypes; @@ -377,6 +383,17 @@ public class SpannerDialect extends Dialect { .register(); } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new SpannerSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + @Override public Exporter getTableExporter() { return this.spannerTableExporter; @@ -404,11 +421,6 @@ public class SpannerDialect extends Dialect { return String.valueOf( bool ); } - @Override - public boolean supportsUnionAll() { - return true; - } - @Override public String translateExtractField(TemporalUnit unit) { switch (unit) { @@ -769,7 +781,7 @@ public class SpannerDialect extends Dialect { @Override public LimitHandler getLimitHandler() { - return new LimitOffsetLimitHandler(); + return LimitOffsetLimitHandler.INSTANCE; } /* Type conversion and casting */ diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SpannerSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/SpannerSqlAstTranslator.java new file mode 100644 index 0000000000..47f4488ce0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SpannerSqlAstTranslator.java @@ -0,0 +1,42 @@ +/* + * 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; + +import org.hibernate.FetchClauseType; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for Spanner. + * + * @author Christian Beikov + */ +public class SpannerSqlAstTranslator extends AbstractSqlAstTranslator { + + public SpannerSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + renderLimitOffsetClause( queryPart ); + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // Spanner does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // Spanner does not support this, but it can be emulated + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java index f58e90202e..630b2c3408 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java @@ -10,6 +10,7 @@ import org.hibernate.LockOptions; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.TopLimitHandler; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.LockTimeoutException; import org.hibernate.exception.spi.SQLExceptionConversionDelegate; @@ -18,6 +19,11 @@ import org.hibernate.query.TrimSpec; import org.hibernate.sql.ForUpdateFragment; import org.hibernate.sql.JoinFragment; import org.hibernate.sql.Sybase11JoinFragment; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; import org.hibernate.type.descriptor.sql.TinyIntTypeDescriptor; @@ -70,6 +76,17 @@ public class SybaseASEDialect extends SybaseDialect { registerSybaseKeywords(); } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new SybaseASESqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + /** * The Sybase ASE {@code BIT} type does not allow * null values, so we don't use it. diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java new file mode 100644 index 0000000000..450bc0b420 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java @@ -0,0 +1,104 @@ +/* + * 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; + +import org.hibernate.FetchClauseType; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for Sybase ASE. + * + * @author Christian Beikov + */ +public class SybaseASESqlAstTranslator extends AbstractSqlAstTranslator { + + public SybaseASESqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // Sybase ASE does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // Sybase ASE does not support this, but it can be emulated + } + + @Override + protected void visitSqlSelections(SelectClause selectClause) { + if ( supportsTopClause() ) { + renderTopClause( (QuerySpec) getQueryPartStack().getCurrent(), true ); + } + super.visitSqlSelections( selectClause ); + } + + @Override + protected void renderFetchPlusOffsetExpression( + Expression fetchClauseExpression, + Expression offsetClauseExpression, + int offset) { + renderFetchPlusOffsetExpressionAsSingleParameter( fetchClauseExpression, offsetClauseExpression, offset ); + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + assertRowsOnlyFetchClauseType( queryPart ); + if ( !queryPart.isRoot() && queryPart.hasOffsetOrFetchClause() ) { + if ( queryPart.getFetchClauseExpression() != null && !supportsTopClause() || queryPart.getOffsetClauseExpression() != null ) { + throw new IllegalArgumentException( "Can't emulate offset fetch clause in subquery" ); + } + } + } + + @Override + protected void renderFetchExpression(Expression fetchExpression) { + if ( supportsParameterOffsetFetchExpression() ) { + super.renderFetchExpression( fetchExpression ); + } + else { + renderExpressionAsLiteral( fetchExpression, getJdbcParameterBindings() ); + } + } + + @Override + protected void renderOffsetExpression(Expression offsetExpression) { + if ( supportsParameterOffsetFetchExpression() ) { + super.renderOffsetExpression( offsetExpression ); + } + else { + renderExpressionAsLiteral( offsetExpression, getJdbcParameterBindings() ); + } + } + + @Override + protected boolean needsRowsToSkip() { + return true; + } + + @Override + protected boolean needsMaxRows() { + return !supportsTopClause(); + } + + private boolean supportsTopClause() { + return getDialect().getVersion() >= 1250; + } + + private boolean supportsParameterOffsetFetchExpression() { + return false; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseAnywhereDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseAnywhereDialect.java index 0459d3e1ee..c47d0e904d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseAnywhereDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseAnywhereDialect.java @@ -12,6 +12,12 @@ import org.hibernate.dialect.identity.SybaseAnywhereIdentityColumnSupport; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.TopLimitHandler; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.type.descriptor.sql.BitTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; @@ -56,6 +62,17 @@ public class SybaseAnywhereDialect extends SybaseDialect { registerColumnType( Types.VARBINARY, "long binary)" ); } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new SybaseAnywhereSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + @Override public boolean supportsTimezoneTypes() { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseAnywhereSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseAnywhereSqlAstTranslator.java new file mode 100644 index 0000000000..37c08e7a65 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseAnywhereSqlAstTranslator.java @@ -0,0 +1,84 @@ +/* + * 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; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for Sybase Anywhere. + * + * @author Christian Beikov + */ +public class SybaseAnywhereSqlAstTranslator extends AbstractSqlAstTranslator { + + public SybaseAnywhereSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + protected boolean needsRowsToSkip() { + return getDialect().getVersion() < 900; + } + + @Override + protected void renderFetchPlusOffsetExpression( + Expression fetchClauseExpression, + Expression offsetClauseExpression, + int offset) { + renderFetchPlusOffsetExpressionAsSingleParameter( fetchClauseExpression, offsetClauseExpression, offset ); + } + + @Override + protected void visitSqlSelections(SelectClause selectClause) { + if ( getDialect().getVersion() < 900 ) { + renderTopClause( (QuerySpec) getQueryPartStack().getCurrent(), true ); + } + else { + renderTopStartAtClause( (QuerySpec) getQueryPartStack().getCurrent() ); + } + super.visitSqlSelections( selectClause ); + } + + @Override + protected void renderTopClause(QuerySpec querySpec, boolean addOffset) { + assertRowsOnlyFetchClauseType( querySpec ); + super.renderTopClause( querySpec, addOffset ); + } + + @Override + protected void renderTopStartAtClause(QuerySpec querySpec) { + assertRowsOnlyFetchClauseType( querySpec ); + super.renderTopStartAtClause( querySpec ); + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + // Sybase Anywhere only supports the TOP clause + if ( getDialect().getVersion() < 900 && !queryPart.isRoot() + && useOffsetFetchClause( queryPart ) && queryPart.getOffsetClauseExpression() != null ) { + throw new IllegalArgumentException( "Can't emulate offset clause in subquery" ); + } + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // Sybase Anywhere does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // Sybase Anywhere does not support this, but it can be emulated + } +} 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 0fe9b6efc4..5821ab325f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java @@ -8,8 +8,14 @@ package org.hibernate.dialect; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.type.descriptor.sql.BlobTypeDescriptor; import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; @@ -45,6 +51,17 @@ public class SybaseDialect extends AbstractTransactSQLDialect { registerColumnType( Types.BIGINT, "numeric(19,0)" ); } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new SybaseSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + @Override public int getVersion() { return version; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseSqlAstTranslator.java new file mode 100644 index 0000000000..86818cb539 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseSqlAstTranslator.java @@ -0,0 +1,55 @@ +/* + * 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; + +import org.hibernate.FetchClauseType; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for Sybase. + * + * @author Christian Beikov + */ +public class SybaseSqlAstTranslator extends AbstractSqlAstTranslator { + + public SybaseSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // Sybase does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // Sybase does not support this, but it can be emulated + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + assertRowsOnlyFetchClauseType( queryPart ); + if ( !queryPart.isRoot() && queryPart.getOffsetClauseExpression() != null ) { + throw new IllegalArgumentException( "Can't emulate offset clause in subquery" ); + } + } + + @Override + protected boolean needsRowsToSkip() { + return true; + } + + @Override + protected boolean needsMaxRows() { + return true; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/TeradataDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/TeradataDialect.java index f3ce86ef96..ce06c4964f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/TeradataDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/TeradataDialect.java @@ -19,6 +19,7 @@ import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.TopLimitHandler; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor; import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; import org.hibernate.mapping.Column; @@ -34,6 +35,11 @@ import org.hibernate.query.sqm.mutation.internal.idtable.IdTable; import org.hibernate.query.sqm.mutation.internal.idtable.TempIdTableExporter; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.sql.ForUpdateFragment; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.internal.StandardIndexExporter; import org.hibernate.tool.schema.spi.Exporter; import org.hibernate.type.StandardBasicTypes; @@ -116,6 +122,17 @@ public class TeradataDialect extends Dialect { } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new TeradataSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + @Override public int getVersion() { return version; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/TeradataSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/TeradataSqlAstTranslator.java new file mode 100644 index 0000000000..1782349451 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/TeradataSqlAstTranslator.java @@ -0,0 +1,66 @@ +/* + * 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; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for Teradata. + * + * @author Christian Beikov + */ +public class TeradataSqlAstTranslator extends AbstractSqlAstTranslator { + + public TeradataSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + protected boolean needsRowsToSkip() { + return true; + } + + @Override + protected void renderFetchPlusOffsetExpression( + Expression fetchClauseExpression, + Expression offsetClauseExpression, + int offset) { + renderFetchPlusOffsetExpressionAsSingleParameter( fetchClauseExpression, offsetClauseExpression, offset ); + } + + @Override + protected void visitSqlSelections(SelectClause selectClause) { + renderTopClause( (QuerySpec) getQueryPartStack().getCurrent(), true ); + super.visitSqlSelections( selectClause ); + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + // Teradata only supports the TOP clause + if ( !queryPart.isRoot() && queryPart.getOffsetClauseExpression() != null ) { + throw new IllegalArgumentException( "Can't emulate offset clause in subquery" ); + } + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // Teradata does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // Teradata does not support this, but it can be emulated + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/TimesTenDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/TimesTenDialect.java index ef4e35ea91..fd4ce122aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/TimesTenDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/TimesTenDialect.java @@ -14,6 +14,7 @@ import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.TimesTenLimitHandler; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.dialect.sequence.TimesTenSequenceSupport; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.Lockable; @@ -24,6 +25,11 @@ import org.hibernate.query.sqm.mutation.internal.idtable.GlobalTemporaryTableStr import org.hibernate.query.sqm.mutation.internal.idtable.IdTable; import org.hibernate.query.sqm.mutation.internal.idtable.TempIdTableExporter; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorTimesTenDatabaseImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.type.StandardBasicTypes; @@ -129,6 +135,17 @@ public class TimesTenDialect extends Dialect { ).setArgumentListSignature("(pattern, string[, start])"); } + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new TimesTenSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + @Override public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType) { switch (unit) { @@ -262,11 +279,6 @@ public class TimesTenDialect extends Dialect { } } - @Override - public boolean supportsUnionAll() { - return true; - } - @Override public boolean supportsCircularCascadeDeleteConstraints() { return false; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/TimesTenSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/TimesTenSqlAstTranslator.java new file mode 100644 index 0000000000..19b33070e8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/TimesTenSqlAstTranslator.java @@ -0,0 +1,58 @@ +/* + * 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; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for TimesTen. + * + * @author Christian Beikov + */ +public class TimesTenSqlAstTranslator extends AbstractSqlAstTranslator { + + public TimesTenSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // TimesTen does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // TimesTen does not support this, but it can be emulated + } + + @Override + protected void visitSqlSelections(SelectClause selectClause) { + renderRowsToClause( (QuerySpec) getQueryPartStack().getCurrent() ); + super.visitSqlSelections( selectClause ); + } + + @Override + protected void renderFetchPlusOffsetExpression( + Expression fetchClauseExpression, + Expression offsetClauseExpression, + int offset) { + renderFetchPlusOffsetExpressionAsSingleParameter( fetchClauseExpression, offsetClauseExpression, offset ); + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + // TimesTen uses ROWS TO clause + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/TypeNames.java b/hibernate-core/src/main/java/org/hibernate/dialect/TypeNames.java index b4f3f41861..324d0ac5de 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/TypeNames.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/TypeNames.java @@ -101,12 +101,14 @@ public final class TypeNames { * the default type name otherwise */ public String get(int typeCode, Long size, Integer precision, Integer scale) { - final Map map = weighted.get( typeCode ); - if ( map != null && map.size() > 0 ) { - // iterate entries ordered by capacity to find first fit - for ( Map.Entry entry: map.entrySet() ) { - if ( size <= entry.getKey() ) { - return replace( entry.getValue(), size, precision, scale ); + if ( size != null ) { + final Map map = weighted.get( typeCode ); + if ( map != null && map.size() > 0 ) { + // iterate entries ordered by capacity to find first fit + for ( Map.Entry entry : map.entrySet() ) { + if ( size <= entry.getKey() ) { + return replace( entry.getValue(), size, precision, scale ); + } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractLimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractLimitHandler.java index cf0fa3e4c2..8155b19286 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractLimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractLimitHandler.java @@ -12,6 +12,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.hibernate.engine.spi.RowSelection; +import org.hibernate.query.Limit; import static java.util.regex.Pattern.CASE_INSENSITIVE; import static java.util.regex.Pattern.compile; @@ -270,6 +271,143 @@ public abstract class AbstractLimitHandler implements LimitHandler { return convertToFirstRowValue( selection.getFirstRow() ); } + @Override + public String processSql(String sql, Limit limit) { + throw new UnsupportedOperationException( "Paged queries not supported by " + getClass().getName() ); + } + + @Override + public int bindLimitParametersAtStartOfQuery(Limit limit, PreparedStatement statement, int index) + throws SQLException { + return bindLimitParametersFirst() + ? bindLimitParameters( limit, statement, index ) + : 0; + } + + @Override + public int bindLimitParametersAtEndOfQuery(Limit limit, PreparedStatement statement, int index) + throws SQLException { + return !bindLimitParametersFirst() + ? bindLimitParameters( limit, statement, index ) + : 0; + } + + @Override + public void setMaxRows(Limit limit, PreparedStatement statement) throws SQLException { + } + + /** + * Default implementation of binding parameter values needed by the LIMIT clause. + * + * @param limit the limit. + * @param statement Statement to which to bind limit parameter values. + * @param index Index from which to start binding. + * @return The number of parameter values bound. + * @throws SQLException Indicates problems binding parameter values. + */ + protected final int bindLimitParameters(Limit limit, PreparedStatement statement, int index) + throws SQLException { + + if ( !supportsVariableLimit() ) { + //never any parameters to bind + return 0; + } + + final boolean hasMaxRows = hasMaxRows( limit ); + final boolean hasFirstRow = hasFirstRow( limit ); + + final boolean bindLimit + = hasMaxRows && supportsLimit() + || forceLimitUsage(); + final boolean bindOffset + = hasFirstRow && supportsOffset() + || hasFirstRow && hasMaxRows && supportsLimitOffset(); + + if ( !bindLimit && !bindOffset ) { + //no parameters to bind this time + return 0; + } + + final boolean reverse = bindLimitParametersInReverseOrder(); + + if ( bindOffset ) { + statement.setInt( + index + ( reverse || !bindLimit ? 1 : 0 ), + getFirstRow( limit ) + ); + } + if ( bindLimit ) { + statement.setInt( + index + ( reverse || !bindOffset ? 0 : 1 ), + getMaxOrLimit( limit ) + ); + } + + return bindOffset && bindLimit ? 2 : 1; + } + + /** + * Is a max row limit indicated? + * + * @param limit The limit + * + * @return Whether a max row limit was indicated + */ + public static boolean hasMaxRows(Limit limit) { + return limit != null + && limit.getMaxRows() != null + && limit.getMaxRows() > 0; + } + + /** + * Is a first row limit indicated? + * + * @param limit The limit + * + * @return Whether a first row limit was indicated + */ + public static boolean hasFirstRow(Limit limit) { + return limit != null + && limit.getFirstRow() != null + && limit.getFirstRow() > 0; + } + + /** + * Some dialect-specific LIMIT clauses require the maximum last row number + * (aka, first_row_number + total_row_count), while others require the maximum + * returned row count (the total maximum number of rows to return). + * + * @param limit The limit + * + * @return The appropriate value to bind into the limit clause. + */ + protected final int getMaxOrLimit(Limit limit) { + if ( limit == null || limit.getMaxRows() == null ) { + return Integer.MAX_VALUE; + } + final int firstRow = getFirstRow( limit ); + final int maxRows = limit.getMaxRows(); + final int maxOrLimit = useMaxForLimit() + ? maxRows + firstRow //TODO: maxRows + firstRow - 1, surely? + : maxRows; + // Use Integer.MAX_VALUE on overflow + return maxOrLimit < 0 ? Integer.MAX_VALUE : maxOrLimit; + } + + /** + * Retrieve the indicated first row for pagination + * + * @param limit The limit + * + * @return The first row + */ + protected final int getFirstRow(Limit limit) { + if ( limit == null || limit.getFirstRow() == null ) { + return 0; + } + return convertToFirstRowValue( limit.getFirstRow() ); + } + /** * Insert a fragment of SQL right after * {@code SELECT}, but before {@code DISTINCT} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractNoOffsetLimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractNoOffsetLimitHandler.java index 67b2bb87b7..aef30c0bb1 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractNoOffsetLimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractNoOffsetLimitHandler.java @@ -7,6 +7,7 @@ package org.hibernate.dialect.pagination; import org.hibernate.engine.spi.RowSelection; +import org.hibernate.query.Limit; import static java.lang.String.valueOf; @@ -34,13 +35,26 @@ public abstract class AbstractNoOffsetLimitHandler extends AbstractLimitHandler @Override public String processSql(String sql, RowSelection selection) { - if ( !hasMaxRows( selection) ) { + if ( !hasMaxRows( selection ) ) { return sql; } String limitClause = limitClause(); if ( !supportsVariableLimit() ) { - String limit = valueOf( getMaxOrLimit(selection) ); - limitClause = limitClause.replace( "?", limit ); + String limitLiteral = valueOf( getMaxOrLimit(selection) ); + limitClause = limitClause.replace( "?", limitLiteral ); + } + return insert( limitClause, sql ); + } + + @Override + public String processSql(String sql, Limit limit) { + if ( !hasMaxRows( limit ) ) { + return sql; + } + String limitClause = limitClause(); + if ( !supportsVariableLimit() ) { + String limitLiteral = valueOf( getMaxOrLimit( limit ) ); + limitClause = limitClause.replace( "?", limitLiteral ); } return insert( limitClause, sql ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractSimpleLimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractSimpleLimitHandler.java index 227bb9cdac..fa49752fb4 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractSimpleLimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractSimpleLimitHandler.java @@ -7,6 +7,7 @@ package org.hibernate.dialect.pagination; import org.hibernate.engine.spi.RowSelection; +import org.hibernate.query.Limit; /** * Superclass for simple {@link LimitHandler}s that don't @@ -20,10 +21,18 @@ public abstract class AbstractSimpleLimitHandler extends AbstractLimitHandler { @Override public String processSql(String sql, RowSelection selection) { - if ( !hasMaxRows( selection) ) { + if ( !hasMaxRows( selection ) ) { return sql; } - return insert( limitClause( hasFirstRow(selection) ), sql ); + return insert( limitClause( hasFirstRow( selection ) ), sql ); + } + + @Override + public String processSql(String sql, Limit limit) { + if ( !hasMaxRows( limit ) ) { + return sql; + } + return insert( limitClause( hasFirstRow( limit ) ), sql ); } protected String insert(String limitClause, String sql) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyDB2LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyDB2LimitHandler.java index e8870e8e36..7ad039691e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyDB2LimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyDB2LimitHandler.java @@ -7,6 +7,7 @@ package org.hibernate.dialect.pagination; import org.hibernate.engine.spi.RowSelection; +import org.hibernate.query.Limit; /** * A {@link LimitHandler} for DB2. Uses {@code FETCH FIRST n ROWS ONLY}, @@ -30,12 +31,33 @@ public class LegacyDB2LimitHandler extends AbstractLimitHandler { else { //on DB2, offset/fetch comes after all the //various "for update"ish clauses - return insertAtEnd( fetchFirstRows(selection), sql ); + return insertAtEnd( fetchFirstRows( selection ), sql ); } } - private String fetchFirstRows(RowSelection selection) { - return " fetch first " + getMaxOrLimit( selection ) + " rows only"; + private String fetchFirstRows(RowSelection limit) { + return " fetch first " + getMaxOrLimit( limit ) + " rows only"; + } + + @Override + public String processSql(String sql, Limit limit) { + if ( hasFirstRow( limit ) ) { + //nest the main query in an outer select + return "select * from ( select row_.*, rownumber() over(order by order of row_) as rownumber_ from ( " + + sql + fetchFirstRows( limit ) + + " ) as row_ ) as query_ where rownumber_ > " + + limit.getFirstRow() + + " order by rownumber_"; + } + else { + //on DB2, offset/fetch comes after all the + //various "for update"ish clauses + return insertAtEnd( fetchFirstRows( limit ), sql ); + } + } + + private String fetchFirstRows(Limit limit) { + return " fetch first " + getMaxOrLimit( limit ) + " rows only"; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyLimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyLimitHandler.java index 4716e993f9..1762bab44e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyLimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyLimitHandler.java @@ -8,6 +8,7 @@ package org.hibernate.dialect.pagination; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.RowSelection; +import org.hibernate.query.Limit; /** * Stub {@link LimitHandler} that delegates all operations @@ -81,4 +82,20 @@ public class LegacyLimitHandler extends AbstractLimitHandler { getMaxOrLimit( selection ) ); } + + @Override + public String processSql(String sql, Limit limit) { + final boolean useLimitOffset + = supportsOffset() + && hasFirstRow( limit ) + || supportsLimit() + && supportsLimitOffset() + && hasFirstRow( limit ) + && hasMaxRows( limit ); + return dialect.getLimitString( + sql, + useLimitOffset ? getFirstRow( limit ) : 0, + getMaxOrLimit( limit ) + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyOracleLimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyOracleLimitHandler.java index 3881eec087..8152acb912 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyOracleLimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyOracleLimitHandler.java @@ -7,6 +7,7 @@ package org.hibernate.dialect.pagination; import org.hibernate.engine.spi.RowSelection; +import org.hibernate.query.Limit; import java.util.regex.Matcher; @@ -56,6 +57,41 @@ public class LegacyOracleLimitHandler extends AbstractLimitHandler { return pagingSelect.toString(); } + @Override + public String processSql(String sql, Limit limit) { + final boolean hasOffset = hasFirstRow( limit ); + sql = sql.trim(); + + String forUpdateClause = null; + Matcher forUpdateMatcher = getForUpdatePattern().matcher( sql ); + if ( forUpdateMatcher.find() ) { + int forUpdateIndex = forUpdateMatcher.start(); + // save 'for update ...' and then remove it + forUpdateClause = sql.substring( forUpdateIndex ); + sql = sql.substring( 0, forUpdateIndex ); + } + + final StringBuilder pagingSelect = new StringBuilder( sql.length() + 100 ); + if ( hasOffset ) { + pagingSelect.append( "select * from (select row_.*, rownum rownum_ from (" ).append( sql ); + if ( version < 9 ) { + pagingSelect.append( ") row_) where rownum_ <= ? and rownum_ > ?" ); + } + else { + pagingSelect.append( ") row_ where rownum <= ?) where rownum_ > ?" ); + } + } + else { + pagingSelect.append( "select * from (" ).append( sql ).append( ") where rownum <= ?" ); + } + + if ( forUpdateClause != null ) { + pagingSelect.append( forUpdateClause ); + } + + return pagingSelect.toString(); + } + @Override public boolean supportsLimit() { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java index a54b5741d5..71a09a83e7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java @@ -10,6 +10,8 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import org.hibernate.engine.spi.RowSelection; +import org.hibernate.query.Limit; +import org.hibernate.query.spi.QueryOptions; /** * Contract defining dialect-specific limit and offset handling. @@ -40,6 +42,58 @@ public interface LimitHandler { */ boolean supportsLimitOffset(); + default String processSql(String sql, Limit limit) { + return processSql( + sql, + limit == null ? null : new RowSelection( + limit.getFirstRow(), + limit.getMaxRows(), + null, + null + ) + ); + } + + default int bindLimitParametersAtStartOfQuery(Limit limit, PreparedStatement statement, int index) + throws SQLException { + return bindLimitParametersAtStartOfQuery( + limit == null ? null : new RowSelection( + limit.getFirstRow(), + limit.getMaxRows(), + null, + null + ), + statement, + index + ); + } + + default int bindLimitParametersAtEndOfQuery(Limit limit, PreparedStatement statement, int index) + throws SQLException { + return bindLimitParametersAtEndOfQuery( + limit == null ? null : new RowSelection( + limit.getFirstRow(), + limit.getMaxRows(), + null, + null + ), + statement, + index + ); + } + + default void setMaxRows(Limit limit, PreparedStatement statement) throws SQLException { + setMaxRows( + limit == null ? null : new RowSelection( + limit.getFirstRow(), + limit.getMaxRows(), + null, + null + ), + statement + ); + } + /** * Return processed SQL query. * @@ -47,7 +101,9 @@ public interface LimitHandler { * @param selection the selection criteria for rows. * * @return Query statement with LIMIT clause applied. + * @deprecated todo (6.0): remove in favor of Limit version? */ + @Deprecated String processSql(String sql, RowSelection selection); /** @@ -60,7 +116,9 @@ public interface LimitHandler { * @param index Index from which to start binding. * @return The number of parameter values bound. * @throws SQLException Indicates problems binding parameter values. + * @deprecated todo (6.0): remove in favor of Limit version? */ + @Deprecated int bindLimitParametersAtStartOfQuery(RowSelection selection, PreparedStatement statement, int index) throws SQLException; @@ -74,7 +132,9 @@ public interface LimitHandler { * @param index Index from which to start binding. * @return The number of parameter values bound. * @throws SQLException Indicates problems binding parameter values. + * @deprecated todo (6.0): remove in favor of Limit version? */ + @Deprecated int bindLimitParametersAtEndOfQuery(RowSelection selection, PreparedStatement statement, int index) throws SQLException; @@ -86,6 +146,8 @@ public interface LimitHandler { * @param selection the selection criteria for rows. * @param statement Statement which number of returned rows shall be limited. * @throws SQLException Indicates problems while limiting maximum rows returned. + * @deprecated todo (6.0): remove in favor of Limit version? */ + @Deprecated void setMaxRows(RowSelection selection, PreparedStatement statement) throws SQLException; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/NoopLimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/NoopLimitHandler.java new file mode 100644 index 0000000000..a34e22a854 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/NoopLimitHandler.java @@ -0,0 +1,89 @@ +/* + * 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.dialect.pagination; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.regex.Pattern; + +import org.hibernate.engine.spi.RowSelection; +import org.hibernate.query.Limit; + +import static java.util.regex.Pattern.CASE_INSENSITIVE; +import static java.util.regex.Pattern.compile; + +/** + * Handler not supporting query LIMIT clause. JDBC API is used to set maximum number of returned rows. + * + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public class NoopLimitHandler extends AbstractLimitHandler { + + public static final NoopLimitHandler INSTANCE = new NoopLimitHandler(); + + @Override + public String processSql(String sql, RowSelection selection) { + return sql; + } + + @Override + public int bindLimitParametersAtStartOfQuery(RowSelection selection, PreparedStatement statement, int index) { + return 0; + } + + @Override + public int bindLimitParametersAtEndOfQuery(RowSelection selection, PreparedStatement statement, int index) { + return 0; + } + + @Override + public void setMaxRows(RowSelection selection, PreparedStatement statement) throws SQLException { + if ( selection != null && selection.getMaxRows() != null && selection.getMaxRows() > 0 ) { + final int maxRows = selection.getMaxRows() + convertToFirstRowValue( + selection.getFirstRow() == null ? 0 : selection.getFirstRow() + ); + // Use Integer.MAX_VALUE on overflow + if ( maxRows < 0 ) { + statement.setMaxRows( Integer.MAX_VALUE ); + } + else { + statement.setMaxRows( maxRows ); + } + } + } + + @Override + public String processSql(String sql, Limit limit) { + return sql; + } + + @Override + public int bindLimitParametersAtStartOfQuery(Limit limit, PreparedStatement statement, int index) { + return 0; + } + + @Override + public int bindLimitParametersAtEndOfQuery(Limit limit, PreparedStatement statement, int index) { + return 0; + } + + @Override + public void setMaxRows(Limit limit, PreparedStatement statement) throws SQLException { + if ( limit != null && limit.getMaxRows() != null && limit.getMaxRows() > 0 ) { + final int maxRows = limit.getMaxRows() + convertToFirstRowValue( + limit.getFirstRow() == null ? 0 : limit.getFirstRow() + ); + // Use Integer.MAX_VALUE on overflow + if ( maxRows < 0 ) { + statement.setMaxRows( Integer.MAX_VALUE ); + } + else { + statement.setMaxRows( maxRows ); + } + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/OffsetFetchLimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/OffsetFetchLimitHandler.java index 0e1329d8d6..205a4704d4 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/OffsetFetchLimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/OffsetFetchLimitHandler.java @@ -7,6 +7,7 @@ package org.hibernate.dialect.pagination; import org.hibernate.engine.spi.RowSelection; +import org.hibernate.query.Limit; /** * A {@link LimitHandler} for databases which support the @@ -71,6 +72,52 @@ public class OffsetFetchLimitHandler extends AbstractLimitHandler { return insert( offsetFetch.toString(), sql ); } + @Override + public String processSql(String sql, Limit limit) { + + boolean hasFirstRow = hasFirstRow(limit); + boolean hasMaxRows = hasMaxRows(limit); + + if ( !hasFirstRow && !hasMaxRows ) { + return sql; + } + + StringBuilder offsetFetch = new StringBuilder(); + + begin(sql, offsetFetch, hasFirstRow, hasMaxRows); + + if ( hasFirstRow ) { + offsetFetch.append( " offset " ); + if ( supportsVariableLimit() ) { + offsetFetch.append( "?" ); + } + else { + offsetFetch.append( limit.getFirstRow() ); + } + if ( !isIngres() ) { + offsetFetch.append( " rows" ); + } + + } + if ( hasMaxRows ) { + if ( hasFirstRow ) { + offsetFetch.append( " fetch next " ); + } + else { + offsetFetch.append( " fetch first " ); + } + if ( supportsVariableLimit() ) { + offsetFetch.append( "?" ); + } + else { + offsetFetch.append( getMaxOrLimit( limit ) ); + } + offsetFetch.append( " rows only" ); + } + + return insert( offsetFetch.toString(), sql ); + } + void begin(String sql, StringBuilder offsetFetch, boolean hasFirstRow, boolean hasMaxRows) {} String insert(String offsetFetch, String sql) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java index 739a54da27..6c3d1267bd 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java @@ -15,6 +15,7 @@ import java.util.regex.Pattern; import org.hibernate.engine.spi.RowSelection; import org.hibernate.internal.util.StringHelper; +import org.hibernate.query.Limit; import static java.util.regex.Pattern.CASE_INSENSITIVE; import static java.util.regex.Pattern.compile; @@ -132,6 +133,84 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler { : 0; } + /** + * When the offset of the given {@link RowSelection} is {@literal 0}, + * add a {@code top(?)} clause to the given SQL query. When the offset + * is non-zero, wrap the given query in an outer query that limits the + * results using the {@code row_number()} window function. + * + *
+	 * with query_ as (
+	 *     select row_.*, row_number()
+	 *         over (order by current_timestamp) AS rownumber_
+	 *     from ( [original-query] ) row_
+	 * )
+	 * select [alias-list] from query_
+	 * where rownumber_ >= ? and rownumber_ < ?
+	 * 
+ * + * Where {@code [original-query]} is the original SQL query, with a + * {@code top()} clause added iff the query has an {@code order by} + * clause, and with generated aliases added to any elements of the + * projection list that don't already have aliases, and + * {@code [alias-list]} is a list of aliases in the projection list. + * + * @return A new SQL statement + */ + @Override + public String processSql(String sql, Limit limit) { + sql = sql.trim(); + if ( sql.endsWith(";") ) { + sql = sql.substring( 0, sql.length()-1 ); + } + + final int selectOffset = Keyword.SELECT.rootOffset( sql ); + final int afterSelectOffset = Keyword.SELECT.endOffset( sql, selectOffset ); + final int fromOffset = Keyword.FROM.rootOffset( sql ); //TODO: what if there is no 'from' clause?! + + boolean hasCommonTables = Keyword.WITH.occursAt( sql, 0 ); + boolean hasOrderBy = Keyword.ORDER_BY.rootOffset( sql ) > 0; + boolean hasFirstRow = hasFirstRow( limit ); + + final StringBuilder result = new StringBuilder( sql ); + + if ( !hasFirstRow || hasOrderBy ) { + result.insert( afterSelectOffset, " top(?)" ); + topAdded = true; + } + + if ( hasFirstRow ) { + + // enclose original SQL statement with outer query + // that provides the rownumber_ column + + String aliases = selectAliases( sql, afterSelectOffset, fromOffset, result ); //warning: changes result by side-effect + result.insert( selectOffset, ( hasCommonTables ? "," : "with" ) + + " query_ as (select row_.*, row_number() over (order by current_timestamp) as rownumber_ from (" ) + .append( ") row_) select " ).append( aliases ) + .append( " from query_ where rownumber_ >= ? and rownumber_ < ?" ); + } + + return result.toString(); + } + + @Override + public int bindLimitParametersAtStartOfQuery(Limit limit, PreparedStatement statement, int index) throws SQLException { + if ( topAdded ) { + // bind parameter to top(?) + statement.setInt( index, getMaxOrLimit( limit ) - 1 ); + return 1; + } + return 0; + } + + @Override + public int bindLimitParametersAtEndOfQuery(Limit limit, PreparedStatement statement, int index) throws SQLException { + return hasFirstRow( limit ) + ? super.bindLimitParametersAtEndOfQuery( limit, statement, index ) + : 0; + } + /** * Add any missing aliases to the given {@code select} list, and return * comma-separated list of all aliases in the list, unless the given list diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2012LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2012LimitHandler.java index f005220ea6..985208a1ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2012LimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2012LimitHandler.java @@ -52,7 +52,8 @@ public class SQLServer2012LimitHandler extends OffsetFetchLimitHandler { //if we can find the end of the select //clause, we will add a dummy column to //it below, so order by that column - offsetFetch.append("zero_"); + // Always need an order by clause: https://blog.jooq.org/2014/05/13/sql-server-trick-circumvent-missing-order-by-clause/ + offsetFetch.append("@@version"); } else { //otherwise order by the first column @@ -66,22 +67,4 @@ public class SQLServer2012LimitHandler extends OffsetFetchLimitHandler { offsetFetch.append(" offset 0 rows"); } } - - @Override - String insert(String offsetFetch, String sql) { - String result = super.insert( offsetFetch, sql ); - if ( Keyword.ORDER_BY.rootOffset( sql ) <= 0 ) { - int from = Keyword.FROM.rootOffset( sql ); - if ( from > 0 ) { - //insert the dummy column at the end of - //the select list (don't add it at the - //start, 'cos that would mess up reading - //results by index) - return new StringBuilder( result ) - .insert( from, ", 0 as zero_ " ) - .toString(); - } - } - return result; - } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SkipFirstLimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SkipFirstLimitHandler.java index 6043f587e7..d0e326c7f7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SkipFirstLimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SkipFirstLimitHandler.java @@ -7,6 +7,7 @@ package org.hibernate.dialect.pagination; import org.hibernate.engine.spi.RowSelection; +import org.hibernate.query.Limit; /** * A {@link LimitHandler} for Informix which supports the syntax @@ -56,6 +57,40 @@ public class SkipFirstLimitHandler extends AbstractLimitHandler { return insertAfterSelect( sql, skipFirst.toString() ); } + @Override + public String processSql(String sql, Limit limit) { + + boolean hasFirstRow = hasFirstRow( limit ); + boolean hasMaxRows = hasMaxRows( limit ); + + if ( !hasFirstRow && !hasMaxRows ) { + return sql; + } + + StringBuilder skipFirst = new StringBuilder(); + + if ( supportsVariableLimit() ) { + if ( hasFirstRow ) { + skipFirst.append( " skip ?" ); + } + if ( hasMaxRows ) { + skipFirst.append( " first ?" ); + } + } + else { + if ( hasFirstRow ) { + skipFirst.append( " skip " ) + .append( limit.getFirstRow() ); + } + if ( hasMaxRows ) { + skipFirst.append( " first " ) + .append( getMaxOrLimit( limit ) ); + } + } + + return insertAfterSelect( sql, skipFirst.toString() ); + } + @Override public final boolean supportsLimit() { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/sequence/MariaDBSequenceSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/sequence/MariaDBSequenceSupport.java new file mode 100644 index 0000000000..fd772771a5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/sequence/MariaDBSequenceSupport.java @@ -0,0 +1,22 @@ +/* + * 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.dialect.sequence; + +/** + * Sequence support for {@link org.hibernate.dialect.MariaDBDialect}. + * + * @author Christian Beikov + */ +public final class MariaDBSequenceSupport extends ANSISequenceSupport { + + public static final SequenceSupport INSTANCE = new MariaDBSequenceSupport(); + + @Override + public boolean sometimesNeedsStartingValue() { + return true; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/RowSelection.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/RowSelection.java index fbf01af381..0bbb3980ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/RowSelection.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/RowSelection.java @@ -10,7 +10,9 @@ package org.hibernate.engine.spi; * Represents a selection criteria for rows in a JDBC {@link java.sql.ResultSet} * * @author Gavin King + * @deprecated todo (6.0): remove in favor of Limit within QueryOptions? */ +@Deprecated public final class RowSelection { private Integer firstRow; private Integer maxRows; diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java index f988bc569e..cb21431812 100755 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java @@ -275,6 +275,18 @@ public final class CollectionHelper { return objects == null || objects.length == 0; } + public static List listOf(T value1) { + final List list = new ArrayList<>( 1 ); + list.add( value1 ); + return list; + } + + public static List listOf(T... values) { + final List list = new ArrayList<>( values.length ); + Collections.addAll( list, values ); + return list; + } + public static Set setOf(T... values) { final HashSet set = new HashSet<>( determineProperSizing( values.length ) ); Collections.addAll( set, values ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractNaturalIdLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractNaturalIdLoader.java index 5932cd5103..3bd15b996c 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractNaturalIdLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractNaturalIdLoader.java @@ -99,11 +99,10 @@ public abstract class AbstractNaturalIdLoader implements NaturalIdLoader { sessionFactory ); - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlSelect ); - final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); - applyNaturalIdAsJdbcParameters( bindValue, jdbcParameters, jdbcParamBindings, session ); + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlSelect ) + .translate( jdbcParamBindings, QueryOptions.NONE ); //noinspection unchecked final List results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list( @@ -179,8 +178,6 @@ public abstract class AbstractNaturalIdLoader implements NaturalIdLoader { sessionFactory ); - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlSelect ); - final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); applyNaturalIdAsJdbcParameters( bindValue, @@ -188,6 +185,8 @@ public abstract class AbstractNaturalIdLoader implements NaturalIdLoader { jdbcParamBindings, session ); + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlSelect ) + .translate( jdbcParamBindings, QueryOptions.NONE ); final List results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list( jdbcSelect, @@ -252,10 +251,7 @@ public abstract class AbstractNaturalIdLoader implements NaturalIdLoader { final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlSelect ); - final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); - int offset = jdbcParamBindings.registerParametersForEachJdbcValue( id, Clause.WHERE, @@ -264,6 +260,8 @@ public abstract class AbstractNaturalIdLoader implements NaturalIdLoader { session ); assert offset == jdbcParameters.size(); + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlSelect ) + .translate( jdbcParamBindings, QueryOptions.NONE ); final List results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list( jdbcSelect, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionElementLoaderByIndex.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionElementLoaderByIndex.java index 74a951dbe1..60e4b0d971 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionElementLoaderByIndex.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionElementLoaderByIndex.java @@ -7,7 +7,6 @@ package org.hibernate.loader.ast.internal; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import org.hibernate.LockOptions; @@ -30,7 +29,6 @@ import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.Callback; import org.hibernate.sql.exec.spi.ExecutionContext; @@ -116,11 +114,7 @@ public class CollectionElementLoaderByIndex implements Loader { final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ) - .translate( sqlAst ); - final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( keyJdbcCount ); - jdbcSelect.bindFilterJdbcParameters( jdbcParameterBindings ); int offset = jdbcParameterBindings.registerParametersForEachJdbcValue( key, @@ -138,6 +132,8 @@ public class CollectionElementLoaderByIndex implements Loader { session ); assert offset == jdbcParameters.size(); + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlAst ) + .translate( jdbcParameterBindings, QueryOptions.NONE ); List list = jdbcServices.getJdbcSelectExecutor().list( jdbcSelect, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderBatchKey.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderBatchKey.java index d9cf144560..e7ed38e61d 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderBatchKey.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderBatchKey.java @@ -7,7 +7,6 @@ package org.hibernate.loader.ast.internal; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import org.hibernate.LockOptions; @@ -27,7 +26,6 @@ import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.Callback; import org.hibernate.sql.exec.spi.ExecutionContext; @@ -159,7 +157,8 @@ public class CollectionLoaderBatchKey implements CollectionLoader { final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst ); + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlAst ) + .translate( null, QueryOptions.NONE ); final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( keyJdbcCount * smallBatchLength ); jdbcSelect.bindFilterJdbcParameters( jdbcParameterBindings ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSingleKey.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSingleKey.java index 36d59e3fd6..16a0ee0caf 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSingleKey.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSingleKey.java @@ -7,7 +7,6 @@ package org.hibernate.loader.ast.internal; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import org.hibernate.LockOptions; @@ -26,7 +25,6 @@ import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.Callback; import org.hibernate.sql.exec.spi.ExecutionContext; @@ -95,11 +93,7 @@ public class CollectionLoaderSingleKey implements CollectionLoader { final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst ); - final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( keyJdbcCount ); - jdbcSelect.bindFilterJdbcParameters( jdbcParameterBindings ); - int offset = jdbcParameterBindings.registerParametersForEachJdbcValue( key, Clause.WHERE, @@ -108,6 +102,8 @@ public class CollectionLoaderSingleKey implements CollectionLoader { session ); assert offset == jdbcParameters.size(); + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlAst ) + .translate( jdbcParameterBindings, QueryOptions.NONE ); jdbcServices.getJdbcSelectExecutor().list( jdbcSelect, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSubSelectFetch.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSubSelectFetch.java index 7b3abe718a..86bc0afbcd 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSubSelectFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSubSelectFetch.java @@ -68,7 +68,8 @@ public class CollectionLoaderSubSelectFetch implements CollectionLoader { final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst ); + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlAst ) + .translate( subselect.getLoadingJdbcParameterBindings(), QueryOptions.NONE ); jdbcServices.getJdbcSelectExecutor().list( jdbcSelect, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/DatabaseSnapshotExecutor.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/DatabaseSnapshotExecutor.java index 82285263c7..c39464a329 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/DatabaseSnapshotExecutor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/DatabaseSnapshotExecutor.java @@ -7,7 +7,6 @@ package org.hibernate.loader.ast.internal; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import org.hibernate.LockMode; @@ -32,7 +31,6 @@ import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.JdbcParameterImpl; import org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl; @@ -185,7 +183,8 @@ class DatabaseSnapshotExecutor { final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( selectStatement ); + jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, selectStatement ) + .translate( null, QueryOptions.NONE ); } Object[] loadDatabaseSnapshot(Object id, SharedSessionContractImplementor session) { 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 3c27ec6baa..2dd6986e1d 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 @@ -64,6 +64,7 @@ import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; import org.hibernate.sql.ast.tree.predicate.InListPredicate; import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.JdbcParameterImpl; @@ -882,7 +883,7 @@ public class LoaderSelectBuilder { ); } - private QuerySpec generateSubSelect( + private QueryPart generateSubSelect( PluralAttributeMapping attributeMapping, TableGroup rootTableGroup, SubselectFetch subselect, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java index 32a421328f..7e3000c20e 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java @@ -25,7 +25,7 @@ import org.hibernate.query.NavigablePath; import org.hibernate.query.ResultListTransformer; import org.hibernate.query.TupleTransformer; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.sqm.sql.internal.SqlAstQuerySpecProcessingStateImpl; +import org.hibernate.query.sqm.sql.internal.SqlAstQueryPartProcessingStateImpl; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; @@ -35,6 +35,7 @@ import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlAstProcessingState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; @@ -49,11 +50,10 @@ public class LoaderSqlAstCreationState List visitFetches(FetchParent fetchParent, QuerySpec querySpec, LoaderSqlAstCreationState creationState); } - private final QuerySpec querySpec; private final SqlAliasBaseManager sqlAliasBaseManager; private final boolean forceIdentifierSelection; private final SqlAstCreationContext sf; - private final SqlAstQuerySpecProcessingStateImpl processingState; + private final SqlAstQueryPartProcessingStateImpl processingState; private final FromClauseAccess fromClauseAccess; private final LockOptions lockOptions; private final FetchProcessor fetchProcessor; @@ -61,22 +61,21 @@ public class LoaderSqlAstCreationState private Set visitedAssociationKeys = new HashSet<>(); public LoaderSqlAstCreationState( - QuerySpec querySpec, + QueryPart queryPart, SqlAliasBaseManager sqlAliasBaseManager, FromClauseAccess fromClauseAccess, LockOptions lockOptions, FetchProcessor fetchProcessor, boolean forceIdentifierSelection, SqlAstCreationContext sf) { - this.querySpec = querySpec; this.sqlAliasBaseManager = sqlAliasBaseManager; this.fromClauseAccess = fromClauseAccess; this.lockOptions = lockOptions; this.fetchProcessor = fetchProcessor; this.forceIdentifierSelection = forceIdentifierSelection; this.sf = sf; - processingState = new SqlAstQuerySpecProcessingStateImpl( - querySpec, + this.processingState = new SqlAstQueryPartProcessingStateImpl( + queryPart, this, this, () -> Clause.IRRELEVANT @@ -99,10 +98,6 @@ public class LoaderSqlAstCreationState ); } - public QuerySpec getQuerySpec() { - return querySpec; - } - @Override public SqlAstCreationContext getCreationContext() { return sf; @@ -135,7 +130,7 @@ public class LoaderSqlAstCreationState @Override public List visitFetches(FetchParent fetchParent) { - return fetchProcessor.visitFetches( fetchParent, getQuerySpec(), this ); + return fetchProcessor.visitFetches( fetchParent, processingState.getInflightQueryPart().getFirstQuerySpec(), this ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdLoaderStandard.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdLoaderStandard.java index f11c3b7d3b..09da4a8d3e 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdLoaderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdLoaderStandard.java @@ -9,7 +9,6 @@ package org.hibernate.loader.ast.internal; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; import org.hibernate.LockMode; @@ -38,7 +37,6 @@ import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl; import org.hibernate.sql.exec.spi.Callback; @@ -255,8 +253,6 @@ public class MultiIdLoaderStandard implements MultiIdEntityLoader { final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst ); - final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); int offset = 0; @@ -275,6 +271,8 @@ public class MultiIdLoaderStandard implements MultiIdEntityLoader { // we should have used all of the JdbcParameter references (created bindings for all) assert offset == jdbcParameters.size(); + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlAst ) + .translate( jdbcParameterBindings, QueryOptions.NONE ); final LoadingEntityCollector loadingEntityCollector; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java index 54b39f3704..258ad01448 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java @@ -7,7 +7,6 @@ package org.hibernate.loader.ast.internal; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import org.hibernate.LockOptions; @@ -19,17 +18,14 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.loader.ast.spi.MultiNaturalIdLoadOptions; -import org.hibernate.metamodel.mapping.Bindable; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.sql.ast.Clause; -import org.hibernate.sql.ast.SqlAstSelectTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl; import org.hibernate.sql.exec.spi.Callback; @@ -95,8 +91,8 @@ public class MultiNaturalIdLoadingBatcher { final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final SqlAstSelectTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ); - this.jdbcSelect = sqlAstTranslator.translate( sqlSelect ); + this.jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlSelect ) + .translate( null, QueryOptions.NONE ); } public List multiLoad(Object[] naturalIdValues, MultiNaturalIdLoadOptions options, SharedSessionContractImplementor session) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SimpleNaturalIdLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SimpleNaturalIdLoader.java index a570d5ca8c..d019d8266c 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SimpleNaturalIdLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SimpleNaturalIdLoader.java @@ -8,7 +8,6 @@ package org.hibernate.loader.ast.internal; import java.util.ArrayList; import java.util.Collections; -import java.util.Iterator; import java.util.List; import org.hibernate.HibernateException; @@ -29,7 +28,6 @@ import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.Callback; import org.hibernate.sql.exec.spi.ExecutionContext; @@ -101,10 +99,7 @@ public class SimpleNaturalIdLoader extends AbstractNaturalIdLoader { final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlSelect ); - final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); - jdbcParamBindings.registerParametersForEachJdbcValue( id, Clause.WHERE, @@ -112,7 +107,8 @@ public class SimpleNaturalIdLoader extends AbstractNaturalIdLoader { jdbcParameters, session ); - + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlSelect ) + .translate( jdbcParamBindings, QueryOptions.NONE ); final List results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list( jdbcSelect, @@ -186,8 +182,6 @@ public class SimpleNaturalIdLoader extends AbstractNaturalIdLoader { ); assert jdbcParameters.size() == 1; - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlSelect ); - final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); final SingularAttributeMapping attributeMapping = naturalIdMapping().getAttribute(); @@ -198,7 +192,8 @@ public class SimpleNaturalIdLoader extends AbstractNaturalIdLoader { jdbcParameters, session ); - + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlSelect ) + .translate( jdbcParamBindings, QueryOptions.NONE ); final List results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list( jdbcSelect, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java index 73606ca217..af40aa259b 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java @@ -7,7 +7,6 @@ package org.hibernate.loader.ast.internal; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import org.hibernate.LockOptions; @@ -26,7 +25,6 @@ import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl; import org.hibernate.sql.exec.spi.Callback; @@ -102,8 +100,6 @@ public class SingleIdEntityLoaderDynamicBatch extends SingleIdEntityLoaderSup final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst ); - final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( getLoadable().getIdentifierMapping().getJdbcTypeCount() ); @@ -118,6 +114,8 @@ public class SingleIdEntityLoaderDynamicBatch extends SingleIdEntityLoaderSup ); assert offset == jdbcParameters.size(); } + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlAst ) + .translate( jdbcParameterBindings, QueryOptions.NONE ); JdbcSelectExecutorStandardImpl.INSTANCE.list( jdbcSelect, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java index c1ab6957fc..6ab071a37b 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java @@ -6,7 +6,6 @@ */ package org.hibernate.loader.ast.internal; -import java.util.Iterator; import java.util.List; import org.hibernate.LockOptions; @@ -23,7 +22,6 @@ import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl; import org.hibernate.sql.exec.spi.Callback; @@ -96,13 +94,10 @@ public class SingleIdLoadPlan implements SingleEntityLoadPlan { final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst ); - final int jdbcTypeCount = restrictivePart.getJdbcTypeCount(); assert jdbcParameters.size() % jdbcTypeCount == 0; final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( jdbcTypeCount ); - jdbcSelect.bindFilterJdbcParameters( jdbcParameterBindings ); int offset = 0; while ( offset < jdbcParameters.size() ) { @@ -116,6 +111,8 @@ public class SingleIdLoadPlan implements SingleEntityLoadPlan { ); } assert offset == jdbcParameters.size(); + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlAst ) + .translate( jdbcParameterBindings, QueryOptions.NONE ); final List list = JdbcSelectExecutorStandardImpl.INSTANCE.list( jdbcSelect, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java index 9b3af37644..28b1a0692e 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java @@ -8,7 +8,6 @@ package org.hibernate.loader.ast.internal; import java.util.ArrayList; import java.util.Collections; -import java.util.Iterator; import java.util.List; import org.hibernate.LockOptions; @@ -29,7 +28,6 @@ import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.Callback; import org.hibernate.sql.exec.spi.ExecutionContext; @@ -86,8 +84,6 @@ public class SingleUniqueKeyEntityLoaderStandard implements SingleUniqueKeyEn final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst ); - final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); int offset = jdbcParameterBindings.registerParametersForEachJdbcValue( ukValue, @@ -97,6 +93,8 @@ public class SingleUniqueKeyEntityLoaderStandard implements SingleUniqueKeyEn session ); assert offset == jdbcParameters.size(); + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlAst ) + .translate( jdbcParameterBindings, QueryOptions.NONE ); final List list = sessionFactory.getJdbcServices().getJdbcSelectExecutor().list( jdbcSelect, @@ -164,8 +162,6 @@ public class SingleUniqueKeyEntityLoaderStandard implements SingleUniqueKeyEn final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst ); - final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); int offset = jdbcParameterBindings.registerParametersForEachJdbcValue( ukValue, @@ -175,6 +171,8 @@ public class SingleUniqueKeyEntityLoaderStandard implements SingleUniqueKeyEn session ); assert offset == jdbcParameters.size(); + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlAst ) + .translate( jdbcParameterBindings, QueryOptions.NONE ); final List list = sessionFactory.getJdbcServices().getJdbcSelectExecutor().list( jdbcSelect, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityValuedModelPart.java index 5811cf2940..ad6c10c073 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityValuedModelPart.java @@ -7,12 +7,14 @@ package org.hibernate.metamodel.mapping; import java.util.List; +import java.util.function.BiConsumer; import java.util.function.Consumer; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.mapping.IndexedConsumer; import org.hibernate.query.NavigablePath; import org.hibernate.sql.ast.Clause; +import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; @@ -59,6 +61,17 @@ public interface EntityValuedModelPart extends FetchableContainer { getEntityMappingType().applySqlSelections( navigablePath, tableGroup, creationState ); } + @Override + default void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState, + BiConsumer selectionConsumer) { + // this is really only valid for root entity returns, not really many-to-ones, etc.. but this should + // really only ever be called as part of creating a root-return. + getEntityMappingType().applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer ); + } + @Override default int getJdbcTypeCount() { int span = 0; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java index 9dc8a22b38..44ae5f0d16 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java @@ -172,6 +172,29 @@ public class BasicEntityIdentifierMappingImpl implements BasicEntityIdentifierMa TableGroup tableGroup, String resultVariable, DomainResultCreationState creationState) { + final SqlSelection sqlSelection = resolveSqlSelection( navigablePath, tableGroup, creationState ); + + //noinspection unchecked + return new BasicResult( + sqlSelection.getValuesArrayPosition(), + resultVariable, + entityPersister.getIdentifierMapping().getMappedType().getMappedJavaTypeDescriptor(), + navigablePath + ); + } + + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState) { + resolveSqlSelection( navigablePath, tableGroup, creationState ); + } + + private SqlSelection resolveSqlSelection( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState) { final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState() .getSqlExpressionResolver(); final TableReference rootTableReference; @@ -194,7 +217,7 @@ public class BasicEntityIdentifierMappingImpl implements BasicEntityIdentifierMa final Expression expression = expressionResolver.resolveSqlExpression( SqlExpressionResolver.createColumnReferenceKey( rootTableReference, pkColumnName ), sqlAstProcessingState -> new ColumnReference( - rootTableReference.getIdentificationVariable(), + rootTableReference, pkColumnName, false, null, @@ -204,45 +227,7 @@ public class BasicEntityIdentifierMappingImpl implements BasicEntityIdentifierMa ) ); - final SqlSelection sqlSelection = expressionResolver.resolveSqlSelection( - expression, - idType.getExpressableJavaTypeDescriptor(), - sessionFactory.getTypeConfiguration() - ); - - //noinspection unchecked - return new BasicResult( - sqlSelection.getValuesArrayPosition(), - resultVariable, - entityPersister.getIdentifierMapping().getMappedType().getMappedJavaTypeDescriptor(), - navigablePath - ); - } - - @Override - public void applySqlSelections( - NavigablePath navigablePath, - TableGroup tableGroup, - DomainResultCreationState creationState) { - final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState() - .getSqlExpressionResolver(); - final TableReference rootTableReference = tableGroup.resolveTableReference( rootTable ); - - final Expression expression = expressionResolver.resolveSqlExpression( - SqlExpressionResolver.createColumnReferenceKey( rootTableReference, pkColumnName ), - sqlAstProcessingState -> new ColumnReference( - rootTable, - pkColumnName, - false, - null, - null, - ( (BasicValuedModelPart) entityPersister.getIdentifierType() ).getJdbcMapping(), - sessionFactory - ) - ); - - // the act of resolving the expression -> selection applies it - expressionResolver.resolveSqlSelection( + return expressionResolver.resolveSqlSelection( expression, idType.getExpressableJavaTypeDescriptor(), sessionFactory.getTypeConfiguration() diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java index 072c4f8420..2ce2e0e281 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java @@ -8,6 +8,7 @@ package org.hibernate.metamodel.mapping.internal; import java.util.Collections; import java.util.List; +import java.util.function.BiConsumer; import org.hibernate.LockMode; import org.hibernate.engine.FetchStyle; @@ -159,7 +160,16 @@ public class BasicValuedCollectionPart @Override public void applySqlSelections( NavigablePath navigablePath, TableGroup tableGroup, DomainResultCreationState creationState) { + resolveSqlSelection( tableGroup, creationState ); + } + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState, + BiConsumer selectionConsumer) { + selectionConsumer.accept( resolveSqlSelection( tableGroup, creationState ), getJdbcMapping() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedSingularAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedSingularAttributeMapping.java index e233352f30..18b41fc3ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedSingularAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedSingularAttributeMapping.java @@ -6,6 +6,8 @@ */ package org.hibernate.metamodel.mapping.internal; +import java.util.function.BiConsumer; + import org.hibernate.LockMode; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchTiming; @@ -169,9 +171,7 @@ public class BasicValuedSingularAttributeMapping private SqlSelection resolveSqlSelection(TableGroup tableGroup, DomainResultCreationState creationState) { final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver(); - final TableReference tableReference = tableGroup.resolveTableReference( getContainingTableExpression() ); - final String tableAlias = tableReference.getIdentificationVariable(); return expressionResolver.resolveSqlSelection( expressionResolver.resolveSqlExpression( @@ -195,26 +195,16 @@ public class BasicValuedSingularAttributeMapping NavigablePath navigablePath, TableGroup tableGroup, DomainResultCreationState creationState) { - // the act of resolving the selection creates the selection if it not already part of the collected selections + resolveSqlSelection( tableGroup, creationState ); + } - final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver(); - final TableReference tableReference = tableGroup.resolveTableReference( getContainingTableExpression() ); - - expressionResolver.resolveSqlSelection( - expressionResolver.resolveSqlExpression( - SqlExpressionResolver.createColumnReferenceKey( - tableReference, - mappedColumnExpression - ), - sqlAstProcessingState -> new ColumnReference( - tableReference.getIdentificationVariable(), - this, - creationState.getSqlAstCreationState().getCreationContext().getSessionFactory() - ) - ), - valueConverter == null ? getMappedType().getMappedJavaTypeDescriptor() : valueConverter.getRelationalJavaDescriptor(), - creationState.getSqlAstCreationState().getCreationContext().getDomainModel().getTypeConfiguration() - ); + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState, + BiConsumer selectionConsumer) { + selectionConsumer.accept( resolveSqlSelection( tableGroup, creationState ), getJdbcMapping() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java index 5149862773..deee839e37 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java @@ -7,6 +7,7 @@ package org.hibernate.metamodel.mapping.internal; import java.util.List; +import java.util.function.BiConsumer; import java.util.function.Consumer; import org.hibernate.LockMode; @@ -34,6 +35,7 @@ import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; +import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.SqlTuple; @@ -143,7 +145,16 @@ public class EmbeddedAttributeMapping NavigablePath navigablePath, TableGroup tableGroup, DomainResultCreationState creationState) { - throw new NotYetImplementedFor6Exception( getClass() ); + embeddableMappingType.applySqlSelections( navigablePath, tableGroup, creationState ); + } + + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState, + BiConsumer selectionConsumer) { + embeddableMappingType.applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer ); } @Override 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 80fc913898..89e8118ea1 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 @@ -8,6 +8,7 @@ package org.hibernate.metamodel.mapping.internal; import java.util.ArrayList; import java.util.List; +import java.util.function.BiConsumer; import java.util.function.Consumer; import org.hibernate.LockMode; @@ -35,6 +36,7 @@ import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; +import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.SqlTuple; @@ -226,6 +228,23 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF getEmbeddableTypeDescriptor().visitSubParts( consumer, treatTargetType ); } + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState) { + embeddableMappingType.applySqlSelections( navigablePath, tableGroup, creationState ); + } + + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState, + BiConsumer selectionConsumer) { + embeddableMappingType.applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer ); + } + @Override public JavaTypeDescriptor getJavaTypeDescriptor() { return getEmbeddableTypeDescriptor().getJavaTypeDescriptor(); 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 254d94fdb9..7956d89571 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 @@ -6,6 +6,8 @@ */ package org.hibernate.metamodel.mapping.internal; +import java.util.function.BiConsumer; + import org.hibernate.LockMode; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.engine.FetchStyle; @@ -13,6 +15,7 @@ import org.hibernate.engine.FetchTiming; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Value; import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.SelectionConsumer; import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.EntityMappingType; @@ -23,6 +26,7 @@ import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.query.NavigablePath; import org.hibernate.sql.ast.spi.FromClauseAccess; +import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; @@ -179,7 +183,16 @@ public class EntityCollectionPart NavigablePath navigablePath, TableGroup tableGroup, DomainResultCreationState creationState) { - throw new NotYetImplementedFor6Exception( getClass() ); + entityMappingType.applySqlSelections( navigablePath, tableGroup, creationState ); + } + + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState, + BiConsumer selectionConsumer) { + entityMappingType.applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java index cea74e4288..ef12d30ed9 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java @@ -187,12 +187,39 @@ public class EntityVersionMappingImpl implements EntityVersionMapping, FetchOpti TableGroup tableGroup, String resultVariable, DomainResultCreationState creationState) { + final SqlSelection sqlSelection = resolveSqlSelection( tableGroup, creationState ); + + //noinspection unchecked + return new BasicResult<>( + sqlSelection.getValuesArrayPosition(), + resultVariable, + getJavaTypeDescriptor(), + navigablePath + ); + } + + @Override + public void applySqlSelections( + NavigablePath navigablePath, TableGroup tableGroup, DomainResultCreationState creationState) { + resolveSqlSelection( tableGroup, creationState ); + } + + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState, + BiConsumer selectionConsumer) { + selectionConsumer.accept( resolveSqlSelection( tableGroup, creationState ), getJdbcMapping() ); + } + + private SqlSelection resolveSqlSelection(TableGroup tableGroup, DomainResultCreationState creationState) { final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState(); final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver(); final TableReference columnTableReference = tableGroup.resolveTableReference( columnTableExpression ); - final SqlSelection sqlSelection = sqlExpressionResolver.resolveSqlSelection( + return sqlExpressionResolver.resolveSqlSelection( sqlExpressionResolver.resolveSqlExpression( SqlExpressionResolver.createColumnReferenceKey( columnTableReference, columnExpression ), sqlAstProcessingState -> new ColumnReference( @@ -208,29 +235,6 @@ public class EntityVersionMappingImpl implements EntityVersionMapping, FetchOpti versionBasicType.getJdbcMapping().getJavaTypeDescriptor(), sqlAstCreationState.getCreationContext().getDomainModel().getTypeConfiguration() ); - - //noinspection unchecked - return new BasicResult<>( - sqlSelection.getValuesArrayPosition(), - resultVariable, - getJavaTypeDescriptor(), - navigablePath - ); - } - - @Override - public void applySqlSelections( - NavigablePath navigablePath, TableGroup tableGroup, DomainResultCreationState creationState) { - - } - - @Override - public void applySqlSelections( - NavigablePath navigablePath, - TableGroup tableGroup, - DomainResultCreationState creationState, - BiConsumer selectionConsumer) { - } @Override 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 482b3ba51b..ea59db3a42 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 @@ -6,6 +6,7 @@ */ package org.hibernate.metamodel.mapping.internal; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -56,6 +57,7 @@ import org.hibernate.sql.ast.spi.SqlAliasStemHelper; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; +import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.from.StandardTableGroup; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroupJoin; @@ -988,6 +990,21 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping return null; } + @Override + public void applySqlSelections( + NavigablePath navigablePath, TableGroup tableGroup, DomainResultCreationState creationState) { + elementDescriptor.applySqlSelections( navigablePath, tableGroup, creationState ); + } + + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState, + BiConsumer selectionConsumer) { + elementDescriptor.applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer ); + } + @Override public void visitSubParts(Consumer consumer, EntityMappingType treatTargetType) { consumer.accept( elementDescriptor ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java index 5e669085a5..c60f390971 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java @@ -51,6 +51,12 @@ public interface JpaMetamodel extends javax.persistence.metamodel.Metamodel { */ EntityDomainType entity(String entityName); + /** + * Specialized handling for resolving entity-name references in + * an HQL query + */ + EntityDomainType getHqlEntityReference(String entityName); + /** * Specialized handling for resolving entity-name references in * an HQL query 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 e1f83ebf0e..8d156ff3e9 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 @@ -110,7 +110,7 @@ public class JpaMetamodelImpl implements JpaMetamodel { } @Override - public EntityDomainType resolveHqlEntityReference(String entityName) { + public EntityDomainType getHqlEntityReference(String entityName) { Class loadedClass = null; final ImportInfo importInfo = resolveImport( entityName ); if ( importInfo != null ) { @@ -133,8 +133,16 @@ public class JpaMetamodelImpl implements JpaMetamodel { if ( loadedClass != null ) { return resolveEntityReference( loadedClass ); } + return null; + } - throw new IllegalArgumentException( "Could not resolve entity reference: " + entityName ); + @Override + public EntityDomainType resolveHqlEntityReference(String entityName) { + final EntityDomainType hqlEntityReference = getHqlEntityReference( entityName ); + if ( hqlEntityReference == null ) { + throw new IllegalArgumentException( "Could not resolve entity reference: " + entityName ); + } + return hqlEntityReference; } @Override 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 fe3e1970f6..ee1eb0327d 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 @@ -28,6 +28,7 @@ import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Supplier; @@ -144,6 +145,7 @@ import org.hibernate.metamodel.RepresentationMode; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMetadata; import org.hibernate.metamodel.mapping.AttributeMetadataAccess; +import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.SelectionConsumer; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; @@ -202,6 +204,7 @@ import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAliasStemHelper; 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.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.from.StandardTableGroup; @@ -1251,6 +1254,83 @@ public abstract class AbstractEntityPersister return new EntityResultImpl( navigablePath, this, resultVariable, creationState ); } + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState) { + identifierMapping.applySqlSelections( + navigablePath.append( identifierMapping.getPartName() ), + tableGroup, + creationState + ); + if ( discriminatorMapping != null ) { + discriminatorMapping.applySqlSelections( + navigablePath.append( discriminatorMapping.getPartName() ), + tableGroup, + creationState + ); + } + if ( versionMapping != null ) { + versionMapping.applySqlSelections( + navigablePath.append( versionMapping.getPartName() ), + tableGroup, + creationState + ); + } + for ( int i = 0; i < attributeMappings.size(); i++ ) { + final AttributeMapping attributeMapping = attributeMappings.get( i ); + if ( attributeMapping instanceof SingularAttributeMapping ) { + attributeMapping.applySqlSelections( + navigablePath.append( attributeMapping.getPartName() ), + tableGroup, + creationState + ); + } + } + } + + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState, + BiConsumer selectionConsumer) { + identifierMapping.applySqlSelections( + navigablePath.append( identifierMapping.getPartName() ), + tableGroup, + creationState, + selectionConsumer + ); + if ( discriminatorMapping != null ) { + discriminatorMapping.applySqlSelections( + navigablePath.append( discriminatorMapping.getPartName() ), + tableGroup, + creationState, + selectionConsumer + ); + } + if ( versionMapping != null ) { + versionMapping.applySqlSelections( + navigablePath.append( versionMapping.getPartName() ), + tableGroup, + creationState, + selectionConsumer + ); + } + for ( int i = 0; i < attributeMappings.size(); i++ ) { + final AttributeMapping attributeMapping = attributeMappings.get( i ); + if ( attributeMapping instanceof SingularAttributeMapping ) { + attributeMapping.applySqlSelections( + navigablePath.append( attributeMapping.getPartName() ), + tableGroup, + creationState, + selectionConsumer + ); + } + } + } + @Override public NaturalIdMapping getNaturalIdMapping() { return naturalIdMapping; @@ -1287,9 +1367,9 @@ public abstract class AbstractEntityPersister sqlAliasBase, (tableExpression) -> ArrayHelper.contains( getSubclassTableNames(), tableExpression ), (tableExpression, tableGroup) -> { - for ( int i = 0; i < getSubclassTableSpan(); i++ ) { - final String subclassTableName = getSubclassTableName( i ); - if ( subclassTableName.equals( tableExpression ) ) { + final String[] subclassTableNames = getSubclassTableNames(); + for ( int i = 0; i < subclassTableNames.length; i++ ) { + if ( tableExpression.equals( subclassTableNames[i] ) ) { final boolean isNullableTable = isNullableSubclassTable( i ); final TableReference joinedTableReference = new TableReference( tableExpression, diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java index f6d58e6666..46dee1a396 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java @@ -1318,69 +1318,6 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { } } - - @Override - public TableGroup createRootTableGroup( - NavigablePath navigablePath, - String explicitSourceAlias, - boolean canUseInnerJoins, - LockMode lockMode, - SqlAliasBaseGenerator aliasBaseGenerator, - SqlExpressionResolver sqlExpressionResolver, - Supplier> additionalPredicateCollectorAccess, - SqlAstCreationContext creationContext) { - final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() ); - - final TableReference primaryTableReference = createPrimaryTableReference( - sqlAliasBase, - sqlExpressionResolver, - creationContext - ); - - StandardTableGroup standardTableGroup = new StandardTableGroup( - navigablePath, - this, - lockMode, - primaryTableReference, - sqlAliasBase, - (tableExpression) -> ArrayHelper.contains( getSubclassTableNames(), tableExpression ), - (tableExpression, tableGroup) -> null, - getFactory() - ); - - final String primaryTableName = primaryTableReference.getTableExpression(); - for ( int i = 1; i < getSubclassTableSpan(); i++ ) { - final String subclassTableName = getSubclassTableName( i ); - if ( !subclassTableName.equals( primaryTableName ) ) { - final boolean isNullableTable = isNullableSubclassTable( i ); - final TableReference joinedTableReference = new TableReference( - subclassTableName, - sqlAliasBase.generateNewAlias(), - isNullableTable, - getFactory() - ); - - TableReferenceJoin tableReferenceJoin = new TableReferenceJoin( - determineSubclassTableJoinType( - i, - canUseInnerJoins, - true, - Collections.emptySet() - ), - joinedTableReference, - generateJoinPredicate( - primaryTableReference, - joinedTableReference, - i, - sqlExpressionResolver - ) - ); - standardTableGroup.addTableReferenceJoin( tableReferenceJoin ); - } - } - return standardTableGroup; - } - @Override public EntityDiscriminatorMapping getDiscriminatorMapping(TableGroup tableGroup) { if ( hasSubclasses() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/Limit.java b/hibernate-core/src/main/java/org/hibernate/query/Limit.java index 0b937f6698..165567a85b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/Limit.java +++ b/hibernate-core/src/main/java/org/hibernate/query/Limit.java @@ -21,6 +21,22 @@ public class Limit { private Integer firstRow; private Integer maxRows; + public Limit() { + } + + public Limit(Integer firstRow, Integer maxRows) { + this.firstRow = firstRow; + this.maxRows = maxRows; + } + + public boolean isEmpty() { + return firstRow == null && maxRows == null; + } + + public Limit makeCopy() { + return new Limit( firstRow, maxRows ); + } + public Integer getFirstRow() { return firstRow; } @@ -46,8 +62,8 @@ public class Limit { } public void setMaxRows(int maxRows) { - if ( maxRows <= 0 ) { - // treat zero and negatives specially as meaning no limit... + if ( maxRows < 0 ) { + // treat negatives specially as meaning no limit... this.maxRows = null; } else { @@ -56,12 +72,27 @@ public class Limit { } public void setMaxRows(Integer maxRows) { - if ( maxRows != null && maxRows <= 0 ) { - // treat zero and negatives specially as meaning no limit... + if ( maxRows != null && maxRows < 0 ) { + // treat negatives specially as meaning no limit... this.maxRows = null; } else { this.maxRows = maxRows; } } + + public boolean isCompatible(Limit limit) { + if ( limit == null ) { + return isEmpty(); + } + else if ( this == limit ) { + return true; + } + + if ( firstRow != null ? !firstRow.equals( limit.firstRow ) : limit.firstRow != null ) { + return false; + } + return maxRows != null ? maxRows.equals( limit.maxRows ) : limit.maxRows == null; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaCollectionJoin.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaCollectionJoin.java index 91bc68bd73..04cf0a01af 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaCollectionJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaCollectionJoin.java @@ -16,8 +16,6 @@ import javax.persistence.criteria.Predicate; * @author Steve Ebersole */ public interface JpaCollectionJoin extends JpaJoin, CollectionJoin { - @Override - JpaCollectionJoin correlateTo(JpaSubQuery subquery); @Override JpaCollectionJoin on(JpaExpression restriction); diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaFrom.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaFrom.java index ae002245d5..6395d5dd38 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaFrom.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaFrom.java @@ -16,6 +16,4 @@ import javax.persistence.criteria.From; public interface JpaFrom extends JpaPath, JpaFetchParent, From { @Override JpaFrom getCorrelationParent(); - - JpaFrom correlateTo(JpaSubQuery subquery); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaJoin.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaJoin.java index 9533dd6256..b4ec93fed4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaJoin.java @@ -32,7 +32,4 @@ public interface JpaJoin extends JpaJoinedFrom, Join { @Override JpaJoin on(Predicate... restrictions); - - @Override - JpaFrom correlateTo(JpaSubQuery subquery); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaListJoin.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaListJoin.java index f9aceaede2..5770e180a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaListJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaListJoin.java @@ -28,9 +28,6 @@ public interface JpaListJoin extends JpaJoin, ListJoin { @Override JpaListJoin on(Predicate... restrictions); - @Override - JpaListJoin correlateTo(JpaSubQuery subquery); - @Override JpaListJoin treatAs(Class treatAsType); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaMapJoin.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaMapJoin.java index 21071d0bba..e938fff8c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaMapJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaMapJoin.java @@ -28,8 +28,5 @@ public interface JpaMapJoin extends JpaJoin, MapJoin { @Override JpaMapJoin on(Predicate... restrictions); - @Override - JpaMapJoin correlateTo(JpaSubQuery subquery); - JpaMapJoin treatAs(Class treatAsType); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaQueryStructure.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaQueryStructure.java index 41437862a6..03f106bc88 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaQueryStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaQueryStructure.java @@ -11,6 +11,8 @@ import java.util.Set; import javax.persistence.criteria.Expression; import javax.persistence.criteria.Predicate; +import org.hibernate.FetchClauseType; + /** * Models a {@code SELECT} query. Used as a delegate in * implementing {@link javax.persistence.criteria.CriteriaQuery} @@ -86,13 +88,17 @@ public interface JpaQueryStructure extends JpaCriteriaNode { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Limit clause - - JpaExpression getLimit(); - - JpaQueryStructure setLimit(JpaExpression limit); + // Limit/Offset/Fetch clause JpaExpression getOffset(); JpaQueryStructure setOffset(JpaExpression offset); + + JpaExpression getFetch(); + + JpaQueryStructure setFetch(JpaExpression fetch); + + JpaQueryStructure setFetch(JpaExpression fetch, FetchClauseType fetchClauseType); + + FetchClauseType getFetchClauseType(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaSetJoin.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaSetJoin.java index f099846463..f31b8d85ee 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaSetJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaSetJoin.java @@ -16,8 +16,6 @@ import javax.persistence.criteria.SetJoin; * @author Steve Ebersole */ public interface JpaSetJoin extends JpaJoin, SetJoin { - @Override - JpaSetJoin correlateTo(JpaSubQuery subquery); JpaSetJoin on(JpaExpression restriction); diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaSubQuery.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaSubQuery.java index a0bf190851..208203d061 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaSubQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaSubQuery.java @@ -7,14 +7,29 @@ package org.hibernate.query.criteria; import java.util.List; +import java.util.Set; import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Join; import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.SetJoin; import javax.persistence.criteria.Subquery; +import org.hibernate.query.sqm.tree.domain.SqmSetJoin; +import org.hibernate.query.sqm.tree.from.SqmCrossJoin; +import org.hibernate.query.sqm.tree.from.SqmEntityJoin; +import org.hibernate.query.sqm.tree.from.SqmJoin; + /** * @author Steve Ebersole */ public interface JpaSubQuery extends Subquery, JpaSelectCriteria, JpaExpression { + + SqmCrossJoin correlate(SqmCrossJoin parentCrossJoin); + + SqmEntityJoin correlate(SqmEntityJoin parentEntityJoin); + + Set> getCorrelatedSqmJoins(); + @Override JpaSubQuery distinct(boolean distinct); 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 2e62c23e71..8b7c052401 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 @@ -58,6 +58,26 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer { this.creationState = creationState; } + public QualifiedJoinPathConsumer( + SqmFrom sqmFrom, + SqmJoinType joinType, + boolean fetch, + String alias, + SqmCreationState creationState) { + this.sqmRoot = null; + this.joinType = joinType; + this.fetch = fetch; + this.alias = alias; + this.creationState = creationState; + this.delegate = new AttributeJoinDelegate( + sqmFrom, + joinType, + fetch, + alias, + creationState + ); + } + @Override public SemanticPathPart getConsumedPart() { return delegate.getConsumedPart(); 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 56c6a58946..c561c82e5e 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 @@ -11,7 +11,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; @@ -61,6 +60,8 @@ import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation; import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiationArgument; import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiationTarget; import org.hibernate.query.sqm.tree.select.SqmOrderByClause; +import org.hibernate.query.sqm.tree.select.SqmQueryGroup; +import org.hibernate.query.sqm.tree.select.SqmQueryPart; import org.hibernate.query.sqm.tree.select.SqmQuerySpec; import org.hibernate.query.sqm.tree.select.SqmSelectClause; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; @@ -169,7 +170,7 @@ public class QuerySplitter { ) ); try { - copy.setQuerySpec( visitQuerySpec( statement.getQuerySpec() ) ); + copy.setQueryPart( visitQueryPart( statement.getQueryPart() ) ); } finally { processingStateStack.pop(); @@ -178,6 +179,22 @@ public class QuerySplitter { return copy; } + @Override + public SqmQueryPart visitQueryPart(SqmQueryPart queryPart) { + return (SqmQueryPart) super.visitQueryPart( queryPart ); + } + + @Override + public SqmQueryGroup visitQueryGroup(SqmQueryGroup queryGroup) { + final List> queryParts = queryGroup.getQueryParts(); + final int size = queryParts.size(); + final List> newQueryParts = new ArrayList<>( size ); + for ( int i = 0; i < size; i++ ) { + newQueryParts.add( visitQueryPart( queryParts.get( i ) ) ); + } + return new SqmQueryGroup( queryGroup.nodeBuilder(), queryGroup.getSetOperator(), newQueryParts ); + } + @Override public SqmQuerySpec visitQuerySpec(SqmQuerySpec querySpec) { // NOTE : it is important that we visit the SqmFromClause first so that the @@ -191,8 +208,11 @@ public class QuerySplitter { sqmQuerySpec.setGroupByClauseExpressions( visitGroupByClause( querySpec.getGroupByClauseExpressions() ) ); sqmQuerySpec.setHavingClausePredicate( visitHavingClause( querySpec.getHavingClausePredicate() ) ); sqmQuerySpec.setOrderByClause( visitOrderByClause( querySpec.getOrderByClause() ) ); - if ( querySpec.getLimitExpression() != null ) { - sqmQuerySpec.setLimitExpression( (SqmExpression) querySpec.getLimitExpression().accept( this ) ); + if ( querySpec.getFetchExpression() != null ) { + sqmQuerySpec.setFetchExpression( + (SqmExpression) querySpec.getFetchExpression().accept( this ), + querySpec.getFetchClauseType() + ); } if ( querySpec.getOffsetExpression() != null ) { sqmQuerySpec.setOffsetExpression( (SqmExpression) querySpec.getOffsetExpression().accept( this ) ); @@ -653,7 +673,8 @@ public class QuerySplitter { return new SqmSubQuery( // todo (6.0) : current? or previous at this point? getProcessingStateStack().getCurrent().getProcessingQuery(), - visitQuerySpec( expression.getQuerySpec() ), + visitQueryPart( expression.getQueryPart() ), + expression.getResultType(), expression.nodeBuilder() ); } 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 4b19d98e1a..1c1809a043 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 @@ -30,9 +30,11 @@ import java.util.Locale; import java.util.Map; import java.util.regex.Pattern; +import org.hibernate.FetchClauseType; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.NullPrecedence; import org.hibernate.QueryException; +import org.hibernate.SetOperator; import org.hibernate.SortOrder; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; @@ -64,6 +66,7 @@ import org.hibernate.query.hql.spi.SemanticPathPart; 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.LiteralNumberFormatException; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.ParsingException; @@ -87,6 +90,8 @@ import org.hibernate.query.sqm.tree.SqmQuery; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; +import org.hibernate.query.sqm.tree.domain.AbstractSqmFrom; +import org.hibernate.query.sqm.tree.domain.SqmCorrelation; import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath; import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference; import org.hibernate.query.sqm.tree.domain.SqmMaxElementPath; @@ -153,9 +158,12 @@ import org.hibernate.query.sqm.tree.predicate.SqmNullnessPredicate; import org.hibernate.query.sqm.tree.predicate.SqmOrPredicate; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; +import org.hibernate.query.sqm.tree.select.AbstractSqmSelectQuery; import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation; import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiationArgument; import org.hibernate.query.sqm.tree.select.SqmOrderByClause; +import org.hibernate.query.sqm.tree.select.SqmQueryGroup; +import org.hibernate.query.sqm.tree.select.SqmQueryPart; import org.hibernate.query.sqm.tree.select.SqmQuerySpec; import org.hibernate.query.sqm.tree.select.SqmSelectClause; import org.hibernate.query.sqm.tree.select.SqmSelectQuery; @@ -180,7 +188,10 @@ import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static org.hibernate.grammars.hql.HqlParser.EXCEPT; import static org.hibernate.grammars.hql.HqlParser.IDENTIFIER; +import static org.hibernate.grammars.hql.HqlParser.INTERSECT; +import static org.hibernate.grammars.hql.HqlParser.UNION; import static org.hibernate.query.TemporalUnit.DATE; import static org.hibernate.query.TemporalUnit.DAY_OF_MONTH; import static org.hibernate.query.TemporalUnit.DAY_OF_WEEK; @@ -292,15 +303,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre @Override public SqmSelectStatement visitSelectStatement(HqlParser.SelectStatementContext ctx) { - if ( creationOptions.useStrictJpaCompliance() ) { - if ( ctx.querySpec().selectClause() == null ) { - throw new StrictJpaComplianceViolation( - "Encountered implicit select-clause, but strict JPQL compliance was requested", - StrictJpaComplianceViolation.Type.IMPLICIT_SELECT - ); - } - } - + final HqlParser.QueryExpressionContext queryExpressionContext = ctx.queryExpression(); final SqmSelectStatement selectStatement = new SqmSelectStatement( creationContext.getNodeBuilder() ); parameterCollector = selectStatement; @@ -314,7 +317,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre ); try { - visitQuerySpec( ctx.querySpec() ); + visitQueryExpression( queryExpressionContext ); } finally { processingStateStack.pop(); @@ -332,7 +335,8 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre creationContext.getNodeBuilder() ); - if ( ctx.querySpec()!=null ) { + final HqlParser.QueryExpressionContext queryExpressionContext = ctx.queryExpression(); + if ( queryExpressionContext != null ) { final SqmInsertSelectStatement insertStatement = new SqmInsertSelectStatement<>( root, creationContext.getNodeBuilder() ); parameterCollector = insertStatement; final SqmDmlCreationProcessingState processingState = new SqmDmlCreationProcessingState( @@ -343,7 +347,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre processingStateStack.push( processingState ); try { - visitQuerySpec( ctx.querySpec() ); + visitQueryExpression( queryExpressionContext ); final SqmCreationProcessingState stateFieldsProcessingState = new SqmCreationProcessingStateImpl( insertStatement, @@ -475,6 +479,177 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Query spec + @Override + public SqmQueryPart visitQueryExpression(HqlParser.QueryExpressionContext ctx) { + final SqmQueryPart queryPart = (SqmQueryPart) ctx.queryGroup().accept( this ); + visitQueryOrder( queryPart, ctx.queryOrder() ); + return queryPart; + } + + @Override + public SqmQueryPart visitQuerySpecQueryGroup(HqlParser.QuerySpecQueryGroupContext ctx) { + return visitQuerySpec( ctx.querySpec() ); + } + + @Override + public SqmQueryPart visitNestedQueryGroup(HqlParser.NestedQueryGroupContext ctx) { + return (SqmQueryPart) ctx.queryGroup().accept( this ); + } + + @Override + public SqmQueryGroup visitSetQueryGroup(HqlParser.SetQueryGroupContext ctx) { + if ( creationOptions.useStrictJpaCompliance() ) { + throw new StrictJpaComplianceViolation( + StrictJpaComplianceViolation.Type.SET_OPERATIONS + ); + } + final List children = ctx.children; + final SqmQueryPart firstQueryPart = (SqmQueryPart) children.get( 0 ).accept( this ); + SqmQueryGroup queryGroup; + if ( firstQueryPart instanceof SqmQueryGroup) { + queryGroup = (SqmQueryGroup) firstQueryPart; + } + else { + queryGroup = new SqmQueryGroup<>( firstQueryPart ); + } + setCurrentQueryPart( queryGroup ); + final List firstSelections = firstQueryPart.getFirstQuerySpec().getSelectClause().getSelections(); + final int firstSelectionSize = firstSelections.size(); + final ParseTree maybeOrderContext = children.get( 1 ); + int i; + if (maybeOrderContext instanceof HqlParser.QueryOrderContext ) { + visitQueryOrder( firstQueryPart, (HqlParser.QueryOrderContext) maybeOrderContext ); + i = 2; + } + else { + i = 1; + } + final int size = children.size(); + final SqmCreationProcessingState firstProcessingState = processingStateStack.pop(); + for ( ; i < size; i += 2 ) { + final SetOperator operator = visitSetOperator( (HqlParser.SetOperatorContext) children.get( i ) ); + final ParseTree parseTree = children.get( i + 1 ); + final List> queryParts; + if ( queryGroup.getSetOperator() == null || queryGroup.getSetOperator() == operator ) { + queryGroup.setSetOperator( operator ); + queryParts = queryGroup.getQueryParts(); + } + else { + queryParts = new ArrayList<>( size - ( i >> 1 ) ); + queryParts.add( queryGroup ); + queryGroup = new SqmQueryGroup( + creationContext.getNodeBuilder(), + operator, + queryParts + ); + setCurrentQueryPart( queryGroup ); + } + + final SqmQueryPart queryPart; + try { + processingStateStack.push( + new SqmQuerySpecCreationProcessingStateStandardImpl( + processingStateStack.getCurrent(), + (SqmSelectQuery) firstProcessingState.getProcessingQuery(), + this + ) + ); + if ( parseTree instanceof HqlParser.QuerySpecContext ) { + final SqmQuerySpec querySpec = new SqmQuerySpec<>( creationContext.getNodeBuilder() ); + queryParts.add( querySpec ); + queryPart = visitQuerySpec( (HqlParser.QuerySpecContext) parseTree ); + } + else { + queryPart = (SqmQueryPart) children.get( i + 2 ).accept( this ); + queryParts.add( queryPart ); + i += 2; + } + } + finally { + processingStateStack.pop(); + } + final List selections = queryPart.getFirstQuerySpec().getSelectClause().getSelections(); + if ( firstSelectionSize != selections.size() ) { + throw new SemanticException( "All query parts must have the same arity!" ); + } + for ( int j = 0; j < firstSelectionSize; j++ ) { + final JavaTypeDescriptor firstJavaTypeDescriptor = firstSelections.get( j ).getNodeJavaTypeDescriptor(); + if ( firstJavaTypeDescriptor != selections.get( j ).getNodeJavaTypeDescriptor() ) { + throw new SemanticException( + "Select items of the same index must have the same java type across all query parts!" + ); + } + } + } + processingStateStack.push( firstProcessingState ); + + return queryGroup; + } + + @Override + public SetOperator visitSetOperator(HqlParser.SetOperatorContext ctx) { + final Token token = ( (TerminalNode) ctx.getChild( 0 ) ).getSymbol(); + final boolean all = ctx.getChildCount() == 2; + switch ( token.getType() ) { + case UNION: + return all ? SetOperator.UNION_ALL : SetOperator.UNION; + case INTERSECT: + return all ? SetOperator.INTERSECT_ALL : SetOperator.INTERSECT; + case EXCEPT: + return all ? SetOperator.EXCEPT_ALL : SetOperator.EXCEPT; + } + throw new SemanticException( "Illegal set operator token: " + token.getText() ); + } + + protected void visitQueryOrder(SqmQueryPart sqmQueryPart, HqlParser.QueryOrderContext ctx) { + if ( ctx == null ) { + return; + } + final SqmOrderByClause orderByClause; + final HqlParser.OrderByClauseContext orderByClauseContext = ctx.orderByClause(); + if ( orderByClauseContext != null ) { + if ( creationOptions.useStrictJpaCompliance() && processingStateStack.depth() > 1 ) { + throw new StrictJpaComplianceViolation( + StrictJpaComplianceViolation.Type.SUBQUERY_ORDER_BY + ); + } + + orderByClause = visitOrderByClause( orderByClauseContext ); + sqmQueryPart.setOrderByClause( orderByClause ); + } + else { + orderByClause = null; + } + + final HqlParser.LimitClauseContext limitClauseContext = ctx.limitClause(); + final HqlParser.OffsetClauseContext offsetClauseContext = ctx.offsetClause(); + final HqlParser.FetchClauseContext fetchClauseContext = ctx.fetchClause(); + if ( limitClauseContext != null || offsetClauseContext != null || fetchClauseContext != null ) { + if ( getCreationOptions().useStrictJpaCompliance() ) { + throw new StrictJpaComplianceViolation( + StrictJpaComplianceViolation.Type.LIMIT_OFFSET_CLAUSE + ); + } + + if ( processingStateStack.depth() > 1 && orderByClause == null ) { + throw new SemanticException( + "limit, offset and fetch clause require an order-by clause when used in sub-query" + ); + } + + sqmQueryPart.setOffsetExpression( visitOffsetClause( offsetClauseContext ) ); + if ( limitClauseContext == null ) { + sqmQueryPart.setFetchExpression( visitFetchClause( fetchClauseContext ), visitFetchClauseType( fetchClauseContext ) ); + } + else if ( fetchClauseContext == null ) { + sqmQueryPart.setFetchExpression( visitLimitClause( limitClauseContext ) ); + } + else { + throw new SemanticException("Can't use both, limit and fetch clause!" ); + } + } + } + @Override public SqmQuerySpec visitQuerySpec(HqlParser.QuerySpecContext ctx) { final SqmQuerySpec sqmQuerySpec = currentQuerySpec(); @@ -488,11 +663,18 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre treatHandlerStack.pop(); } + final HqlParser.SelectClauseContext selectClauseContext = ctx.selectClause(); final SqmSelectClause selectClause; - if ( ctx.selectClause() != null ) { - selectClause = visitSelectClause( ctx.selectClause() ); + if ( selectClauseContext != null ) { + selectClause = visitSelectClause( selectClauseContext ); } else { + if ( creationOptions.useStrictJpaCompliance() ) { + throw new StrictJpaComplianceViolation( + "Encountered implicit select-clause, but strict JPQL compliance was requested", + StrictJpaComplianceViolation.Type.IMPLICIT_SELECT + ); + } log.debugf( "Encountered implicit select clause : %s", ctx.getText() ); selectClause = buildInferredSelectClause( sqmQuerySpec.getFromClause() ); } @@ -519,41 +701,6 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre sqmQuerySpec.setHavingClausePredicate( visitHavingClause( havingClauseContext ) ); } - final SqmOrderByClause orderByClause; - final HqlParser.OrderByClauseContext orderByClauseContext = ctx.orderByClause(); - if ( orderByClauseContext != null ) { - if ( creationOptions.useStrictJpaCompliance() && processingStateStack.depth() > 1 ) { - throw new StrictJpaComplianceViolation( - StrictJpaComplianceViolation.Type.SUBQUERY_ORDER_BY - ); - } - - orderByClause = visitOrderByClause( orderByClauseContext ); - } - else { - orderByClause = new SqmOrderByClause(); - } - sqmQuerySpec.setOrderByClause( orderByClause ); - - - if ( ctx.limitClause() != null || ctx.offsetClause() != null ) { - if ( getCreationOptions().useStrictJpaCompliance() ) { - throw new StrictJpaComplianceViolation( - StrictJpaComplianceViolation.Type.LIMIT_OFFSET_CLAUSE - ); - } - - if ( processingStateStack.depth() > 1 && orderByClause == null ) { - throw new SemanticException( - "limit and offset clause require an order-by clause when used in sub-query" ); - } - - //noinspection unchecked - sqmQuerySpec.setOffsetExpression( visitOffsetClause( ctx.offsetClause() ) ); - //noinspection unchecked - sqmQuerySpec.setLimitExpression( visitLimitClause( ctx.limitClause() ) ); - } - return sqmQuerySpec; } @@ -866,13 +1013,6 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre return new SqmSortSpecification( sortExpression, sortOrder, nullPrecedence ); } - private SqmExpression wrapCollate(SqmExpression expression, HqlParser.CollationSpecificationContext collationSpecificationContext) { - if ( collationSpecificationContext == null ) { - return expression; - } - return new SqmCollate<>( expression, collationSpecificationContext.collateName().getText() ); - } - @Override public SqmExpression visitSortExpression(HqlParser.SortExpressionContext ctx) { if ( ctx.INTEGER_LITERAL() != null ) { @@ -881,7 +1021,10 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre throw new ParsingException( "COLLATE is not allowed for position based order by items!" ); } final int position = Integer.parseInt( ctx.INTEGER_LITERAL().getText() ); - final SqmSelection selection = getCurrentProcessingState().getPathRegistry().findSelectionByPosition( position ); + SqmSelection selection = getCurrentProcessingState().getPathRegistry().findSelectionByPosition( position ); + if ( selection == null ) { + selection = currentQuerySpec().getSelectClause().getSelections().get( position - 1 ); + } if ( selection == null ) { throw new ParsingException( "Invalid select item position " + position + " used for order by item!" ); } @@ -895,7 +1038,16 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre if ( ctx.identifier() != null ) { final HqlParser.CollationSpecificationContext collationSpecificationContext = ctx.collationSpecification(); - final SqmSelection selection = getCurrentProcessingState().getPathRegistry().findSelectionByAlias( ctx.identifier().getText() ); + final String alias = ctx.identifier().getText(); + SqmSelection selection = getCurrentProcessingState().getPathRegistry().findSelectionByAlias( alias ); + if ( selection == null ) { + for ( SqmSelection sqmSelection : currentQuerySpec().getSelectClause().getSelections() ) { + if ( alias.equals( sqmSelection.getAlias() ) ) { + selection = sqmSelection; + break; + } + } + } if ( selection != null ) { // This is syntactically disallowed if ( collationSpecificationContext != null ) { @@ -928,10 +1080,20 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre private SqmQuerySpec currentQuerySpec() { SqmQuery processingQuery = processingStateStack.getCurrent().getProcessingQuery(); if ( processingQuery instanceof SqmInsertSelectStatement ) { - return ( (SqmInsertSelectStatement) processingQuery ).getSelectQuerySpec(); + return ( (SqmInsertSelectStatement) processingQuery ).getSelectQueryPart().getLastQuerySpec(); } else { - return ( (SqmSelectQuery) processingQuery ).getQuerySpec(); + return ( (SqmSelectQuery) processingQuery ).getQueryPart().getLastQuerySpec(); + } + } + + private void setCurrentQueryPart(SqmQueryPart queryPart) { + SqmQuery processingQuery = processingStateStack.getCurrent().getProcessingQuery(); + if ( processingQuery instanceof SqmInsertSelectStatement ) { + ( (SqmInsertSelectStatement) processingQuery ).setSelectQueryPart( queryPart ); + } + else { + ( (AbstractSqmSelectQuery) processingQuery ).setQueryPart( queryPart ); } } @@ -945,7 +1107,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre return null; } - return (SqmExpression) ctx.parameterOrNumberLiteral().accept( this ); + return (SqmExpression) ctx.getChild( 1 ).accept( this ); } @Override @@ -954,7 +1116,28 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre return null; } - return (SqmExpression) ctx.parameterOrNumberLiteral().accept( this ); + return (SqmExpression) ctx.getChild( 1 ).accept( this ); + } + + @Override + public SqmExpression visitFetchClause(HqlParser.FetchClauseContext ctx) { + if ( ctx == null ) { + return null; + } + + return (SqmExpression) ctx.getChild( 2 ).accept( this ); + } + + private FetchClauseType visitFetchClauseType(HqlParser.FetchClauseContext ctx) { + if ( ctx == null ) { + return FetchClauseType.ROWS_ONLY; + } + if ( ctx.TIES() == null ) { + return ctx.PERCENT() == null ? FetchClauseType.ROWS_ONLY : FetchClauseType.PERCENT_ONLY; + } + else { + return ctx.PERCENT() == null ? FetchClauseType.ROWS_WITH_TIES : FetchClauseType.PERCENT_WITH_TIES; + } } @Override @@ -967,11 +1150,29 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre return (SqmExpression) ctx.function().accept( this ); } + @Override + public SqmExpression visitParameterOrIntegerLiteral(HqlParser.ParameterOrIntegerLiteralContext ctx) { + if ( ctx.INTEGER_LITERAL() != null ) { + return integerLiteral( ctx.INTEGER_LITERAL().getText() ); + } + if ( ctx.parameter() != null ) { + return (SqmExpression) ctx.parameter().accept( this ); + } + + return null; + } + @Override public SqmExpression visitParameterOrNumberLiteral(HqlParser.ParameterOrNumberLiteralContext ctx) { if ( ctx.INTEGER_LITERAL() != null ) { return integerLiteral( ctx.INTEGER_LITERAL().getText() ); } + if ( ctx.FLOAT_LITERAL() != null ) { + return floatLiteral( ctx.FLOAT_LITERAL().getText() ); + } + if ( ctx.DOUBLE_LITERAL() != null ) { + return doubleLiteral( ctx.DOUBLE_LITERAL().getText() ); + } if ( ctx.parameter() != null ) { return (SqmExpression) ctx.parameter().accept( this ); } @@ -1025,10 +1226,15 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre treatHandlerStack.push( new TreatHandlerFromClause() ); try { - final SqmFromClause fromClause = new SqmFromClause( parserFromClause==null ? 0 : parserFromClause.fromClauseSpace().size() ); - if ( parserFromClause!=null ) { - for ( HqlParser.FromClauseSpaceContext parserSpace : parserFromClause.fromClauseSpace() ) { - final SqmRoot sqmPathRoot = visitFromClauseSpace( parserSpace ); + final SqmFromClause fromClause; + if ( parserFromClause == null ) { + fromClause = new SqmFromClause(); + } + else { + final List fromClauseSpaceContexts = parserFromClause.fromClauseSpace(); + fromClause = new SqmFromClause( fromClauseSpaceContexts.size() ); + for ( int i = 0; i < fromClauseSpaceContexts.size(); i++ ) { + final SqmRoot sqmPathRoot = visitFromClauseSpace( fromClauseSpaceContexts.get( i ) ); fromClause.addRoot( sqmPathRoot ); } } @@ -1062,14 +1268,59 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre @Override @SuppressWarnings( { "rawtypes", "unchecked" } ) public SqmRoot visitPathRoot(HqlParser.PathRootContext ctx) { - final String name = ctx.entityName().fullNameText; + final HqlParser.EntityNameContext entityNameContext = ctx.entityName(); + final List entityNameParseTreeChildren = entityNameContext.children; + final String name = entityNameContext.fullNameText; log.debugf( "Handling root path - %s", name ); - final EntityDomainType entityDescriptor = getCreationContext() .getJpaMetamodel() - .resolveHqlEntityReference( name ); + .getHqlEntityReference( name ); + final String alias = applyJpaCompliance( + visitIdentificationVariableDef( ctx.identificationVariableDef() ) + ); + + final SqmCreationProcessingState processingState = processingStateStack.getCurrent(); + final SqmPathRegistry pathRegistry = processingState.getPathRegistry(); + if ( entityDescriptor == null ) { + final int size = entityNameParseTreeChildren.size(); + // Handle the use of a correlation path in subqueries + if ( processingStateStack.depth() > 1 && size > 2 ) { + final String parentAlias = entityNameParseTreeChildren.get( 0 ).getText(); + final AbstractSqmFrom correlationBasis = (AbstractSqmFrom) processingState.getParentProcessingState() + .getPathRegistry() + .findFromByAlias( parentAlias ); + if ( correlationBasis != null ) { + final SqmCorrelation correlation = correlationBasis.createCorrelation( + ); + pathRegistry.register( correlation ); + final DotIdentifierConsumer dotIdentifierConsumer = new QualifiedJoinPathConsumer( + correlation, + SqmJoinType.INNER, + false, + alias, + this + ); + final int lastIdx = size - 1; + for ( int i = 2; i != lastIdx; i += 2 ) { + dotIdentifierConsumer.consumeIdentifier( + entityNameParseTreeChildren.get( i ).getText(), + false, + false + ); + } + dotIdentifierConsumer.consumeIdentifier( + entityNameParseTreeChildren.get( lastIdx ).getText(), + false, + true + ); + return correlation.getCorrelatedRoot(); + } + throw new IllegalArgumentException( "Could not resolve entity reference or correlation path: " + name ); + } + throw new IllegalArgumentException( "Could not resolve entity reference: " + name ); + } checkFQNEntityNameJpaComplianceViolationIfNeeded( name, entityDescriptor ); if ( entityDescriptor instanceof SqmPolymorphicRootDescriptor ) { @@ -1088,13 +1339,9 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre } } - final String alias = applyJpaCompliance( - visitIdentificationVariableDef( ctx.identificationVariableDef() ) - ); - final SqmRoot sqmRoot = new SqmRoot( entityDescriptor, alias, creationContext.getNodeBuilder() ); - processingStateStack.getCurrent().getPathRegistry().register( sqmRoot ); + pathRegistry.register( sqmRoot ); return sqmRoot; } @@ -1384,7 +1631,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre } @Override - public SqmComparisonPredicate visitComparisonPredicate(HqlParser.ComparisonPredicateContext ctx) { + public SqmPredicate visitComparisonPredicate(HqlParser.ComparisonPredicateContext ctx) { final ComparisonOperator comparisonOperator = (ComparisonOperator) ctx.comparisonOperator().accept( this ); final List expressionContexts = ctx.expression(); final SqmExpression left; @@ -1413,6 +1660,24 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre ); break; } + left = (SqmExpression) leftExpressionContext.accept( this ); + right = (SqmExpression) rightExpressionContext.accept( this ); + // This is something that we used to support before 6 which is also used in our testsuite + if ( left instanceof SqmLiteralNull ) { + return new SqmNullnessPredicate( + right, + comparisonOperator == ComparisonOperator.NOT_EQUAL, + creationContext.getNodeBuilder() + ); + } + else if ( right instanceof SqmLiteralNull ) { + return new SqmNullnessPredicate( + left, + comparisonOperator == ComparisonOperator.NOT_EQUAL, + creationContext.getNodeBuilder() + ); + } + break; } default: { left = (SqmExpression) leftExpressionContext.accept( this ); @@ -1727,7 +1992,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre else if (ctx.SLASH() != null) { return BinaryArithmeticOperator.DIVIDE; } - else if (ctx.PERCENT() != null) { + else if (ctx.PERCENT_OP() != null) { return BinaryArithmeticOperator.MODULO; } else { @@ -1822,11 +2087,26 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre @Override public Object visitCollateExpression(HqlParser.CollateExpressionContext ctx) { - return wrapCollate( (SqmExpression) ctx.primaryExpression().accept( this ), ctx.collationSpecification() ); + SqmExpression expression = (SqmExpression) ctx.primaryExpression().accept( this ); + HqlParser.CollationSpecificationContext collationSpecificationContext = ctx.collationSpecification(); + if ( collationSpecificationContext == null ) { + return expression; + } + if ( creationOptions.useStrictJpaCompliance() ) { + throw new StrictJpaComplianceViolation( + StrictJpaComplianceViolation.Type.COLLATIONS + ); + } + return new SqmCollate<>( expression, collationSpecificationContext.collateName().getText() ); } @Override public Object visitTupleExpression(HqlParser.TupleExpressionContext ctx) { + if ( creationOptions.useStrictJpaCompliance() ) { + throw new StrictJpaComplianceViolation( + StrictJpaComplianceViolation.Type.TUPLES + ); + } final List> expressions = visitExpressions( ctx.expression() ); return new SqmTuple<>( expressions, creationContext.getNodeBuilder() ); } @@ -3572,10 +3852,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre @Override public SqmSubQuery visitSubQuery(HqlParser.SubQueryContext ctx) { - if ( ctx.querySpec().selectClause() == null ) { - throw new SemanticException( "Sub-query cannot use implicit select-clause : " + ctx.getText() ); - } - + final HqlParser.QueryExpressionContext queryExpressionContext = ctx.queryExpression(); final SqmSubQuery subQuery = new SqmSubQuery<>( processingStateStack.getCurrent().getProcessingQuery(), creationContext.getNodeBuilder() @@ -3590,7 +3867,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre ); try { - visitQuerySpec( ctx.querySpec() ); + visitQueryExpression( queryExpressionContext ); return subQuery; } finally { diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/DelegatingQueryOptions.java b/hibernate-core/src/main/java/org/hibernate/query/spi/DelegatingQueryOptions.java new file mode 100644 index 0000000000..05c8c4b1a4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/DelegatingQueryOptions.java @@ -0,0 +1,131 @@ +/* + * 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.spi; + +import java.util.List; +import javax.persistence.CacheRetrieveMode; +import javax.persistence.CacheStoreMode; + +import org.hibernate.CacheMode; +import org.hibernate.FlushMode; +import org.hibernate.LockOptions; +import org.hibernate.graph.spi.AppliedGraph; +import org.hibernate.query.Limit; +import org.hibernate.query.ResultListTransformer; +import org.hibernate.query.TupleTransformer; + +/** + * @author Christian Beikov + */ +public class DelegatingQueryOptions implements QueryOptions { + + private final QueryOptions queryOptions; + + public DelegatingQueryOptions(QueryOptions queryOptions) { + this.queryOptions = queryOptions; + } + + @Override + public Integer getTimeout() { + return queryOptions.getTimeout(); + } + + @Override + public FlushMode getFlushMode() { + return queryOptions.getFlushMode(); + } + + @Override + public Boolean isReadOnly() { + return queryOptions.isReadOnly(); + } + + @Override + public AppliedGraph getAppliedGraph() { + return queryOptions.getAppliedGraph(); + } + + @Override + public TupleTransformer getTupleTransformer() { + return queryOptions.getTupleTransformer(); + } + + @Override + public ResultListTransformer getResultListTransformer() { + return queryOptions.getResultListTransformer(); + } + + @Override + public Boolean isResultCachingEnabled() { + return queryOptions.isResultCachingEnabled(); + } + + @Override + public CacheRetrieveMode getCacheRetrieveMode() { + return queryOptions.getCacheRetrieveMode(); + } + + @Override + public CacheStoreMode getCacheStoreMode() { + return queryOptions.getCacheStoreMode(); + } + + @Override + public CacheMode getCacheMode() { + return queryOptions.getCacheMode(); + } + + @Override + public String getResultCacheRegionName() { + return queryOptions.getResultCacheRegionName(); + } + + @Override + public LockOptions getLockOptions() { + return queryOptions.getLockOptions(); + } + + @Override + public String getComment() { + return queryOptions.getComment(); + } + + @Override + public List getDatabaseHints() { + return queryOptions.getDatabaseHints(); + } + + @Override + public Integer getFetchSize() { + return queryOptions.getFetchSize(); + } + + @Override + public Limit getLimit() { + return queryOptions.getLimit(); + } + + @Override + public Integer getFirstRow() { + return queryOptions.getFirstRow(); + } + + @Override + public Integer getMaxRows() { + return queryOptions.getMaxRows(); + } + + @Override + public Limit getEffectiveLimit() { + return queryOptions.getEffectiveLimit(); + } + + @Override + public boolean hasLimit() { + return queryOptions.hasLimit(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/SqlOmittingQueryOptions.java b/hibernate-core/src/main/java/org/hibernate/query/spi/SqlOmittingQueryOptions.java new file mode 100644 index 0000000000..311634ae06 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/SqlOmittingQueryOptions.java @@ -0,0 +1,94 @@ +/* + * 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.spi; + +import org.hibernate.LockOptions; +import org.hibernate.query.Limit; +import org.hibernate.sql.exec.internal.DelegatingExecutionContext; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcSelect; + +/** + * @author Christian Beikov + */ +public class SqlOmittingQueryOptions extends DelegatingQueryOptions { + + private final boolean omitLimit; + private final boolean omitLocks; + + public SqlOmittingQueryOptions(QueryOptions queryOptions, boolean omitLimit, boolean omitLocks) { + super( queryOptions ); + this.omitLimit = omitLimit; + this.omitLocks = omitLocks; + } + + public static ExecutionContext omitSqlQueryOptions(ExecutionContext context) { + return omitSqlQueryOptions( context, true, true ); + } + + public static ExecutionContext omitSqlQueryOptions(ExecutionContext context, JdbcSelect select) { + return omitSqlQueryOptions( context, !select.usesLimitParameters(), true ); + } + + public static ExecutionContext omitSqlQueryOptions(ExecutionContext context, boolean omitLimit, boolean omitLocks) { + final QueryOptions originalQueryOptions = context.getQueryOptions(); + final Limit limit = originalQueryOptions.getLimit(); + // No need for a context when there are no options we use during SQL rendering + if ( originalQueryOptions.getLockOptions().isEmpty() ) { + if ( !omitLimit || limit == null || limit.isEmpty() ) { + return context; + } + } + else if ( !omitLocks ) { + if ( !omitLimit || limit == null || limit.isEmpty() ) { + return context; + } + } + final QueryOptions queryOptions = new SqlOmittingQueryOptions( originalQueryOptions, omitLimit, omitLocks ); + return new DelegatingExecutionContext( context ) { + @Override + public QueryOptions getQueryOptions() { + return queryOptions; + } + }; + } + + @Override + public LockOptions getLockOptions() { + return omitLocks ? LockOptions.NONE : super.getLockOptions(); + } + + @Override + public Integer getFetchSize() { + return null; + } + + @Override + public Limit getLimit() { + return omitLimit ? Limit.NONE : super.getLimit(); + } + + @Override + public Integer getFirstRow() { + return omitLimit ? null : super.getFirstRow(); + } + + @Override + public Integer getMaxRows() { + return omitLimit ? null : super.getMaxRows(); + } + + @Override + public Limit getEffectiveLimit() { + return omitLimit ? Limit.NONE : super.getEffectiveLimit(); + } + + @Override + public boolean hasLimit() { + return omitLimit ? false : super.hasLimit(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeSelectQueryPlanImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeSelectQueryPlanImpl.java index aad5fb2d31..bf0e805daa 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeSelectQueryPlanImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeSelectQueryPlanImpl.java @@ -14,6 +14,7 @@ import java.util.Set; import org.hibernate.ScrollMode; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.EmptyScrollableResults; import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.model.domain.AllowableParameterType; @@ -71,6 +72,9 @@ public class NativeSelectQueryPlanImpl implements NativeSelectQueryPlan { @Override public List performList(ExecutionContext executionContext) { + if ( executionContext.getQueryOptions().getEffectiveLimit().getMaxRowsJpa() == 0 ) { + return Collections.emptyList(); + } final List jdbcParameterBinders; final JdbcParameterBindings jdbcParameterBindings; @@ -129,6 +133,9 @@ public class NativeSelectQueryPlanImpl implements NativeSelectQueryPlan { @Override public ScrollableResultsImplementor performScroll(ScrollMode scrollMode, ExecutionContext executionContext) { + if ( executionContext.getQueryOptions().getEffectiveLimit().getMaxRowsJpa() == 0 ) { + return EmptyScrollableResults.INSTANCE; + } final List jdbcParameterBinders; final JdbcParameterBindings jdbcParameterBindings; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java index 5f30fdd0d1..f13b889e71 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java @@ -9,7 +9,7 @@ package org.hibernate.query.sqm; import java.util.List; import org.hibernate.NotYetImplementedFor6Exception; -import org.hibernate.query.sqm.tree.cte.SqmCteConsumer; +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.NonAggregatedCompositeSimplePath; @@ -84,6 +84,8 @@ import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation; import org.hibernate.query.sqm.tree.select.SqmJpaCompoundSelection; import org.hibernate.query.sqm.tree.select.SqmOrderByClause; +import org.hibernate.query.sqm.tree.select.SqmQueryGroup; +import org.hibernate.query.sqm.tree.select.SqmQueryPart; import org.hibernate.query.sqm.tree.select.SqmQuerySpec; import org.hibernate.query.sqm.tree.select.SqmSelectClause; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; @@ -114,9 +116,9 @@ public interface SemanticQueryWalker { T visitSelectStatement(SqmSelectStatement statement); - T visitCteStatement(SqmCteStatement sqmCteStatement); + T visitCteStatement(SqmCteStatement sqmCteStatement); - T visitCteConsumer(SqmCteConsumer consumer); + T visitCteContainer(SqmCteContainer consumer); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -160,6 +162,8 @@ public interface SemanticQueryWalker { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Query spec + T visitQueryGroup(SqmQueryGroup queryGroup); + T visitQuerySpec(SqmQuerySpec querySpec); T visitSelectClause(SqmSelectClause selectClause); @@ -290,7 +294,7 @@ public interface SemanticQueryWalker { // paging T visitOffsetExpression(SqmExpression expression); - T visitLimitExpression(SqmExpression expression); + T visitFetchExpression(SqmExpression expression); 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 d50ea16af4..b08ca8faf7 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 @@ -23,7 +23,10 @@ public class StrictJpaComplianceViolation extends SemanticException { VALUE_FUNCTION_ON_NON_MAP( "use of value() function for non-Map type" ), RESERVED_WORD_USED_AS_ALIAS( "use of reserved word as alias (identification variable or result variable)" ), INDEXED_ELEMENT_REFERENCE( "use of HQL indexed element reference syntax" ), + TUPLES( "use of tuples/row value constructors" ), + COLLATIONS( "use of collations" ), SUBQUERY_ORDER_BY( "use of ORDER BY clause in subquery" ), + SET_OPERATIONS( "use of set operations" ), 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" ), 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 ffe50f3737..c6437afc76 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 @@ -7,9 +7,11 @@ package org.hibernate.query.sqm.internal; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.hibernate.ScrollMode; +import org.hibernate.internal.EmptyScrollableResults; import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.query.spi.SelectQueryPlan; import org.hibernate.NotYetImplementedFor6Exception; @@ -27,6 +29,9 @@ public class AggregatedSelectQueryPlanImpl implements SelectQueryPlan { @Override public List performList(ExecutionContext executionContext) { + if ( executionContext.getQueryOptions().getEffectiveLimit().getMaxRowsJpa() == 0 ) { + return Collections.emptyList(); + } final List overallResults = new ArrayList<>(); for ( SelectQueryPlan aggregatedQueryPlan : aggregatedQueryPlans ) { @@ -38,6 +43,9 @@ public class AggregatedSelectQueryPlanImpl implements SelectQueryPlan { @Override public ScrollableResultsImplementor performScroll(ScrollMode scrollMode, ExecutionContext executionContext) { + if ( executionContext.getQueryOptions().getEffectiveLimit().getMaxRowsJpa() == 0 ) { + return EmptyScrollableResults.INSTANCE; + } throw new NotYetImplementedFor6Exception(); } } 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 1de86c6cac..977d6e7e6a 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 @@ -7,6 +7,7 @@ package org.hibernate.query.sqm.internal; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import javax.persistence.Tuple; @@ -17,6 +18,7 @@ import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.EmptyScrollableResults; import org.hibernate.internal.util.streams.StingArrayCollector; import org.hibernate.query.IllegalQueryOperationException; import org.hibernate.query.spi.QueryEngine; @@ -24,15 +26,18 @@ import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.query.spi.SelectQueryPlan; -import org.hibernate.query.sqm.sql.SqmSelectTranslation; -import org.hibernate.query.sqm.sql.SqmSelectTranslator; +import org.hibernate.query.spi.SqlOmittingQueryOptions; +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.SqmSelectStatement; import org.hibernate.query.sqm.tree.select.SqmSelection; +import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcSelect; @@ -53,8 +58,9 @@ import org.hibernate.sql.results.spi.RowTransformer; public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { private final SqmSelectStatement sqm; private final DomainParameterXref domainParameterXref; - private final RowTransformer rowTransformer; + private final SqmInterpreter, Void> listInterpreter; + private final SqmInterpreter, ScrollMode> scrollInterpreter; @SuppressWarnings("WeakerAccess") public ConcreteSqmSelectQueryPlan( @@ -66,6 +72,37 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { this.domainParameterXref = domainParameterXref; this.rowTransformer = determineRowTransformer( sqm, resultType, queryOptions ); + this.listInterpreter = (unused, executionContext, sqmInterpretation, jdbcParameterBindings) -> { + final SharedSessionContractImplementor session = executionContext.getSession(); + final JdbcSelect jdbcSelect = sqmInterpretation.getJdbcSelect(); + try { + session.autoFlushIfRequired( jdbcSelect.getAffectedTableNames() ); + return session.getFactory().getJdbcServices().getJdbcSelectExecutor().list( + jdbcSelect, + jdbcParameterBindings, + SqlOmittingQueryOptions.omitSqlQueryOptions( executionContext, jdbcSelect ), + rowTransformer, + true + ); + } + finally { + domainParameterXref.clearExpansions(); + } + }; + this.scrollInterpreter = (scrollMode, executionContext, sqmInterpretation, jdbcParameterBindings) -> { + try { + return executionContext.getSession().getFactory().getJdbcServices().getJdbcSelectExecutor().scroll( + sqmInterpretation.getJdbcSelect(), + scrollMode, + jdbcParameterBindings, + executionContext, + rowTransformer + ); + } + finally { + domainParameterXref.clearExpansions(); + } + }; // todo (6.0) : we should do as much of the building as we can here // since this is the thing cached, all the work we do here will @@ -95,13 +132,15 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { // NOTE : if we get here, a result-type of some kind (other than Object[].class) was specified + final List selections = sqm.getQuerySpec().getSelectClause().getSelections(); if ( Tuple.class.isAssignableFrom( resultType ) ) { // resultType is Tuple.. if ( queryOptions.getTupleTransformer() == null ) { - final List> tupleElementList = new ArrayList<>(); - for ( SqmSelection selection : sqm.getQuerySpec().getSelectClause().getSelections() ) { + final List> tupleElementList = new ArrayList<>( selections.size() ); + for ( int i = 0; i < selections.size(); i++ ) { + final SqmSelection selection = selections.get( i ); tupleElementList.add( - new TupleElementImpl( + new TupleElementImpl<>( selection.getSelectableNode().getJavaTypeDescriptor().getJavaType(), selection.getAlias() ) @@ -124,7 +163,7 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { // the same type. We rely on the API here and assume the best return makeRowTransformerTupleTransformerAdapter( sqm, queryOptions ); } - else if ( sqm.getQuerySpec().getSelectClause().getSelections().size() > 1 ) { + else if ( selections.size() > 1 ) { throw new IllegalQueryOperationException( "Query defined multiple selections, return cannot be typed (other that Object[] or Tuple)" ); } else { @@ -132,8 +171,7 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { } } - @SuppressWarnings("unchecked") - private RowTransformer makeRowTransformerTupleTransformerAdapter( + private RowTransformer makeRowTransformerTupleTransformerAdapter( SqmSelectStatement sqm, QueryOptions queryOptions) { return new RowTransformerTupleTransformerAdapter<>( @@ -147,80 +185,30 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { @Override public List performList(ExecutionContext executionContext) { - final SharedSessionContractImplementor session = executionContext.getSession(); - - final CacheableSqmInterpretation sqmInterpretation = resolveCacheableSqmInterpretation( executionContext ); - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - sqmInterpretation.getJdbcParamsXref(), - session.getFactory().getDomainModel(), - sqmInterpretation.getTableGroupAccess()::findTableGroup, - session - ); - - final JdbcSelect jdbcSelect = sqmInterpretation.getJdbcSelect(); - jdbcSelect.bindFilterJdbcParameters( jdbcParameterBindings ); - - try { - session.autoFlushIfRequired( jdbcSelect.getAffectedTableNames() ); - return session.getFactory().getJdbcServices().getJdbcSelectExecutor().list( - jdbcSelect, - jdbcParameterBindings, - executionContext, - rowTransformer, - true - ); - } - finally { - domainParameterXref.clearExpansions(); + if ( executionContext.getQueryOptions().getEffectiveLimit().getMaxRowsJpa() == 0 ) { + return Collections.emptyList(); } + return withCacheableSqmInterpretation( executionContext, null, listInterpreter ); } - - - - @Override public ScrollableResultsImplementor performScroll(ScrollMode scrollMode, ExecutionContext executionContext) { - final SharedSessionContractImplementor session = executionContext.getSession(); - - final CacheableSqmInterpretation sqmInterpretation = resolveCacheableSqmInterpretation( executionContext ); - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - sqmInterpretation.getJdbcParamsXref(), - session.getFactory().getDomainModel(), - sqmInterpretation.getTableGroupAccess()::findTableGroup, - session - ); - sqmInterpretation.getJdbcSelect().bindFilterJdbcParameters( jdbcParameterBindings ); - - try { - return session.getFactory().getJdbcServices().getJdbcSelectExecutor().scroll( - sqmInterpretation.getJdbcSelect(), - scrollMode, - jdbcParameterBindings, - executionContext, - rowTransformer - ); - } - finally { - domainParameterXref.clearExpansions(); + if ( executionContext.getQueryOptions().getEffectiveLimit().getMaxRowsJpa() == 0 ) { + return EmptyScrollableResults.INSTANCE; } + return withCacheableSqmInterpretation( executionContext, scrollMode, scrollInterpreter ); } private volatile CacheableSqmInterpretation cacheableSqmInterpretation; - private CacheableSqmInterpretation resolveCacheableSqmInterpretation(ExecutionContext executionContext) { + private T withCacheableSqmInterpretation(ExecutionContext executionContext, X context, SqmInterpreter interpreter) { // NOTE : VERY IMPORTANT - intentional double-lock checking // The other option would be to leverage `java.util.concurrent.locks.ReadWriteLock` // to protect access. However, synchronized is much simpler here. We will verify // during throughput testing whether this is an issue and consider changes then CacheableSqmInterpretation localCopy = cacheableSqmInterpretation; + JdbcParameterBindings jdbcParameterBindings = null; if ( localCopy == null ) { synchronized ( this ) { @@ -231,24 +219,61 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { domainParameterXref, executionContext ); + jdbcParameterBindings = localCopy.firstParameterBindings; + localCopy.firstParameterBindings = null; cacheableSqmInterpretation = localCopy; } } } + else { + // If the translation depends on parameter bindings or it isn't compatible with the current query options, + // we have to rebuild the JdbcSelect, which is still better than having to translate from SQM to SQL AST again + if ( localCopy.jdbcSelect.dependsOnParameterBindings() ) { + jdbcParameterBindings = createJdbcParameterBindings( localCopy, executionContext ); + } + if ( !localCopy.jdbcSelect.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { + localCopy = buildCacheableSqmInterpretation( + sqm, + domainParameterXref, + executionContext + ); + jdbcParameterBindings = localCopy.firstParameterBindings; + localCopy.firstParameterBindings = null; + cacheableSqmInterpretation = localCopy; + } + } + if ( jdbcParameterBindings == null ) { + jdbcParameterBindings = createJdbcParameterBindings( localCopy, executionContext ); + } + return interpreter.interpret( context, executionContext, localCopy, jdbcParameterBindings ); + } - return localCopy; + private JdbcParameterBindings createJdbcParameterBindings(CacheableSqmInterpretation sqmInterpretation, ExecutionContext executionContext) { + final SharedSessionContractImplementor session = executionContext.getSession(); + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + executionContext.getQueryParameterBindings(), + domainParameterXref, + sqmInterpretation.getJdbcParamsXref(), + session.getFactory().getDomainModel(), + sqmInterpretation.getTableGroupAccess()::findTableGroup, + session + ); + sqmInterpretation.getJdbcSelect().bindFilterJdbcParameters( jdbcParameterBindings ); + return jdbcParameterBindings; } private static CacheableSqmInterpretation buildCacheableSqmInterpretation( SqmSelectStatement sqm, - DomainParameterXref domainParameterXref, ExecutionContext executionContext) { + DomainParameterXref domainParameterXref, + ExecutionContext executionContext) { final SharedSessionContractImplementor session = executionContext.getSession(); final SessionFactoryImplementor sessionFactory = session.getFactory(); final QueryEngine queryEngine = sessionFactory.getQueryEngine(); final SqmTranslatorFactory sqmTranslatorFactory = queryEngine.getSqmTranslatorFactory(); - final SqmSelectTranslator sqmConverter = sqmTranslatorFactory.createSelectTranslator( + final SqmTranslator sqmConverter = sqmTranslatorFactory.createSelectTranslator( + sqm, executionContext.getQueryOptions(), domainParameterXref, executionContext.getQueryParameterBindings(), @@ -257,36 +282,62 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { ); // tableGroupAccess = sqmConverter.getFromClauseAccess(); + final SqmTranslation interpretation = sqmConverter.translate(); final FromClauseAccess tableGroupAccess = sqmConverter.getFromClauseAccess(); - final SqmSelectTranslation interpretation = sqmConverter.translate( sqm ); - final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( interpretation.getSqlAst() ); + final SqlAstTranslator selectTranslator = sqlAstTranslatorFactory.buildSelectTranslator( + sessionFactory, + interpretation.getSqlAst() + ); final Map, Map>> jdbcParamsXref = SqmUtil.generateJdbcParamsXref( domainParameterXref, interpretation::getJdbcParamsBySqmParam ); + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + executionContext.getQueryParameterBindings(), + domainParameterXref, + jdbcParamsXref, + session.getFactory().getDomainModel(), + tableGroupAccess::findTableGroup, + session + ); + final JdbcSelect jdbcSelect = selectTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - return new CacheableSqmInterpretation( jdbcSelect, tableGroupAccess, jdbcParamsXref ); + return new CacheableSqmInterpretation( + jdbcSelect, + tableGroupAccess, + jdbcParamsXref, + jdbcParameterBindings + ); + } + + private static interface SqmInterpreter { + T interpret( + X context, + ExecutionContext executionContext, + CacheableSqmInterpretation sqmInterpretation, + JdbcParameterBindings jdbcParameterBindings); } private static class CacheableSqmInterpretation { private final JdbcSelect jdbcSelect; private final FromClauseAccess tableGroupAccess; private final Map, Map>> jdbcParamsXref; + private transient JdbcParameterBindings firstParameterBindings; CacheableSqmInterpretation( JdbcSelect jdbcSelect, FromClauseAccess tableGroupAccess, - Map, Map>> jdbcParamsXref) { + Map, Map>> jdbcParamsXref, + JdbcParameterBindings firstParameterBindings) { this.jdbcSelect = jdbcSelect; this.tableGroupAccess = tableGroupAccess; this.jdbcParamsXref = jdbcParamsXref; + this.firstParameterBindings = firstParameterBindings; } JdbcSelect getJdbcSelect() { @@ -300,5 +351,13 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { Map, Map>> getJdbcParamsXref() { return jdbcParamsXref; } + + JdbcParameterBindings getFirstParameterBindings() { + return firstParameterBindings; + } + + void setFirstParameterBindings(JdbcParameterBindings firstParameterBindings) { + this.firstParameterBindings = firstParameterBindings; + } } } 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 8cb191654b..5905339d99 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 @@ -6,6 +6,7 @@ */ package org.hibernate.query.sqm.internal; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -16,15 +17,22 @@ import javax.persistence.Parameter; import javax.persistence.PersistenceException; import javax.persistence.Tuple; +import org.hibernate.AssertionFailure; +import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.ScrollMode; import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.engine.query.spi.EntityGraphQueryHint; +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.RowSelection; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.collections.IdentitySet; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.Query; import org.hibernate.query.QueryTypeMismatchException; @@ -47,6 +55,7 @@ import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.query.spi.SelectQueryPlan; +import org.hibernate.query.spi.SqlOmittingQueryOptions; import org.hibernate.query.sqm.SqmExpressable; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.tree.SqmDmlStatement; @@ -78,6 +87,7 @@ public class QuerySqmImpl * The value used for {@link #getQueryString} for Criteria-based queries */ public static final String CRITERIA_HQL_STRING = ""; + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( QuerySqmImpl.class ); private final String hqlString; private final SqmStatement sqmStatement; @@ -194,7 +204,7 @@ public class QuerySqmImpl final List sqmSelections = sqmQuerySpec.getSelectClause().getSelections(); // make sure there is at least one root - final List sqmRoots = sqmQuerySpec.getFromClause().getRoots(); + final List> sqmRoots = sqmQuerySpec.getFromClause().getRoots(); if ( sqmRoots == null || sqmRoots.isEmpty() ) { throw new IllegalArgumentException( "Criteria did not define any query roots" ); } @@ -202,7 +212,7 @@ public class QuerySqmImpl if ( sqmSelections == null || sqmSelections.isEmpty() ) { // if there is a single root, use that as the selection if ( sqmRoots.size() == 1 ) { - final SqmRoot sqmRoot = sqmRoots.get( 0 ); + final SqmRoot sqmRoot = sqmRoots.get( 0 ); sqmQuerySpec.getSelectClause().add( sqmRoot, null ); } else { @@ -454,8 +464,63 @@ public class QuerySqmImpl protected List doList() { SqmUtil.verifyIsSelectStatement( getSqmStatement() ); getSession().prepareForQueryExecution( requiresTxn( getLockOptions().findGreatestLockMode() ) ); + final SqmSelectStatement selectStatement = (SqmSelectStatement) getSqmStatement(); + final boolean containsCollectionFetches = selectStatement.getQuerySpec().containsCollectionFetches(); + final boolean hasLimit = queryOptions.hasLimit(); + final boolean needsDistincting = ( + selectStatement.getQuerySpec().getSelectClause().isDistinct() || + queryOptions.getGraph() != null || + hasLimit ) + && containsCollectionFetches; + ExecutionContext executionContextToUse; + if ( queryOptions.hasLimit() && containsCollectionFetches ) { + boolean fail = getSessionFactory().getSessionFactoryOptions().isFailOnPaginationOverCollectionFetchEnabled(); + if (fail) { + throw new HibernateException( + "firstResult/maxResults specified with collection fetch. " + + "In memory pagination was about to be applied. " + + "Failing because 'Fail on pagination over collection fetch' is enabled." + ); + } + else { + LOG.firstOrMaxResultsSpecifiedWithCollectionFetch(); + } + executionContextToUse = SqlOmittingQueryOptions.omitSqlQueryOptions( this, true, false ); + } + else { + executionContextToUse = this; + } - return resolveSelectQueryPlan().performList( this ); + final List list = resolveSelectQueryPlan().performList( executionContextToUse ); + + if ( needsDistincting ) { + int includedCount = -1; + // NOTE : firstRow is zero-based + final int first = !hasLimit || queryOptions.getLimit().getFirstRow() == null + ? 0 + : queryOptions.getLimit().getFirstRow(); + final int max = !hasLimit || queryOptions.getLimit().getMaxRows() == null + ? -1 + : queryOptions.getLimit().getMaxRows(); + final List tmp = new ArrayList<>( list.size() ); + final IdentitySet distinction = new IdentitySet<>( list.size() ); + for ( final R result : list ) { + if ( !distinction.add( result ) ) { + continue; + } + includedCount++; + if ( includedCount < first ) { + continue; + } + tmp.add( result ); + // NOTE : ( max - 1 ) because first is zero-based while max is not... + if ( max >= 0 && ( includedCount - first ) >= ( max - 1 ) ) { + break; + } + } + return tmp; + } + return list; } private boolean requiresTxn(LockMode lockMode) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java index bfed5831bc..0aa2d31603 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java @@ -20,14 +20,16 @@ import org.hibernate.query.NavigablePath; import org.hibernate.query.spi.NonSelectQueryPlan; import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryParameterImplementor; +import org.hibernate.query.spi.SqlOmittingQueryOptions; import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; -import org.hibernate.query.sqm.sql.SimpleSqmDeleteTranslation; -import org.hibernate.query.sqm.sql.SimpleSqmDeleteTranslator; +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.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.sql.ast.SqlAstDeleteTranslator; +import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper; import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; @@ -47,7 +49,7 @@ public class SimpleDeleteQueryPlan implements NonSelectQueryPlan { private final DomainParameterXref domainParameterXref; private JdbcDelete jdbcDelete; - private SimpleSqmDeleteTranslation sqmInterpretation; + private SqmTranslation sqmInterpretation; private Map, Map>> jdbcParamsXref; public SimpleDeleteQueryPlan( @@ -61,37 +63,39 @@ public class SimpleDeleteQueryPlan implements NonSelectQueryPlan { this.domainParameterXref = domainParameterXref; } + private SqlAstTranslator createDeleteTranslator(ExecutionContext executionContext) { + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + final QueryEngine queryEngine = factory.getQueryEngine(); + + final SqmTranslatorFactory translatorFactory = queryEngine.getSqmTranslatorFactory(); + final SqmTranslator translator = translatorFactory.createSimpleDeleteTranslator( + sqmDelete, + executionContext.getQueryOptions(), + domainParameterXref, + executionContext.getQueryParameterBindings(), + executionContext.getLoadQueryInfluencers(), + factory + ); + + sqmInterpretation = translator.translate(); + + this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( + domainParameterXref, + sqmInterpretation::getJdbcParamsBySqmParam + ); + + return factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory() + .buildDeleteTranslator( factory, sqmInterpretation.getSqlAst() ); + } + @Override public int executeUpdate(ExecutionContext executionContext) { final SharedSessionContractImplementor session = executionContext.getSession(); final SessionFactoryImplementor factory = session.getFactory(); final JdbcServices jdbcServices = factory.getJdbcServices(); - + SqlAstTranslator deleteTranslator = null; if ( jdbcDelete == null ) { - final QueryEngine queryEngine = factory.getQueryEngine(); - - final SqmTranslatorFactory translatorFactory = queryEngine.getSqmTranslatorFactory(); - final SimpleSqmDeleteTranslator translator = translatorFactory.createSimpleDeleteTranslator( - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getLoadQueryInfluencers(), - factory - ); - - sqmInterpretation = translator.translate( sqmDelete ); - - final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - - final SqlAstDeleteTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildDeleteTranslator( factory ); - - jdbcDelete = sqlAstTranslator.translate( sqmInterpretation.getSqlAst() ); - - this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( - domainParameterXref, - sqmInterpretation::getJdbcParamsBySqmParam - ); + deleteTranslator = createDeleteTranslator( executionContext ); } final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( @@ -102,7 +106,20 @@ public class SimpleDeleteQueryPlan implements NonSelectQueryPlan { sqmInterpretation.getFromClauseAccess()::findTableGroup, session ); - jdbcDelete.bindFilterJdbcParameters( jdbcParameterBindings ); + + if ( jdbcDelete != null && !jdbcDelete.isCompatibleWith( + jdbcParameterBindings, + executionContext.getQueryOptions() + ) ) { + deleteTranslator = createDeleteTranslator( executionContext ); + } + + if ( deleteTranslator != null ) { + jdbcDelete = deleteTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + } + else { + jdbcDelete.bindFilterJdbcParameters( jdbcParameterBindings ); + } final boolean missingRestriction = sqmDelete.getWhereClause() == null || sqmDelete.getWhereClause().getPredicate() == null; @@ -158,7 +175,7 @@ public class SimpleDeleteQueryPlan implements NonSelectQueryPlan { .getStatementPreparer() .prepareStatement( sql ), (integer, preparedStatement) -> {}, - executionContext + SqlOmittingQueryOptions.omitSqlQueryOptions( executionContext ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleInsertQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleInsertQueryPlan.java index 8b7d10f939..f90d14d779 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleInsertQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleInsertQueryPlan.java @@ -13,15 +13,17 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.spi.NonSelectQueryPlan; import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryParameterImplementor; -import org.hibernate.query.sqm.sql.SqmInsertTranslation; -import org.hibernate.query.sqm.sql.SqmInsertTranslator; +import org.hibernate.query.spi.SqlOmittingQueryOptions; +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.insert.SqmInsertStatement; -import org.hibernate.sql.ast.SqlAstInsertTranslator; +import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.insert.InsertStatement; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcInsert; import org.hibernate.sql.exec.spi.JdbcParameterBindings; @@ -47,42 +49,43 @@ public class SimpleInsertQueryPlan implements NonSelectQueryPlan { this.domainParameterXref = domainParameterXref; } + private SqlAstTranslator createInsertTranslator(ExecutionContext executionContext) { + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + final QueryEngine queryEngine = factory.getQueryEngine(); + + final SqmTranslatorFactory translatorFactory = queryEngine.getSqmTranslatorFactory(); + final SqmTranslator translator = translatorFactory.createInsertTranslator( + sqmInsert, + executionContext.getQueryOptions(), + domainParameterXref, + executionContext.getQueryParameterBindings(), + executionContext.getLoadQueryInfluencers(), + factory + ); + + final SqmTranslation sqmInterpretation = translator.translate(); + + tableGroupAccess = sqmInterpretation.getFromClauseAccess(); + + this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( + domainParameterXref, + sqmInterpretation::getJdbcParamsBySqmParam + ); + + return factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory() + .buildInsertTranslator( factory, sqmInterpretation.getSqlAst() ); + } + @Override public int executeUpdate(ExecutionContext executionContext) { final SharedSessionContractImplementor session = executionContext.getSession(); final SessionFactoryImplementor factory = session.getFactory(); final JdbcServices jdbcServices = factory.getJdbcServices(); - + SqlAstTranslator insertTranslator = null; if ( jdbcInsert == null ) { - final QueryEngine queryEngine = factory.getQueryEngine(); - - final SqmTranslatorFactory translatorFactory = queryEngine.getSqmTranslatorFactory(); - final SqmInsertTranslator translator = translatorFactory.createInsertTranslator( - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getLoadQueryInfluencers(), - factory - ); - - final SqmInsertTranslation sqmInterpretation = translator.translate(sqmInsert); - - tableGroupAccess = translator.getFromClauseAccess(); - - this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( - domainParameterXref, - sqmInterpretation::getJdbcParamsBySqmParam - ); - - final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - - final SqlAstInsertTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildInsertTranslator( factory ); - - jdbcInsert = sqlAstTranslator.translate( sqmInterpretation.getSqlAst() ); + insertTranslator = createInsertTranslator( executionContext ); } - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( executionContext.getQueryParameterBindings(), domainParameterXref, @@ -92,7 +95,19 @@ public class SimpleInsertQueryPlan implements NonSelectQueryPlan { session ); - jdbcInsert.bindFilterJdbcParameters( jdbcParameterBindings ); + if ( jdbcInsert != null && !jdbcInsert.isCompatibleWith( + jdbcParameterBindings, + executionContext.getQueryOptions() + ) ) { + insertTranslator = createInsertTranslator( executionContext ); + } + + if ( insertTranslator != null ) { + jdbcInsert = insertTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + } + else { + jdbcInsert.bindFilterJdbcParameters( jdbcParameterBindings ); + } return jdbcServices.getJdbcMutationExecutor().execute( jdbcInsert, @@ -102,7 +117,7 @@ public class SimpleInsertQueryPlan implements NonSelectQueryPlan { .getStatementPreparer() .prepareStatement( sql ), (integer, preparedStatement) -> {}, - executionContext + SqlOmittingQueryOptions.omitSqlQueryOptions( executionContext ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleUpdateQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleUpdateQueryPlan.java index 30751585cc..4c29dc840a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleUpdateQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleUpdateQueryPlan.java @@ -16,14 +16,16 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.spi.NonSelectQueryPlan; import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryParameterImplementor; -import org.hibernate.query.sqm.sql.SimpleSqmUpdateTranslation; -import org.hibernate.query.sqm.sql.SimpleSqmUpdateTranslator; +import org.hibernate.query.spi.SqlOmittingQueryOptions; +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.update.SqmUpdateStatement; +import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.ast.SqlAstUpdateTranslator; import org.hibernate.sql.ast.spi.FromClauseAccess; +import org.hibernate.sql.ast.tree.update.UpdateStatement; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.exec.spi.JdbcParameterBindings; @@ -52,37 +54,11 @@ public class SimpleUpdateQueryPlan implements NonSelectQueryPlan { final SharedSessionContractImplementor session = executionContext.getSession(); final SessionFactoryImplementor factory = session.getFactory(); final JdbcServices jdbcServices = factory.getJdbcServices(); - + SqlAstTranslator updateTranslator = null; if ( jdbcUpdate == null ) { - final QueryEngine queryEngine = factory.getQueryEngine(); - - final SqmTranslatorFactory translatorFactory = queryEngine.getSqmTranslatorFactory(); - final SimpleSqmUpdateTranslator translator = translatorFactory.createSimpleUpdateTranslator( - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getLoadQueryInfluencers(), - factory - ); - - final SimpleSqmUpdateTranslation sqmInterpretation = translator.translate( sqmUpdate ); - - tableGroupAccess = translator.getFromClauseAccess(); - - this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( - domainParameterXref, - sqmInterpretation::getJdbcParamsBySqmParam - ); - - final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - - final SqlAstUpdateTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildUpdateTranslator( factory ); - - jdbcUpdate = sqlAstTranslator.translate( sqmInterpretation.getSqlAst() ); + updateTranslator = createUpdateTranslator( executionContext ); } - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( executionContext.getQueryParameterBindings(), domainParameterXref, @@ -91,7 +67,20 @@ public class SimpleUpdateQueryPlan implements NonSelectQueryPlan { tableGroupAccess::findTableGroup, session ); - jdbcUpdate.bindFilterJdbcParameters( jdbcParameterBindings ); + + if ( jdbcUpdate != null && !jdbcUpdate.isCompatibleWith( + jdbcParameterBindings, + executionContext.getQueryOptions() + ) ) { + updateTranslator = createUpdateTranslator( executionContext ); + } + + if ( updateTranslator != null ) { + jdbcUpdate = updateTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + } + else { + jdbcUpdate.bindFilterJdbcParameters( jdbcParameterBindings ); + } return jdbcServices.getJdbcMutationExecutor().execute( jdbcUpdate, @@ -101,7 +90,34 @@ public class SimpleUpdateQueryPlan implements NonSelectQueryPlan { .getStatementPreparer() .prepareStatement( sql ), (integer, preparedStatement) -> {}, - executionContext + SqlOmittingQueryOptions.omitSqlQueryOptions( executionContext ) ); } + + private SqlAstTranslator createUpdateTranslator(ExecutionContext executionContext) { + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + final QueryEngine queryEngine = factory.getQueryEngine(); + + final SqmTranslatorFactory translatorFactory = queryEngine.getSqmTranslatorFactory(); + final SqmTranslator translator = translatorFactory.createSimpleUpdateTranslator( + sqmUpdate, + executionContext.getQueryOptions(), + domainParameterXref, + executionContext.getQueryParameterBindings(), + executionContext.getLoadQueryInfluencers(), + factory + ); + + final SqmTranslation sqmInterpretation = translator.translate(); + + tableGroupAccess = sqmInterpretation.getFromClauseAccess(); + + this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( + domainParameterXref, + sqmInterpretation::getJdbcParamsBySqmParam + ); + + return factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory() + .buildUpdateTranslator( factory, sqmInterpretation.getSqlAst() ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmInterpretationsKey.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmInterpretationsKey.java index 90e1c911e5..8a77bad1f7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmInterpretationsKey.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmInterpretationsKey.java @@ -68,11 +68,8 @@ public class SqmInterpretationsKey implements QueryInterpretationCache.Key { // cannot cache query plans if there are multi-valued param bindings // todo (6.0) : this one may be ok because of how I implemented multi-valued param handling // - the expansion is done per-execution based on the "static" SQM - return false; - } - - if ( hasLimit( query.getQueryOptions().getLimit() ) ) { - // cannot cache query plans if there is a limit defined + // - Note from Christian: The call to domainParameterXref.clearExpansions() in ConcreteSqmSelectQueryPlan is a concurrency issue when cached + // - This could be solved by using a method-local clone of domainParameterXref when multi-valued params exist return false; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java index d9df5ffb5b..0fbf081368 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java @@ -13,7 +13,7 @@ import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.query.QueryLogging; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.SqmStatement; -import org.hibernate.query.sqm.tree.cte.SqmCteConsumer; +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.NonAggregatedCompositeSimplePath; @@ -88,6 +88,8 @@ import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation; import org.hibernate.query.sqm.tree.select.SqmOrderByClause; +import org.hibernate.query.sqm.tree.select.SqmQueryGroup; +import org.hibernate.query.sqm.tree.select.SqmQueryPart; import org.hibernate.query.sqm.tree.select.SqmQuerySpec; import org.hibernate.query.sqm.tree.select.SqmSelectClause; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; @@ -294,7 +296,7 @@ public class SqmTreePrinter implements SemanticQueryWalker { "into", () -> statement.getInsertionTargetPaths().forEach( sqmPath -> sqmPath.accept( this ) ) ); - visitQuerySpec( statement.getSelectQuerySpec() ); + statement.getSelectQueryPart().accept( this ); } ); } @@ -325,7 +327,7 @@ public class SqmTreePrinter implements SemanticQueryWalker { if ( DEBUG_ENABLED ) { processStanza( "select", - () -> visitQuerySpec( statement.getQuerySpec() ) + () -> statement.getQueryPart().accept( this ) ); } @@ -342,7 +344,7 @@ public class SqmTreePrinter implements SemanticQueryWalker { } @Override - public Object visitCteConsumer(SqmCteConsumer consumer) { + public Object visitCteContainer(SqmCteContainer consumer) { return null; } @@ -395,6 +397,24 @@ public class SqmTreePrinter implements SemanticQueryWalker { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // query-spec + @Override + public Object visitQueryGroup(SqmQueryGroup queryGroup) { + processStanza( + "query-group", + () -> { + for ( SqmQueryPart queryPart : queryGroup.getQueryParts() ) { + if ( queryPart instanceof SqmQuerySpec ) { + visitQuerySpec( (SqmQuerySpec) queryPart ); + } + else { + visitQueryGroup( (SqmQueryGroup) queryPart ); + } + } + } + ); + return null; + } + @Override public Object visitQuerySpec(SqmQuerySpec querySpec) { processStanza( @@ -408,8 +428,8 @@ public class SqmTreePrinter implements SemanticQueryWalker { visitHavingClause( querySpec.getHavingClausePredicate() ); visitOrderByClause( querySpec.getOrderByClause() ); - visitLimitExpression( querySpec.getLimitExpression() ); visitOffsetExpression( querySpec.getOffsetExpression() ); + visitFetchExpression( querySpec.getFetchExpression() ); } ); @@ -857,7 +877,7 @@ public class SqmTreePrinter implements SemanticQueryWalker { } @Override - public Object visitLimitExpression(SqmExpression expression) { + public Object visitFetchExpression(SqmExpression expression) { return null; } 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 3e51078727..7570fa87f9 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 @@ -14,19 +14,24 @@ import java.util.Map; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.FilterHelper; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.persister.entity.Joinable; +import org.hibernate.query.spi.SqlOmittingQueryOptions; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmUtil; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.sql.ast.SqlAstSelectTranslator; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAstTreeHelper; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.predicate.FilterPredicate; import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.select.SelectStatement; @@ -62,6 +67,7 @@ public class MatchingIdSelectionHelper { SqmDeleteOrUpdateStatement sqmStatement, Predicate restriction, MultiTableSqmMutationConverter sqmConverter, + ExecutionContext executionContext, SessionFactoryImplementor sessionFactory) { final EntityDomainType entityDomainType = sqmStatement.getTarget().getModel(); if ( log.isTraceEnabled() ) { @@ -104,6 +110,14 @@ public class MatchingIdSelectionHelper { } ); + final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate( + executionContext.getLoadQueryInfluencers(), + (Joinable) targetEntityDescriptor.getEntityPersister(), + mutatingTableGroup + ); + if ( filterPredicate != null ) { + restriction = SqlAstTreeHelper.combinePredicates( restriction, filterPredicate ); + } idSelectionQuery.applyPredicate( restriction ); return new SelectStatement( idSelectionQuery, domainResults ); @@ -206,16 +220,14 @@ public class MatchingIdSelectionHelper { sqmMutationStatement, restriction, sqmConverter, + executionContext, factory ); - final JdbcServices jdbcServices = factory.getJdbcServices(); - final SqlAstSelectTranslator sqlAstSelectTranslator = jdbcServices.getJdbcEnvironment() + final SqlAstTranslator sqlAstSelectTranslator = jdbcServices.getJdbcEnvironment() .getSqlAstTranslatorFactory() - .buildSelectTranslator( factory ); - - final JdbcSelect idSelectJdbcOperation = sqlAstSelectTranslator.translate( matchingIdSelection ); + .buildSelectTranslator( factory, matchingIdSelection ); final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( executionContext.getQueryParameterBindings(), @@ -225,11 +237,15 @@ public class MatchingIdSelectionHelper { navigablePath -> sqmConverter.getMutatingTableGroup(), executionContext.getSession() ); + final JdbcSelect idSelectJdbcOperation = sqlAstSelectTranslator.translate( + jdbcParameterBindings, + executionContext.getQueryOptions() + ); return jdbcServices.getJdbcSelectExecutor().list( idSelectJdbcOperation, jdbcParameterBindings, - executionContext, + SqlOmittingQueryOptions.omitSqlQueryOptions( executionContext, idSelectJdbcOperation ), row -> row, true ); 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 be76992648..9e738bad66 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 @@ -22,7 +22,7 @@ import org.hibernate.query.sqm.internal.DomainParameterXref; 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.SqlAstQuerySpecProcessingStateImpl; +import org.hibernate.query.sqm.sql.internal.SqlAstQueryPartProcessingStateImpl; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; import org.hibernate.query.sqm.tree.select.SqmSelectClause; @@ -31,6 +31,7 @@ import org.hibernate.query.sqm.tree.update.SqmSetClause; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstProcessingState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; +import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.JdbcParameter; @@ -51,7 +52,7 @@ import org.hibernate.sql.ast.tree.update.Assignment; * * @author Steve Ebersole */ -public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter { +public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter { private final EntityMappingType mutatingEntityDescriptor; private final TableGroup mutatingTableGroup; @@ -65,7 +66,7 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter { LoadQueryInfluencers loadQueryInfluencers, QueryParameterBindings domainParameterBindings, SqlAstCreationContext creationContext) { - super( creationContext, queryOptions, loadQueryInfluencers, domainParameterXref, domainParameterBindings ); + super( creationContext, null, queryOptions, loadQueryInfluencers, domainParameterXref, domainParameterBindings ); this.mutatingEntityDescriptor = mutatingEntityDescriptor; final SqlAstProcessingStateImpl rootProcessingState = new SqlAstProcessingStateImpl( @@ -74,7 +75,7 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter { getCurrentClauseStack()::getCurrent ); - getProcessingStateStack().push( rootProcessingState ); + pushProcessingState( rootProcessingState ); final NavigablePath navigablePath = new NavigablePath( mutatingEntityDescriptor.getEntityName(), mutatingEntityExplicitAlias ); this.mutatingTableGroup = mutatingEntityDescriptor.createRootTableGroup( @@ -153,7 +154,7 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter { return null; } - final SqlAstProcessingState rootProcessingState = getProcessingStateStack().getCurrent(); + final SqlAstProcessingState rootProcessingState = getCurrentProcessingState(); final SqlAstProcessingStateImpl restrictionProcessingState = new SqlAstProcessingStateImpl( rootProcessingState, this, @@ -178,12 +179,12 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter { } }; - getProcessingStateStack().push( restrictionProcessingState ); + pushProcessingState( restrictionProcessingState, getFromClauseIndex() ); try { return (Predicate) sqmWhereClause.getPredicate().accept( this ); } finally { - getProcessingStateStack().pop(); + popProcessingStateStack(); this.parameterResolutionConsumer = null; } } @@ -216,12 +217,12 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter { this.parameterResolutionConsumer = parameterResolutionConsumer; - final SqlAstProcessingState rootProcessingState = getProcessingStateStack().getCurrent(); - final SqlAstProcessingStateImpl processingState = new SqlAstQuerySpecProcessingStateImpl( + final SqlAstProcessingState rootProcessingState = getCurrentProcessingState(); + final SqlAstProcessingStateImpl processingState = new SqlAstQueryPartProcessingStateImpl( sqlQuerySpec, rootProcessingState, this, - r -> new SqlSelectionForSqmSelectionCollector( + r -> new SqlSelectionForSqmSelectionResolver( r, sqmSelectClause.getSelectionItems() .size() @@ -247,7 +248,7 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter { } }; - getProcessingStateStack().push( processingState ); + pushProcessingState( processingState, getFromClauseIndex() ); try { for ( int i = 0; i < sqmSelectClause.getSelectionItems().size(); i++ ) { final DomainResultProducer domainResultProducer = (DomainResultProducer) sqmSelectClause.getSelectionItems() @@ -257,7 +258,7 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter { } } finally { - getProcessingStateStack().pop(); + popProcessingStateStack(); this.parameterResolutionConsumer = null; } } 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 eb8dea6871..1554e85b2a 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 @@ -16,8 +16,8 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.query.spi.SqlOmittingQueryOptions; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; -import org.hibernate.sql.ast.SqlAstDeleteTranslator; import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.predicate.Predicate; @@ -111,19 +111,18 @@ public class SqmMutationStrategyHelper { restrictionProducer.apply( tableReference, attributeMapping ) ); - final SqlAstDeleteTranslator sqlAstDeleteTranslator = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildDeleteTranslator( sessionFactory ); - jdbcServices.getJdbcMutationExecutor().execute( - sqlAstDeleteTranslator.translate( sqlAstDelete ), + jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildDeleteTranslator( sessionFactory, sqlAstDelete ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ), jdbcParameterBindings, sql -> executionContext.getSession() .getJdbcCoordinator() .getStatementPreparer() .prepareStatement( sql ), (integer, preparedStatement) -> {}, - executionContext + SqlOmittingQueryOptions.omitSqlQueryOptions( executionContext ) ); } } 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 1bc6799a08..474a566d7b 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 @@ -6,26 +6,69 @@ */ package org.hibernate.query.sqm.mutation.internal.cte; +import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.SqlExpressable; +import org.hibernate.metamodel.model.domain.BasicDomainType; +import org.hibernate.query.spi.SqlOmittingQueryOptions; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper; +import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler; +import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; -import org.hibernate.sql.ast.tree.cte.CteTable; +import org.hibernate.query.sqm.tree.cte.SqmCteTable; +import org.hibernate.query.sqm.tree.expression.SqmExpression; +import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.query.sqm.tree.expression.SqmStar; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.tree.cte.CteColumn; +import org.hibernate.sql.ast.tree.cte.CteContainer; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.cte.CteTableGroup; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.expression.SqlTuple; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; +import org.hibernate.sql.ast.tree.predicate.Junction; +import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelect; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.basic.BasicResult; +import org.hibernate.sql.results.internal.SqlSelectionImpl; +import org.hibernate.type.spi.TypeConfiguration; /** * Defines how identifier values are selected from the updatable/deletable tables. * - * @author Evandro Pires da Silva - * @author Vlad Mihalcea - * @author Steve Ebersole + * @author Christian Beikov */ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler { - private final CteTable cteTable; + + public static final String DML_RESULT_TABLE_NAME_PREFIX = "dml_cte_"; + public static final String CTE_TABLE_IDENTIFIER = "id"; + + private final SqmCteTable cteTable; private final DomainParameterXref domainParameterXref; private final CteStrategy strategy; public AbstractCteMutationHandler( - CteTable cteTable, + SqmCteTable cteTable, SqmDeleteOrUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, CteStrategy strategy, @@ -37,7 +80,7 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler this.strategy = strategy; } - public CteTable getCteTable() { + public SqmCteTable getCteTable() { return cteTable; } @@ -48,4 +91,167 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler public CteStrategy getStrategy() { return strategy; } + + @Override + public int execute(ExecutionContext executionContext) { + final SqmDeleteOrUpdateStatement sqmMutationStatement = getSqmDeleteOrUpdateStatement(); + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + final EntityMappingType entityDescriptor = getEntityDescriptor(); + + final MultiTableSqmMutationConverter sqmConverter = new MultiTableSqmMutationConverter( + entityDescriptor, + sqmMutationStatement.getTarget().getExplicitAlias(), + domainParameterXref, + executionContext.getQueryOptions(), + executionContext.getLoadQueryInfluencers(), + executionContext.getQueryParameterBindings(), + factory + ); + final Map> parameterResolutions; + if ( domainParameterXref.getSqmParameterCount() == 0 ) { + parameterResolutions = Collections.emptyMap(); + } + else { + parameterResolutions = new IdentityHashMap<>(); + } + + final Predicate restriction = sqmConverter.visitWhereClause( + sqmMutationStatement.getWhereClause(), + columnReference -> {}, + parameterResolutions::put + ); + + final CteStatement idSelectCte = new CteStatement( + BaseSqmToSqlAstConverter.createCteTable( getCteTable(), factory ), + MatchingIdSelectionHelper.generateMatchingIdSelectStatement( + entityDescriptor, + sqmMutationStatement, + restriction, + sqmConverter, + executionContext, + factory + ) + ); + + // Create the main query spec that will return the count of + final QuerySpec querySpec = new QuerySpec( true, 1 ); + final List domainResults = new ArrayList<>( 1 ); + final SelectStatement statement = new SelectStatement( querySpec, domainResults ); + final JdbcServices jdbcServices = factory.getJdbcServices(); + final SqlAstTranslator translator = jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildSelectTranslator( factory, statement ); + + final Expression count = createCountStart( factory, sqmConverter ); + domainResults.add( + new BasicResult<>( + 0, + null, + ( (SqlExpressable) count).getJdbcMapping().getJavaTypeDescriptor() + ) + ); + querySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( 1, 0, count ) ); + querySpec.getFromClause().addRoot( + new CteTableGroup( + new TableReference( + idSelectCte.getCteTable().getTableExpression(), + CTE_TABLE_IDENTIFIER, + false, + factory + ) + ) + ); + + // Add all CTEs + statement.addCteStatement( idSelectCte ); + addDmlCtes( statement, idSelectCte, sqmConverter, parameterResolutions, factory ); + + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + executionContext.getQueryParameterBindings(), + domainParameterXref, + SqmUtil.generateJdbcParamsXref( domainParameterXref, sqmConverter ), + factory.getDomainModel(), + navigablePath -> sqmConverter.getMutatingTableGroup(), + executionContext.getSession() + ); + final JdbcSelect select = translator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + List list = jdbcServices.getJdbcSelectExecutor().list( + select, + jdbcParameterBindings, + SqlOmittingQueryOptions.omitSqlQueryOptions( executionContext, select ), + row -> row[0], + false + ); + return ( (Number) list.get( 0 ) ).intValue(); + } + + private Expression createCountStart( + SessionFactoryImplementor factory, + MultiTableSqmMutationConverter sqmConverter) { + final SqmExpression arg = new SqmStar( factory.getNodeBuilder() ); + final TypeConfiguration typeConfiguration = factory.getJpaMetamodel().getTypeConfiguration(); + final BasicDomainType type = typeConfiguration.standardBasicTypeForJavaType( Long.class ); + return factory.getQueryEngine().getSqmFunctionRegistry().findFunctionDescriptor("count").generateSqmExpression( + arg, + type, + factory.getQueryEngine(), + typeConfiguration + ).convertToSqlAst( sqmConverter ); + } + + protected Predicate createIdSubQueryPredicate( + List lhsExpressions, + CteStatement idSelectCte, + SessionFactoryImplementor factory) { + final TableReference idSelectTableReference = new TableReference( + idSelectCte.getCteTable().getTableExpression(), + CTE_TABLE_IDENTIFIER, + false, + factory + ); + final Junction predicate = new Junction( Junction.Nature.CONJUNCTION ); + final List cteColumns = idSelectCte.getCteTable().getCteColumns(); + final int size = cteColumns.size(); + final QuerySpec subQuery = new QuerySpec( false, 1 ); + subQuery.getFromClause().addRoot( new CteTableGroup( idSelectTableReference ) ); + final SelectClause subQuerySelectClause = subQuery.getSelectClause(); + for ( int i = 0; i < size; i++ ) { + final CteColumn cteColumn = cteColumns.get( i ); + subQuerySelectClause.addSqlSelection( + new SqlSelectionImpl( + i + 1, + i, + new ColumnReference( + idSelectTableReference, + cteColumn.getColumnExpression(), + cteColumn.getJdbcMapping(), + factory + ) + ) + ); + } + final Expression lhs; + if ( lhsExpressions.size() == 1 ) { + lhs = lhsExpressions.get( 0 ); + } + else { + lhs = new SqlTuple( lhsExpressions, null ); + } + predicate.add( + new InSubQueryPredicate( + lhs, + subQuery, + false + ) + ); + return predicate; + } + + protected abstract void addDmlCtes( + CteContainer statement, + CteStatement idSelectCte, + MultiTableSqmMutationConverter sqmConverter, + Map> parameterResolutions, + SessionFactoryImplementor factory); + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteDeleteHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteDeleteHandler.java index 37e3e18121..f1632a3f2f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteDeleteHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteDeleteHandler.java @@ -6,91 +6,56 @@ */ package org.hibernate.query.sqm.mutation.internal.cte; -import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; -import java.util.function.Supplier; +import java.util.Map; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; -import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.mapping.SelectionConsumer; -import org.hibernate.metamodel.mapping.MappingModelExpressable; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.DeleteHandler; -import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper; +import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; +import org.hibernate.query.sqm.tree.cte.SqmCteTable; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor; -import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.sql.ast.spi.SqlAliasBase; +import org.hibernate.sql.ast.spi.SqlAliasBaseManager; +import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.ast.tree.cte.CteContainer; import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.SqlTuple; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.from.TableGroupProducer; import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcDelete; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * Bulk-id delete handler that uses CTE and VALUES lists. * - * @author Evandro Pires da Silva - * @author Vlad Mihalcea - * @author Steve Ebersole + * @author Christian Beikov */ @SuppressWarnings("WeakerAccess") public class CteDeleteHandler extends AbstractCteMutationHandler implements DeleteHandler { - private final SqlAstTranslatorFactory sqlAstTranslatorFactory; protected CteDeleteHandler( - CteTable cteTable, + SqmCteTable cteTable, SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, CteStrategy strategy, SessionFactoryImplementor sessionFactory) { super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, sessionFactory ); - - final JdbcServices jdbcServices = getSessionFactory().getJdbcServices(); - final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); - sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); } @Override - public SqmDeleteStatement getSqmDeleteOrUpdateStatement() { - return (SqmDeleteStatement) super.getSqmDeleteOrUpdateStatement(); - } - - @Override - public int execute(ExecutionContext executionContext) { - final List ids = MatchingIdSelectionHelper.selectMatchingIds( - getSqmDeleteOrUpdateStatement(), - getDomainParameterXref(), - executionContext - ); - - if ( ids == null || ids.isEmpty() ) { - return 0; - } - - final QuerySpec cteQuerySpec = getCteTable().createCteSubQuery( executionContext ); - - final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( getDomainParameterXref().getQueryParameterCount() ); - final QuerySpec cteDefinitionQuerySpec = getCteTable().createCteDefinition( - ids, - getEntityDescriptor().getIdentifierMapping(), - jdbcParameterBindings, - executionContext - ); - - // for every table to be deleted, create the CteStatement and execute it - + protected void addDmlCtes( + CteContainer statement, + CteStatement idSelectCte, + MultiTableSqmMutationConverter sqmConverter, + Map> parameterResolutions, + SessionFactoryImplementor factory) { + final TableGroup updatingTableGroup = sqmConverter.getMutatingTableGroup(); getEntityDescriptor().visitAttributeMappings( attribute -> { if ( attribute instanceof PluralAttributeMapping ) { @@ -104,149 +69,63 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele // // in all of these cases, we should clean up the matching rows in the // collection table - - executeDelete( - cteDefinitionQuerySpec, - pluralAttribute.getSeparateCollectionTable(), - () -> columnConsumer -> pluralAttribute.getKeyDescriptor().visitReferringColumns( columnConsumer ), - pluralAttribute.getKeyDescriptor(), - cteQuerySpec, - jdbcParameterBindings, - executionContext + final String tableExpression = pluralAttribute.getSeparateCollectionTable(); + final CteTable dmlResultCte = new CteTable( + DML_RESULT_TABLE_NAME_PREFIX + tableExpression, + idSelectCte.getCteTable().getCteColumns(), + factory ); + final TableReference dmlTableReference = new TableReference( tableExpression, null, true, factory ); + final List columnReferences = new ArrayList<>( idSelectCte.getCteTable().getCteColumns().size() ); + pluralAttribute.getKeyDescriptor().visitReferringColumns( + (selectionIndex, selectionMapping) -> { + columnReferences.add( + new ColumnReference( + dmlTableReference, + selectionMapping, + factory + ) + ); + } + ); + final MutationStatement dmlStatement = new DeleteStatement( + dmlTableReference, + createIdSubQueryPredicate( columnReferences, idSelectCte, factory ), + columnReferences + ); + statement.addCteStatement( new CteStatement( dmlResultCte, dmlStatement ) ); } } } ); getEntityDescriptor().visitConstraintOrderedTables( - (tableExpression, tableColumnsVisitationSupplier) -> executeDelete( - cteDefinitionQuerySpec, - tableExpression, - tableColumnsVisitationSupplier, - getEntityDescriptor().getIdentifierMapping(), - cteQuerySpec, - jdbcParameterBindings, - executionContext - ) - ); - - return ids.size(); - } - - protected void executeDelete( - QuerySpec cteDefinition, - String targetTable, - Supplier> columnsToMatchVisitationSupplier, - MappingModelExpressable cteType, - QuerySpec cteSubQuery, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - final CteStatement cteStatement = generateCteStatement( - cteDefinition, - targetTable, - columnsToMatchVisitationSupplier, - cteType, - cteSubQuery, - executionContext - ); - - final SessionFactoryImplementor sessionFactory = getSessionFactory(); - - final JdbcDelete jdbcDelete = sqlAstTranslatorFactory.buildDeleteTranslator( sessionFactory ) - .translate( cteStatement ); - - - final LogicalConnectionImplementor logicalConnection = executionContext.getSession() - .getJdbcCoordinator() - .getLogicalConnection(); - - sessionFactory.getJdbcServices().getJdbcMutationExecutor().execute( - jdbcDelete, - jdbcParameterBindings, - sql -> { - try { - return logicalConnection.getPhysicalConnection().prepareStatement( sql ); - } - catch (SQLException e) { - throw sessionFactory.getJdbcServices().getSqlExceptionHelper().convert( - e, - "Error performing DELETE", - sql - ); - } - }, - (integer, preparedStatement) -> {}, - executionContext - ); - } - - protected CteStatement generateCteStatement( - QuerySpec cteDefinition, - String targetTable, - Supplier> columnsToMatchVisitationSupplier, - MappingModelExpressable cteType, - QuerySpec cteSubQuery, - ExecutionContext executionContext) { - final DeleteStatement deleteStatement = generateCteConsumer( - targetTable, - columnsToMatchVisitationSupplier, - cteType, - cteSubQuery, - executionContext - ); - return new CteStatement( - cteDefinition, - CteStrategy.TABLE_NAME, - getCteTable(), - deleteStatement - ); - } - - - private DeleteStatement generateCteConsumer( - String targetTable, - Supplier> columnsToMatchVisitationSupplier, - MappingModelExpressable cteType, - QuerySpec cteSubQuery, - ExecutionContext executionContext) { - final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); - final TableReference targetTableReference = new TableReference( - targetTable, - null, - false, - sessionFactory - ); - - final List columnsToMatchReferences = new ArrayList<>(); - - columnsToMatchVisitationSupplier.get().accept( - (columnIndex, selection) -> - columnsToMatchReferences.add( - new ColumnReference( - targetTableReference, - selection, - sessionFactory - ) - ) - ); - - final Expression columnsToMatchExpression; - - if ( columnsToMatchReferences.size() == 1 ) { - columnsToMatchExpression = columnsToMatchReferences.get( 0 ); - } - else { - columnsToMatchExpression = new SqlTuple( columnsToMatchReferences, cteType ); - } - - return new DeleteStatement( - targetTableReference, - new InSubQueryPredicate( - columnsToMatchExpression, - cteSubQuery, - false - ) + (tableExpression, tableColumnsVisitationSupplier) -> { + final CteTable dmlResultCte = new CteTable( + DML_RESULT_TABLE_NAME_PREFIX + tableExpression, + idSelectCte.getCteTable().getCteColumns(), + factory + ); + final TableReference dmlTableReference = updatingTableGroup.resolveTableReference( tableExpression ); + final List columnReferences = new ArrayList<>( idSelectCte.getCteTable().getCteColumns().size() ); + tableColumnsVisitationSupplier.get().accept( + (selectionIndex, selectionMapping) -> { + columnReferences.add( + new ColumnReference( + dmlTableReference, + selectionMapping, + factory + ) + ); + } + ); + final MutationStatement dmlStatement = new DeleteStatement( + dmlTableReference, + createIdSubQueryPredicate( columnReferences, idSelectCte, factory ), + columnReferences + ); + statement.addCteStatement( new CteStatement( dmlResultCte, dmlStatement ) ); + } ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteStrategy.java index af95992f35..a30e9b0908 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteStrategy.java @@ -11,11 +11,13 @@ import java.util.Locale; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; +import org.hibernate.query.sqm.tree.cte.SqmCteTable; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.sql.ast.tree.cte.CteTable; @@ -24,34 +26,30 @@ import org.hibernate.sql.exec.spi.ExecutionContext; /** * @asciidoc * - * {@link SqmMultiTableMutationStrategy} implementation using SQL's CTE (Common Table Expression) + * {@link SqmMultiTableMutationStrategy} implementation using SQL's modifiable CTE (Common Table Expression) * approach to perform the update/delete. E.g. (using delete): * * ```` * with cte_id (id) as ( * select * id - * from ( - * values - * (?), - * (?), - * (?) - * (?) - * ) + * from Person + * where condition + * ), delete_1 as ( + * delete + * from + * Person + * where + * (id) in ( + * select id + * from cte_id + * ) + * returning id * ) - * delete - * from - * Person - * where - * ( id ) in ( - * select id - * from cte_id - * ) + * select count(*) from cte_id * ```` * - * @author Evandro Pires da Silva - * @author Vlad Mihalcea - * @author Steve Ebersole + * @author Christian Beikov */ public class CteStrategy implements SqmMultiTableMutationStrategy { public static final String SHORT_NAME = "cte"; @@ -59,7 +57,13 @@ public class CteStrategy implements SqmMultiTableMutationStrategy { private final EntityPersister rootDescriptor; private final SessionFactoryImplementor sessionFactory; - private final CteTable cteTable; + private final SqmCteTable cteTable; + + public CteStrategy( + EntityMappingType rootEntityType, + RuntimeModelCreationContext runtimeModelCreationContext) { + this( rootEntityType.getEntityPersister(), runtimeModelCreationContext ); + } public CteStrategy( EntityPersister rootDescriptor, @@ -93,7 +97,7 @@ public class CteStrategy implements SqmMultiTableMutationStrategy { ); } - this.cteTable = new CteTable( rootDescriptor ); + this.cteTable = new SqmCteTable( TABLE_NAME, rootDescriptor ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java index cc6bbfb11b..6fb0944a21 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java @@ -6,27 +6,44 @@ */ package org.hibernate.query.sqm.mutation.internal.cte; -import org.hibernate.NotYetImplementedFor6Exception; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.Joinable; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.UpdateHandler; +import org.hibernate.query.sqm.tree.cte.SqmCteTable; +import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.query.sqm.tree.update.SqmSetClause; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.ast.tree.cte.CteContainer; +import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.from.TableReferenceJoin; +import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.sql.ast.tree.update.UpdateStatement; /** * - * @author Evandro Pires da Silva - * @author Vlad Mihalcea - * @author Steve Ebersole + * @author Christian Beikov */ -public class CteUpdateHandler - extends AbstractCteMutationHandler - implements UpdateHandler { +public class CteUpdateHandler extends AbstractCteMutationHandler implements UpdateHandler { @SuppressWarnings("WeakerAccess") public CteUpdateHandler( - CteTable cteTable, + SqmCteTable cteTable, SqmUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, CteStrategy strategy, @@ -35,7 +52,136 @@ public class CteUpdateHandler } @Override - public int execute(ExecutionContext executionContext) { - throw new NotYetImplementedFor6Exception(); + protected void addDmlCtes( + CteContainer statement, + CteStatement idSelectCte, + MultiTableSqmMutationConverter sqmConverter, + Map> parameterResolutions, + SessionFactoryImplementor factory) { + final TableGroup updatingTableGroup = sqmConverter.getMutatingTableGroup(); + final SqmUpdateStatement updateStatement = (SqmUpdateStatement) getSqmDeleteOrUpdateStatement(); + final EntityMappingType entityDescriptor = getEntityDescriptor(); + + final EntityPersister entityPersister = entityDescriptor.getEntityPersister(); + final String rootEntityName = entityPersister.getRootEntityName(); + final EntityPersister rootEntityDescriptor = factory.getDomainModel().getEntityDescriptor( rootEntityName ); + + final String hierarchyRootTableName = ( (Joinable) rootEntityDescriptor ).getTableName(); + final TableReference hierarchyRootTableReference = updatingTableGroup.resolveTableReference( hierarchyRootTableName ); + assert hierarchyRootTableReference != null; + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // visit the set-clause using our special converter, collecting + // information about the assignments + final SqmSetClause setClause = updateStatement.getSetClause(); + final List assignments = new ArrayList<>( setClause.getAssignments().size() ); + + sqmConverter.visitSetClause( + setClause, + assignments::add, + parameterResolutions::put + ); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // cross-reference the TableReference by alias. The TableGroup already + // cross-references it by name, but the ColumnReference only has the alias + + final Map tableReferenceByAlias = CollectionHelper.mapOfSize( updatingTableGroup.getTableReferenceJoins().size() + 1 ); + collectTableReference( updatingTableGroup.getPrimaryTableReference(), tableReferenceByAlias::put ); + for ( int i = 0; i < updatingTableGroup.getTableReferenceJoins().size(); i++ ) { + collectTableReference( updatingTableGroup.getTableReferenceJoins().get( i ), tableReferenceByAlias::put ); + } + + final Map> assignmentsByTable = CollectionHelper.mapOfSize( + updatingTableGroup.getTableReferenceJoins().size() + 1 + ); + + for ( int i = 0; i < assignments.size(); i++ ) { + final Assignment assignment = assignments.get( i ); + final List assignmentColumnRefs = assignment.getAssignable().getColumnReferences(); + + String assignmentTableReference = null; + + for ( int c = 0; c < assignmentColumnRefs.size(); c++ ) { + final ColumnReference columnReference = assignmentColumnRefs.get( c ); + final String tableReference = resolveTableReference( + columnReference, + tableReferenceByAlias + ); + + // TODO: this could be fixed by introducing joins to DML statements + if ( assignmentTableReference != null && !assignmentTableReference.equals( tableReference ) ) { + throw new IllegalStateException( "Assignment referred to columns from multiple tables" ); + } + + assignmentTableReference = tableReference; + } + assert assignmentTableReference != null; + + List assignmentsForTable = assignmentsByTable.get( assignmentTableReference ); + if ( assignmentsForTable == null ) { + assignmentsForTable = new ArrayList<>(); + assignmentsByTable.put( assignmentTableReference, assignmentsForTable ); + } + assignmentsForTable.add( assignment ); + } + + getEntityDescriptor().visitConstraintOrderedTables( + (tableExpression, tableColumnsVisitationSupplier) -> { + final CteTable dmlResultCte = new CteTable( + DML_RESULT_TABLE_NAME_PREFIX + tableExpression, + idSelectCte.getCteTable().getCteColumns(), + factory + ); + final List assignmentList = assignmentsByTable.get( tableExpression ); + if ( assignmentList == null ) { + return; + } + final TableReference dmlTableReference = updatingTableGroup.resolveTableReference( tableExpression ); + final List columnReferences = new ArrayList<>( idSelectCte.getCteTable().getCteColumns().size() ); + tableColumnsVisitationSupplier.get().accept( + (selectionIndex, selectionMapping) -> { + columnReferences.add( + new ColumnReference( + dmlTableReference, + selectionMapping, + factory + ) + ); + } + ); + final MutationStatement dmlStatement = new UpdateStatement( + dmlTableReference, + assignmentList, + createIdSubQueryPredicate( columnReferences, idSelectCte, factory ), + columnReferences + ); + statement.addCteStatement( new CteStatement( dmlResultCte, dmlStatement ) ); + } + ); + } + + private void collectTableReference( + TableReference tableReference, + BiConsumer consumer) { + consumer.accept( tableReference.getIdentificationVariable(), tableReference ); + } + + private void collectTableReference( + TableReferenceJoin tableReferenceJoin, + BiConsumer consumer) { + collectTableReference( tableReferenceJoin.getJoinedTableReference(), consumer ); + } + + private String resolveTableReference( + ColumnReference columnReference, + Map tableReferenceByAlias) { + final String qualifier = columnReference.getQualifier(); + final TableReference tableReferenceByQualifier = tableReferenceByAlias.get( qualifier ); + if ( tableReferenceByQualifier != null ) { + return tableReferenceByQualifier.getTableExpression(); + } + + return qualifier; } } 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 699a35dce3..41a3f64469 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 @@ -19,8 +19,8 @@ import org.hibernate.engine.transaction.spi.IsolationDelegate; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.query.ComparisonOperator; import org.hibernate.query.NavigablePath; +import org.hibernate.query.spi.SqlOmittingQueryOptions; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.sql.ast.SqlAstInsertTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.tree.expression.ColumnReference; @@ -61,10 +61,8 @@ public final class ExecuteWithIdTableHelper { assert mutatingTableGroup.getModelPart() instanceof EntityMappingType; final EntityMappingType mutatingEntityDescriptor = (EntityMappingType) mutatingTableGroup.getModelPart(); - final InsertStatement idTableInsert = new InsertStatement(); - final TableReference idTableReference = new TableReference( idTable.getTableExpression(), null, false, factory ); - idTableInsert.setTargetTable( idTableReference ); + final InsertStatement idTableInsert = new InsertStatement( idTableReference ); for ( int i = 0; i < idTable.getIdTableColumns().size(); i++ ) { final IdTableColumn column = idTable.getIdTableColumns().get( i ); @@ -126,9 +124,8 @@ public final class ExecuteWithIdTableHelper { final JdbcServices jdbcServices = factory.getJdbcServices(); final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final SqlAstInsertTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildInsertTranslator( factory ); - final JdbcInsert jdbcInsert = sqlAstTranslator.translate( idTableInsert ); - jdbcInsert.bindFilterJdbcParameters( jdbcParameterBindings ); + final JdbcInsert jdbcInsert = sqlAstTranslatorFactory.buildInsertTranslator( factory, idTableInsert ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); return jdbcServices.getJdbcMutationExecutor().execute( jdbcInsert, @@ -138,7 +135,7 @@ public final class ExecuteWithIdTableHelper { .getStatementPreparer() .prepareStatement( sql ), (integer, preparedStatement) -> {}, - executionContext + SqlOmittingQueryOptions.omitSqlQueryOptions( executionContext ) ); } 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 b6ec0ae84a..30b430c1ff 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 @@ -31,13 +31,13 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBindings; +import org.hibernate.query.spi.SqlOmittingQueryOptions; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmUtil; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.sql.ast.SqlAstDeleteTranslator; import org.hibernate.sql.ast.spi.SqlAstTreeHelper; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.tree.delete.DeleteStatement; @@ -151,7 +151,8 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate( executionContext.getLoadQueryInfluencers(), - (Joinable) entityDescriptor + (Joinable) entityDescriptor, + deletingTableGroup ); if ( filterPredicate != null ) { needsIdTableWrapper.set( true ); @@ -333,11 +334,10 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle final JdbcServices jdbcServices = factory.getJdbcServices(); - final SqlAstDeleteTranslator sqlAstTranslator = jdbcServices.getJdbcEnvironment() + final JdbcDelete jdbcDelete = jdbcServices.getJdbcEnvironment() .getSqlAstTranslatorFactory() - .buildDeleteTranslator( factory ); - final JdbcDelete jdbcDelete = sqlAstTranslator.translate( sqlAst ); - jdbcDelete.bindFilterJdbcParameters( jdbcParameterBindings ); + .buildDeleteTranslator( factory, sqlAst ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); return jdbcServices.getJdbcMutationExecutor().execute( jdbcDelete, @@ -347,7 +347,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle .getStatementPreparer() .prepareStatement( sql ), (integer, preparedStatement) -> {}, - executionContext + SqlOmittingQueryOptions.omitSqlQueryOptions( 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 2f6fec09e4..d914d619d7 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 @@ -20,7 +20,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.FilterHelper; import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.internal.util.collections.Stack; import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; @@ -28,11 +27,9 @@ import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.UpdateHandler; import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler; -import org.hibernate.query.sqm.sql.internal.SqlAstProcessingStateImpl; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.sql.ast.spi.SqlAstProcessingState; import org.hibernate.sql.ast.spi.SqlAstTreeHelper; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.from.TableGroup; @@ -132,33 +129,11 @@ public class TableBasedUpdateHandler sessionFactory ); - final Stack converterProcessingStateStack = converterDelegate.getProcessingStateStack(); - - final SqlAstProcessingStateImpl rootProcessingState = new SqlAstProcessingStateImpl( - null, - converterDelegate, - converterDelegate.getCurrentClauseStack()::getCurrent - ); - - converterProcessingStateStack.push( rootProcessingState ); - final TableGroup updatingTableGroup = converterDelegate.getMutatingTableGroup(); final TableReference hierarchyRootTableReference = updatingTableGroup.resolveTableReference( hierarchyRootTableName ); assert hierarchyRootTableReference != null; - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // cross-reference the TableReference by alias. The TableGroup already - // cross-references it by name, bu the ColumnReference only has the alias - - final Map tableReferenceByAlias = CollectionHelper.mapOfSize( updatingTableGroup.getTableReferenceJoins().size() + 1 ); - collectTableReference( updatingTableGroup.getPrimaryTableReference(), tableReferenceByAlias::put ); - for ( int i = 0; i < updatingTableGroup.getTableReferenceJoins().size(); i++ ) { - collectTableReference( updatingTableGroup.getTableReferenceJoins().get( i ), tableReferenceByAlias::put ); - } - - final Map> parameterResolutions; if ( domainParameterXref.getSqmParameterCount() == 0 ) { parameterResolutions = Collections.emptyMap(); @@ -199,12 +174,23 @@ public class TableBasedUpdateHandler final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate( executionContext.getLoadQueryInfluencers(), - (Joinable) rootEntityDescriptor + (Joinable) rootEntityDescriptor, + updatingTableGroup ); if ( filterPredicate != null ) { predicate = SqlAstTreeHelper.combinePredicates( predicate, filterPredicate ); } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // cross-reference the TableReference by alias. The TableGroup already + // cross-references it by name, bu the ColumnReference only has the alias + + final Map tableReferenceByAlias = CollectionHelper.mapOfSize( updatingTableGroup.getTableReferenceJoins().size() + 1 ); + collectTableReference( updatingTableGroup.getPrimaryTableReference(), tableReferenceByAlias::put ); + for ( int i = 0; i < updatingTableGroup.getTableReferenceJoins().size(); i++ ) { + collectTableReference( updatingTableGroup.getTableReferenceJoins().get( i ), tableReferenceByAlias::put ); + } + return new UpdateExecutionDelegate( getSqmUpdate(), converterDelegate, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UnrestrictedDeleteExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UnrestrictedDeleteExecutionDelegate.java index 905c411652..70952201a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UnrestrictedDeleteExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UnrestrictedDeleteExecutionDelegate.java @@ -15,8 +15,8 @@ import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.entity.JoinedSubclassEntityPersister; +import org.hibernate.query.spi.SqlOmittingQueryOptions; import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; -import org.hibernate.sql.ast.SqlAstDeleteTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.from.TableReference; @@ -82,18 +82,10 @@ public class UnrestrictedDeleteExecutionDelegate implements TableBasedDeleteHand final JdbcServices jdbcServices = factory.getJdbcServices(); final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); + final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( 1 ); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final SqlAstDeleteTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildDeleteTranslator( factory ); - final JdbcDelete jdbcDelete = sqlAstTranslator.translate( deleteStatement ); - - final JdbcParameterBindings jdbcParameterBindings; - if ( CollectionHelper.isNotEmpty( jdbcDelete.getFilterJdbcParameters() ) ) { - jdbcParameterBindings = new JdbcParameterBindingsImpl( 1 ); - jdbcDelete.bindFilterJdbcParameters( jdbcParameterBindings ); - } - else { - jdbcParameterBindings = JdbcParameterBindings.NO_BINDINGS; - } + final JdbcDelete jdbcDelete = sqlAstTranslatorFactory.buildDeleteTranslator( factory, deleteStatement ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); return jdbcServices.getJdbcMutationExecutor().execute( jdbcDelete, @@ -103,7 +95,7 @@ public class UnrestrictedDeleteExecutionDelegate implements TableBasedDeleteHand .getStatementPreparer() .prepareStatement( sql ), (integer, preparedStatement) -> {}, - executionContext + SqlOmittingQueryOptions.omitSqlQueryOptions( executionContext ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UpdateExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UpdateExecutionDelegate.java index a32583ab4a..e26f2d4b66 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UpdateExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UpdateExecutionDelegate.java @@ -21,12 +21,12 @@ import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.mapping.SelectionConsumer; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPartContainer; +import org.hibernate.query.spi.SqlOmittingQueryOptions; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmUtil; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.sql.ast.SqlAstUpdateTranslator; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; @@ -259,12 +259,10 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio final UpdateStatement sqlAst = new UpdateStatement( updatingTableReference, assignments, idTableSubQueryPredicate ); final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); - final SqlAstUpdateTranslator sqlAstTranslator = jdbcServices.getJdbcEnvironment() + final JdbcUpdate jdbcUpdate = jdbcServices.getJdbcEnvironment() .getSqlAstTranslatorFactory() - .buildUpdateTranslator( sessionFactory ); - - final JdbcUpdate jdbcUpdate = sqlAstTranslator.translate( sqlAst ); - jdbcUpdate.bindFilterJdbcParameters( jdbcParameterBindings ); + .buildUpdateTranslator( sessionFactory, sqlAst ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); jdbcServices.getJdbcMutationExecutor().execute( jdbcUpdate, @@ -274,7 +272,7 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio .getStatementPreparer() .prepareStatement( sql ), (integer, preparedStatement) -> {}, - executionContext + SqlOmittingQueryOptions.omitSqlQueryOptions( executionContext ) ); } } 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 e2e15ae81f..869cd6c26e 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 @@ -12,21 +12,17 @@ import java.util.function.Consumer; import java.util.function.Supplier; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.FilterHelper; import org.hibernate.metamodel.mapping.SelectionConsumer; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.PluralAttributeMapping; -import org.hibernate.persister.entity.Joinable; +import org.hibernate.query.spi.SqlOmittingQueryOptions; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.DeleteHandler; import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.sql.ast.SqlAstDeleteTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.ast.spi.SqlAstTreeHelper; import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.predicate.FilterPredicate; import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.ExecutionContext; @@ -160,23 +156,17 @@ public class InlineDeleteHandler implements DeleteHandler { executionContext ); - Predicate restriction = matchingIdsPredicate; - final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate( executionContext.getLoadQueryInfluencers(), (Joinable) entityDescriptor ); - if ( filterPredicate != null ) { - restriction = SqlAstTreeHelper.combinePredicates( restriction, filterPredicate ); - } - final DeleteStatement deleteStatement = new DeleteStatement( targetTableReference, restriction ); + final DeleteStatement deleteStatement = new DeleteStatement( targetTableReference, matchingIdsPredicate ); - final SqlAstDeleteTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildDeleteTranslator( sessionFactory ); - final JdbcDelete jdbcOperation = sqlAstTranslator.translate( deleteStatement ); - jdbcOperation.bindFilterJdbcParameters( jdbcParameterBindings ); + final JdbcDelete jdbcOperation = sqlAstTranslatorFactory.buildDeleteTranslator( sessionFactory, deleteStatement ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); jdbcMutationExecutor.execute( jdbcOperation, jdbcParameterBindings, this::prepareQueryStatement, (integer, preparedStatement) -> {}, - executionContext + SqlOmittingQueryOptions.omitSqlQueryOptions( executionContext ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java index f7ca2c076e..d132abe1e7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java @@ -10,7 +10,8 @@ import java.util.List; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.query.sqm.SemanticQueryWalker; -import org.hibernate.query.sqm.tree.cte.SqmCteConsumer; +import org.hibernate.query.sqm.tree.SqmStatement; +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.NonAggregatedCompositeSimplePath; @@ -85,6 +86,8 @@ import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation; import org.hibernate.query.sqm.tree.select.SqmOrderByClause; +import org.hibernate.query.sqm.tree.select.SqmQueryGroup; +import org.hibernate.query.sqm.tree.select.SqmQueryPart; import org.hibernate.query.sqm.tree.select.SqmQuerySpec; import org.hibernate.query.sqm.tree.select.SqmSelectClause; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; @@ -95,6 +98,7 @@ import org.hibernate.query.sqm.tree.update.SqmAssignment; import org.hibernate.query.sqm.tree.update.SqmSetClause; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.service.ServiceRegistry; +import org.hibernate.sql.ast.tree.Statement; /** * Base support for an SQM walker @@ -112,9 +116,13 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker sqmStatement) { + return sqmStatement.accept( this ); + } + @Override public Object visitSelectStatement(SqmSelectStatement statement) { - visitQuerySpec( statement.getQuerySpec() ); + visitQueryPart( statement.getQueryPart() ); return statement; } @@ -147,7 +155,7 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker stateField : statement.getInsertionTargetPaths() ) { stateField.accept( this ); } - visitQuerySpec( statement.getSelectQuerySpec() ); + statement.getSelectQueryPart().accept( this ); return statement; } @@ -171,30 +179,30 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker sqmCteStatement) { + visitStatement( sqmCteStatement.getCteDefinition() ); return sqmCteStatement; } @Override - public Object visitCteConsumer(SqmCteConsumer consumer) { - if ( consumer instanceof SqmQuerySpec ) { - return visitQuerySpec( ( (SqmQuerySpec) consumer ) ); + public Object visitCteContainer(SqmCteContainer consumer) { + for ( SqmCteStatement cteStatement : consumer.getCteStatements() ) { + cteStatement.accept( this ); } - if ( consumer instanceof SqmDeleteStatement ) { - return visitDeleteStatement( ( (SqmDeleteStatement) consumer ) ); - } + return consumer; + } - if ( consumer instanceof SqmUpdateStatement ) { - visitUpdateStatement( (SqmUpdateStatement) consumer ); - } + public Object visitQueryPart(SqmQueryPart queryPart) { + return queryPart.accept( this ); + } - if ( consumer instanceof SqmInsertSelectStatement ) { - visitInsertSelectStatement( (SqmInsertSelectStatement) consumer ); + @Override + public Object visitQueryGroup(SqmQueryGroup queryGroup) { + for ( SqmQueryPart queryPart : queryGroup.getQueryParts() ) { + visitQueryPart( queryPart ); } - - throw new UnsupportedOperationException( "Unsupported SqmCteConsumer : " + consumer ); + return queryGroup; } @Override @@ -204,7 +212,7 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker expression) { + public Object visitFetchExpression(SqmExpression expression) { if ( expression == null ) { return null; } 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 b449666fcf..71b1d5edb6 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 @@ -7,44 +7,64 @@ package org.hibernate.query.sqm.sql; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import javax.persistence.TemporalType; -import org.hibernate.AssertionFailure; +import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.dialect.function.TimestampaddFunction; import org.hibernate.dialect.function.TimestampdiffFunction; +import org.hibernate.engine.FetchTiming; +import org.hibernate.engine.profile.FetchProfile; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.graph.spi.AppliedGraph; +import org.hibernate.internal.FilterHelper; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; +import org.hibernate.loader.MultipleBagFetchException; +import org.hibernate.metamodel.CollectionClassification; +import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.metamodel.mapping.Association; import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.MappingModelExpressable; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.internal.EmbeddedCollectionPart; import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; +import org.hibernate.metamodel.mapping.ordering.OrderByFragment; import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType; import org.hibernate.metamodel.model.domain.AllowableParameterType; 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.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.Joinable; import org.hibernate.query.BinaryArithmeticOperator; import org.hibernate.query.ComparisonOperator; +import org.hibernate.query.DynamicInstantiationNature; import org.hibernate.query.NavigablePath; import org.hibernate.query.QueryLogging; import org.hibernate.query.SemanticException; @@ -61,20 +81,22 @@ import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmMappingModelHelper; import org.hibernate.query.sqm.spi.BaseSemanticQueryWalker; -import org.hibernate.query.sqm.spi.JdbcParameterBySqmParameterAccess; import org.hibernate.query.sqm.sql.internal.BasicValuedPathInterpretation; +import org.hibernate.query.sqm.sql.internal.DomainResultProducer; import org.hibernate.query.sqm.sql.internal.EmbeddableValuedPathInterpretation; import org.hibernate.query.sqm.sql.internal.EntityValuedPathInterpretation; import org.hibernate.query.sqm.sql.internal.NonAggregatedCompositeValuedPathInterpretation; import org.hibernate.query.sqm.sql.internal.PluralValuedSimplePathInterpretation; import org.hibernate.query.sqm.sql.internal.SqlAstProcessingStateImpl; -import org.hibernate.query.sqm.sql.internal.SqlAstQuerySpecProcessingStateImpl; +import org.hibernate.query.sqm.sql.internal.SqlAstQueryPartProcessingStateImpl; import org.hibernate.query.sqm.sql.internal.SqmParameterInterpretation; import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation; -import org.hibernate.query.sqm.tree.cte.SqmCteConsumer; +import org.hibernate.query.sqm.tree.SqmStatement; +import org.hibernate.query.sqm.tree.cte.SqmCteContainer; import org.hibernate.query.sqm.tree.cte.SqmCteStatement; import org.hibernate.query.sqm.tree.cte.SqmCteTable; import org.hibernate.query.sqm.tree.cte.SqmCteTableColumn; +import org.hibernate.query.sqm.tree.cte.SqmSearchClauseSpecification; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.domain.NonAggregatedCompositeSimplePath; import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath; @@ -103,9 +125,11 @@ import org.hibernate.query.sqm.tree.expression.SqmFormat; import org.hibernate.query.sqm.tree.expression.SqmFunction; import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; import org.hibernate.query.sqm.tree.expression.SqmLiteral; +import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType; import org.hibernate.query.sqm.tree.expression.SqmLiteralNull; import org.hibernate.query.sqm.tree.expression.SqmNamedParameter; import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.query.sqm.tree.expression.SqmPathEntityType; import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter; import org.hibernate.query.sqm.tree.expression.SqmStar; import org.hibernate.query.sqm.tree.expression.SqmSummarization; @@ -122,6 +146,7 @@ import org.hibernate.query.sqm.tree.from.SqmJoin; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement; +import org.hibernate.query.sqm.tree.insert.SqmValues; import org.hibernate.query.sqm.tree.predicate.SqmAndPredicate; import org.hibernate.query.sqm.tree.predicate.SqmBetweenPredicate; import org.hibernate.query.sqm.tree.predicate.SqmBooleanExpressionPredicate; @@ -138,39 +163,52 @@ import org.hibernate.query.sqm.tree.predicate.SqmNullnessPredicate; import org.hibernate.query.sqm.tree.predicate.SqmOrPredicate; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; +import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation; +import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiationArgument; +import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiationTarget; import org.hibernate.query.sqm.tree.select.SqmOrderByClause; +import org.hibernate.query.sqm.tree.select.SqmQueryGroup; +import org.hibernate.query.sqm.tree.select.SqmQueryPart; import org.hibernate.query.sqm.tree.select.SqmQuerySpec; import org.hibernate.query.sqm.tree.select.SqmSelectClause; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.query.sqm.tree.select.SqmSelection; import org.hibernate.query.sqm.tree.select.SqmSortSpecification; import org.hibernate.query.sqm.tree.select.SqmSubQuery; +import org.hibernate.query.sqm.tree.update.SqmAssignment; +import org.hibernate.query.sqm.tree.update.SqmSetClause; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstJoinType; +import org.hibernate.sql.ast.SqlTreeCreationLogger; import org.hibernate.sql.ast.spi.FromClauseAccess; +import org.hibernate.sql.ast.spi.SqlAliasBase; import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAliasBaseManager; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlAstProcessingState; -import org.hibernate.sql.ast.spi.SqlAstQuerySpecProcessingState; +import org.hibernate.sql.ast.spi.SqlAstQueryPartProcessingState; import org.hibernate.sql.ast.spi.SqlAstTreeHelper; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.ast.tree.cte.CteColumn; -import org.hibernate.sql.ast.tree.cte.CteConsumer; import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; +import org.hibernate.sql.ast.tree.cte.SearchClauseSpecification; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.expression.Any; import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; import org.hibernate.sql.ast.tree.expression.CastTarget; import org.hibernate.sql.ast.tree.expression.Collate; +import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Distinct; import org.hibernate.sql.ast.tree.expression.Duration; import org.hibernate.sql.ast.tree.expression.DurationUnit; +import org.hibernate.sql.ast.tree.expression.EntityTypeLiteral; import org.hibernate.sql.ast.tree.expression.Every; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.ExtractUnit; @@ -185,12 +223,16 @@ import org.hibernate.sql.ast.tree.expression.Star; import org.hibernate.sql.ast.tree.expression.Summarization; import org.hibernate.sql.ast.tree.expression.TrimSpecification; import org.hibernate.sql.ast.tree.expression.UnaryOperation; +import org.hibernate.sql.ast.tree.from.CorrelatedTableGroup; 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.insert.InsertStatement; +import org.hibernate.sql.ast.tree.insert.Values; import org.hibernate.sql.ast.tree.predicate.BetweenPredicate; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; import org.hibernate.sql.ast.tree.predicate.ExistsPredicate; +import org.hibernate.sql.ast.tree.predicate.FilterPredicate; import org.hibernate.sql.ast.tree.predicate.GroupedPredicate; import org.hibernate.sql.ast.tree.predicate.InListPredicate; import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; @@ -200,17 +242,28 @@ import org.hibernate.sql.ast.tree.predicate.NegatedPredicate; import org.hibernate.sql.ast.tree.predicate.NullnessPredicate; import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.ast.tree.predicate.SelfRenderingPredicate; +import org.hibernate.sql.ast.tree.select.QueryGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.select.SelectClause; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.ast.tree.select.SortSpecification; +import org.hibernate.sql.ast.tree.update.Assignable; +import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.sql.ast.tree.update.UpdateStatement; import org.hibernate.sql.exec.internal.JdbcParameterImpl; import org.hibernate.sql.exec.internal.JdbcParametersImpl; import org.hibernate.sql.exec.spi.JdbcParameters; +import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.EntityGraphTraversalState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; +import org.hibernate.sql.results.graph.instantiation.internal.DynamicInstantiation; import org.hibernate.sql.results.internal.SqlSelectionImpl; +import org.hibernate.sql.results.internal.StandardEntityGraphTraversalStateImpl; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.spi.TypeConfiguration; @@ -229,9 +282,8 @@ import static org.hibernate.type.spi.TypeConfiguration.isDuration; /** * @author Steve Ebersole */ -public abstract class BaseSqmToSqlAstConverter - extends BaseSemanticQueryWalker - implements SqmToSqlAstConverter, JdbcParameterBySqmParameterAccess, FromClauseAccess, DomainResultCreationState { +public abstract class BaseSqmToSqlAstConverter extends BaseSemanticQueryWalker + implements SqmTranslator, DomainResultCreationState { private static final Logger log = Logger.getLogger( BaseSqmToSqlAstConverter.class ); @@ -243,22 +295,29 @@ public abstract class BaseSqmToSqlAstConverter } private final SqlAstCreationContext creationContext; - private final SessionFactoryImplementor sessionFactory; + private final SqmStatement statement; private final QueryOptions queryOptions; private final LoadQueryInfluencers loadQueryInfluencers; private final DomainParameterXref domainParameterXref; private final QueryParameterBindings domainParameterBindings; - private final Map,Supplier>> jpaCriteriaParamResolutions; + private final Map, Supplier>> jpaCriteriaParamResolutions; + private final List domainResults; + private final EntityGraphTraversalState entityGraphTraversalState; - private Map joinPathBySqmJoinFullPath = new HashMap<>(); + private int fetchDepth; + + private Map collectionFilterPredicates; + private OrderByFragmentConsumer orderByFragmentConsumer; + + private Map joinPathBySqmJoinFullPath = new HashMap<>(); private final SqlAliasBaseManager sqlAliasBaseManager = new SqlAliasBaseManager(); - - private final FromClauseIndex fromClauseIndex = new FromClauseIndex(); - private final Stack processingStateStack = new StandardStack<>(); + private final Stack fromClauseIndexStack = new StandardStack<>(); + private SqlAstProcessingState lastPoppedProcessingState; + private FromClauseIndex lastPoppedFromClauseIndex; private final Stack currentClauseStack = new StandardStack<>(); private final Stack shallownessStack = new StandardStack<>( Shallowness.NONE ); @@ -271,6 +330,7 @@ public abstract class BaseSqmToSqlAstConverter public BaseSqmToSqlAstConverter( SqlAstCreationContext creationContext, + SqmStatement statement, QueryOptions queryOptions, LoadQueryInfluencers loadQueryInfluencers, DomainParameterXref domainParameterXref, @@ -278,25 +338,82 @@ public abstract class BaseSqmToSqlAstConverter super( creationContext.getServiceRegistry() ); this.creationContext = creationContext; - this.sessionFactory = creationContext.getSessionFactory(); + this.statement = statement; + + if ( statement instanceof SqmSelectStatement ) { + this.domainResults = new ArrayList<>( + ( (SqmSelectStatement) statement ).getQuerySpec() + .getSelectClause() + .getSelectionItems() + .size() + ); + + final AppliedGraph appliedGraph = queryOptions.getAppliedGraph(); + if ( appliedGraph != null && appliedGraph.getSemantic() != null && appliedGraph.getGraph() != null ) { + this.entityGraphTraversalState = new StandardEntityGraphTraversalStateImpl( + appliedGraph.getSemantic(), appliedGraph.getGraph() ); + } + else { + this.entityGraphTraversalState = null; + } + } + else if ( statement instanceof SqmInsertSelectStatement ) { + this.domainResults = new ArrayList<>( + ( (SqmInsertSelectStatement) statement ).getSelectQueryPart() + .getFirstQuerySpec() + .getSelectClause() + .getSelectionItems() + .size() + ); + this.entityGraphTraversalState = null; + } + else { + this.domainResults = null; + this.entityGraphTraversalState = null; + } this.queryOptions = queryOptions; this.loadQueryInfluencers = loadQueryInfluencers; this.domainParameterXref = domainParameterXref; this.domainParameterBindings = domainParameterBindings; - this.jpaCriteriaParamResolutions = domainParameterXref.getParameterResolutions().getJpaCriteriaParamResolutions(); + this.jpaCriteriaParamResolutions = domainParameterXref.getParameterResolutions() + .getJpaCriteriaParamResolutions(); } protected Stack getProcessingStateStack() { return processingStateStack; } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + protected void pushProcessingState(SqlAstProcessingState processingState) { + pushProcessingState( processingState, new FromClauseIndex( getFromClauseIndex() ) ); + } + + protected void pushProcessingState(SqlAstProcessingState processingState, FromClauseIndex fromClauseIndex) { + fromClauseIndexStack.push( fromClauseIndex ); + processingStateStack.push( processingState ); + } + + protected void popProcessingStateStack() { + lastPoppedFromClauseIndex = fromClauseIndexStack.pop(); + lastPoppedProcessingState = processingStateStack.pop(); + } + + protected SqmStatement getStatement() { + return statement; + } + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // FromClauseAccess @Override public TableGroup findTableGroup(NavigablePath navigablePath) { - return fromClauseIndex.findTableGroup( navigablePath ); + return getFromClauseAccess().findTableGroup( navigablePath ); + } + + @Override + public ModelPart resolveModelPart(NavigablePath navigablePath) { + // again, assume that the path refers to a TableGroup + return getFromClauseAccess().findTableGroup( navigablePath ).getModelPart(); } @Override @@ -323,11 +440,6 @@ public abstract class BaseSqmToSqlAstConverter return getCurrentProcessingState().getSqlExpressionResolver(); } - @Override - public FromClauseAccess getFromClauseAccess() { - return fromClauseIndex; - } - @Override public SqlAliasBaseGenerator getSqlAliasBaseGenerator() { return sqlAliasBaseManager; @@ -335,7 +447,10 @@ public abstract class BaseSqmToSqlAstConverter @Override public LockMode determineLockMode(String identificationVariable) { - return queryOptions.getLockOptions().getEffectiveLockMode( identificationVariable ); + final LockOptions lockOptions = getQueryOptions().getLockOptions(); + return lockOptions.getScope() || identificationVariable == null + ? lockOptions.getLockMode() + : lockOptions.getEffectiveLockMode( identificationVariable ); } public QueryOptions getQueryOptions() { @@ -347,93 +462,718 @@ public abstract class BaseSqmToSqlAstConverter } public FromClauseIndex getFromClauseIndex() { - return fromClauseIndex; + return (FromClauseIndex) getFromClauseAccess(); } + @Override + public FromClauseAccess getFromClauseAccess() { + final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent(); + if ( fromClauseIndex == null ) { + return lastPoppedFromClauseIndex; + } + else { + return fromClauseIndex; + } + } + + @Override public Stack getCurrentClauseStack() { return currentClauseStack; } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Statements @Override - public Object visitUpdateStatement(SqmUpdateStatement statement) { - throw new AssertionFailure( "UpdateStatement not supported" ); + public SqmTranslation translate() { + final SqmStatement sqmStatement = getStatement(); + final T statement = (T) sqmStatement.accept( this ); + return new StandardSqmTranslation<>( + statement, + getJdbcParamsBySqmParam(), + lastPoppedProcessingState.getSqlExpressionResolver(), + getFromClauseAccess() + ); } @Override - public Object visitDeleteStatement(SqmDeleteStatement statement) { - throw new AssertionFailure( "DeleteStatement not supported" ); + public Statement visitStatement(SqmStatement sqmStatement) { + return (Statement) sqmStatement.accept( this ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Update statement + + @Override + public UpdateStatement visitUpdateStatement(SqmUpdateStatement sqmStatement) { + Map cteStatements = this.visitCteContainer( sqmStatement ); + + final String entityName = sqmStatement.getTarget().getEntityName(); + final EntityPersister entityDescriptor = getCreationContext().getDomainModel() + .getEntityDescriptor( entityName ); + assert entityDescriptor != null; + + pushProcessingState( + new SqlAstProcessingStateImpl( + getCurrentProcessingState(), + this, + getCurrentClauseStack()::getCurrent + ) + ); + + try { + final NavigablePath rootPath = sqmStatement.getTarget().getNavigablePath(); + final TableGroup rootTableGroup = entityDescriptor.createRootTableGroup( + rootPath, + sqmStatement.getRoot().getAlias(), + false, + LockMode.WRITE, + getSqlAliasBaseGenerator(), + getSqlExpressionResolver(), + () -> predicate -> additionalRestrictions = predicate, + getCreationContext() + ); + + if ( !rootTableGroup.getTableReferenceJoins().isEmpty() ) { + throw new HibernateException( "Not expecting multiple table references for an SQM DELETE" ); + } + + getFromClauseAccess().registerTableGroup( rootPath, rootTableGroup ); + + final List assignments = visitSetClause( sqmStatement.getSetClause() ); + + final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate( + getLoadQueryInfluencers(), + (Joinable) entityDescriptor + ); + if ( filterPredicate != null ) { + additionalRestrictions = SqlAstTreeHelper.combinePredicates( additionalRestrictions, filterPredicate ); + } + + Predicate suppliedPredicate = null; + final SqmWhereClause whereClause = sqmStatement.getWhereClause(); + if ( whereClause != null && whereClause.getPredicate() != null ) { + getCurrentClauseStack().push( Clause.WHERE ); + try { + suppliedPredicate = (Predicate) whereClause.getPredicate().accept( this ); + } + finally { + getCurrentClauseStack().pop(); + } + } + + return new UpdateStatement( + sqmStatement.isWithRecursive(), cteStatements, + rootTableGroup.getPrimaryTableReference(), + assignments, + SqlAstTreeHelper.combinePredicates( suppliedPredicate, additionalRestrictions ), + Collections.emptyList() + ); + } + finally { + popProcessingStateStack(); + } } @Override - public Object visitInsertSelectStatement(SqmInsertSelectStatement statement) { - throw new AssertionFailure( "InsertStatement not supported" ); + public List visitSetClause(SqmSetClause setClause) { + final List assignments = new ArrayList<>(); + + for ( SqmAssignment sqmAssignment : setClause.getAssignments() ) { + final List targetColumnReferences = new ArrayList<>(); + + pushProcessingState( + new SqlAstProcessingStateImpl( + getCurrentProcessingState(), + this, + getCurrentClauseStack()::getCurrent + ) { + @Override + public Expression resolveSqlExpression( + String key, + Function creator) { + final Expression expression = getParentState().getSqlExpressionResolver() + .resolveSqlExpression( key, creator ); + assert expression instanceof ColumnReference; + + targetColumnReferences.add( (ColumnReference) expression ); + + return expression; + } + }, + getFromClauseIndex() + ); + + final SqmPathInterpretation assignedPathInterpretation; + try { + assignedPathInterpretation = (SqmPathInterpretation) sqmAssignment.getTargetPath().accept( this ); + } + finally { + popProcessingStateStack(); + } + + inferableTypeAccessStack.push( assignedPathInterpretation::getExpressionType ); + + final List valueColumnReferences = new ArrayList<>(); + pushProcessingState( + new SqlAstProcessingStateImpl( + getCurrentProcessingState(), + this, + getCurrentClauseStack()::getCurrent + ) { + @Override + public Expression resolveSqlExpression( + String key, + Function creator) { + final Expression expression = getParentState().getSqlExpressionResolver() + .resolveSqlExpression( key, creator ); + assert expression instanceof ColumnReference; + + valueColumnReferences.add( (ColumnReference) expression ); + + return expression; + } + }, + getFromClauseIndex() + ); + + try { + + if ( sqmAssignment.getValue() instanceof SqmParameter ) { + final SqmParameter sqmParameter = (SqmParameter) sqmAssignment.getValue(); + final List jdbcParametersForSqm = new ArrayList<>(); + + // create one JdbcParameter for each column in the assigned path + assignedPathInterpretation.getExpressionType().forEachSelection( + (columnIndex, selection) -> { + final JdbcParameter jdbcParameter = new JdbcParameterImpl( selection.getJdbcMapping() ); + jdbcParametersForSqm.add( jdbcParameter ); + assignments.add( + new Assignment( + new ColumnReference( + // we do not want a qualifier (table alias) here + (String) null, + selection, + getCreationContext().getSessionFactory() + ), + jdbcParameter + ) + ); + } + ); + + getJdbcParamsBySqmParam().put( sqmParameter, jdbcParametersForSqm ); + } + else { + final MappingMetamodel domainModel = getCreationContext().getDomainModel(); + final Expression valueExpression = (Expression) sqmAssignment.getValue().accept( this ); + + final int valueExprJdbcCount = valueExpression.getExpressionType().getJdbcTypeCount(); + final int assignedPathJdbcCount = assignedPathInterpretation.getExpressionType().getJdbcTypeCount(); + + if ( valueExprJdbcCount != assignedPathJdbcCount ) { + SqlTreeCreationLogger.LOGGER.debugf( + "JDBC type count does not match in UPDATE assignment between the assigned-path and the assigned-value; " + + "this will likely lead to problems executing the query" + ); + } + + assert assignedPathJdbcCount == valueExprJdbcCount; + + for ( ColumnReference columnReference : targetColumnReferences ) { + assignments.add( + new Assignment( columnReference, valueExpression ) + ); + } + } + } + finally { + popProcessingStateStack(); + inferableTypeAccessStack.pop(); + } + + } + + return assignments; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Delete statement + + @Override + public DeleteStatement visitDeleteStatement(SqmDeleteStatement statement) { + Map cteStatements = this.visitCteContainer( statement ); + + final String entityName = statement.getTarget().getEntityName(); + final EntityPersister entityDescriptor = getCreationContext().getDomainModel() + .getEntityDescriptor( entityName ); + assert entityDescriptor != null; + + pushProcessingState( + new SqlAstProcessingStateImpl( + getCurrentProcessingState(), + this, + getCurrentClauseStack()::getCurrent + ) + ); + + try { + final NavigablePath rootPath = statement.getTarget().getNavigablePath(); + final TableGroup rootTableGroup = entityDescriptor.createRootTableGroup( + rootPath, + statement.getRoot().getAlias(), + false, + LockMode.WRITE, + stem -> getSqlAliasBaseGenerator().createSqlAliasBase( stem ), + getSqlExpressionResolver(), + () -> predicate -> additionalRestrictions = predicate, + getCreationContext() + ); + getFromClauseAccess().registerTableGroup( rootPath, rootTableGroup ); + + if ( !rootTableGroup.getTableReferenceJoins().isEmpty() ) { + throw new HibernateException( "Not expecting multiple table references for an SQM DELETE" ); + } + + final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate( + getLoadQueryInfluencers(), + (Joinable) entityDescriptor + ); + if ( filterPredicate != null ) { + additionalRestrictions = SqlAstTreeHelper.combinePredicates( additionalRestrictions, filterPredicate ); + } + + Predicate suppliedPredicate = null; + final SqmWhereClause whereClause = statement.getWhereClause(); + if ( whereClause != null && whereClause.getPredicate() != null ) { + getCurrentClauseStack().push( Clause.WHERE ); + try { + suppliedPredicate = (Predicate) whereClause.getPredicate().accept( this ); + } + finally { + getCurrentClauseStack().pop(); + } + } + + return new DeleteStatement( + statement.isWithRecursive(), + cteStatements, + rootTableGroup.getPrimaryTableReference(), + SqlAstTreeHelper.combinePredicates( suppliedPredicate, additionalRestrictions ), + Collections.emptyList() + ); + } + finally { + popProcessingStateStack(); + } + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Insert-select statement + + @Override + public InsertStatement visitInsertSelectStatement(SqmInsertSelectStatement sqmStatement) { + Map cteStatements = this.visitCteContainer( sqmStatement ); + + final String entityName = sqmStatement.getTarget().getEntityName(); + final EntityPersister entityDescriptor = getCreationContext().getDomainModel() + .getEntityDescriptor( entityName ); + assert entityDescriptor != null; + + SqmQueryPart selectQueryPart = sqmStatement.getSelectQueryPart(); + pushProcessingState( + new SqlAstProcessingStateImpl( + null, + this, + r -> new SqlSelectionForSqmSelectionResolver( + r, + selectQueryPart.getFirstQuerySpec() + .getSelectClause() + .getSelectionItems() + .size() + ), + getCurrentClauseStack()::getCurrent + ) + ); + + try { + final NavigablePath rootPath = sqmStatement.getTarget().getNavigablePath(); + final TableGroup rootTableGroup = entityDescriptor.createRootTableGroup( + rootPath, + sqmStatement.getTarget().getExplicitAlias(), + false, + LockMode.WRITE, + stem -> getSqlAliasBaseGenerator().createSqlAliasBase( stem ), + getSqlExpressionResolver(), + () -> predicate -> additionalRestrictions = predicate, + getCreationContext() + ); + + if ( !rootTableGroup.getTableReferenceJoins().isEmpty() + || !rootTableGroup.getTableGroupJoins().isEmpty() ) { + throw new HibernateException( "Not expecting multiple table references for an SQM INSERT-SELECT" ); + } + + getFromClauseAccess().registerTableGroup( rootPath, rootTableGroup ); + + final InsertStatement insertStatement = new InsertStatement( + sqmStatement.isWithRecursive(), + cteStatements, + rootTableGroup.getPrimaryTableReference(), + Collections.emptyList() + ); + + List targetPaths = sqmStatement.getInsertionTargetPaths(); + for ( SqmPath target : targetPaths ) { + Assignable assignable = (Assignable) target.accept( this ); + insertStatement.addTargetColumnReferences( assignable.getColumnReferences() ); + } + + insertStatement.setSourceSelectStatement( + visitQueryPart( selectQueryPart ) + ); + + return insertStatement; + } + finally { + popProcessingStateStack(); + } + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Insert-values statement + + @Override + public InsertStatement visitInsertValuesStatement(SqmInsertValuesStatement sqmStatement) { + Map cteStatements = this.visitCteContainer( sqmStatement ); + final String entityName = sqmStatement.getTarget().getEntityName(); + final EntityPersister entityDescriptor = getCreationContext().getDomainModel() + .getEntityDescriptor( entityName ); + assert entityDescriptor != null; + + pushProcessingState( + new SqlAstProcessingStateImpl( + null, + this, + getCurrentClauseStack()::getCurrent + ) + ); + + try { + final NavigablePath rootPath = sqmStatement.getTarget().getNavigablePath(); + final TableGroup rootTableGroup = entityDescriptor.createRootTableGroup( + rootPath, + sqmStatement.getTarget().getExplicitAlias(), + false, + LockMode.WRITE, + stem -> getSqlAliasBaseGenerator().createSqlAliasBase( stem ), + getSqlExpressionResolver(), + () -> predicate -> additionalRestrictions = predicate, + getCreationContext() + ); + + if ( !rootTableGroup.getTableReferenceJoins().isEmpty() + || !rootTableGroup.getTableGroupJoins().isEmpty() ) { + throw new HibernateException( "Not expecting multiple table references for an SQM INSERT-SELECT" ); + } + + getFromClauseAccess().registerTableGroup( rootPath, rootTableGroup ); + + final InsertStatement insertValuesStatement = new InsertStatement( + sqmStatement.isWithRecursive(), + cteStatements, + rootTableGroup.getPrimaryTableReference(), + Collections.emptyList() + ); + + List targetPaths = sqmStatement.getInsertionTargetPaths(); + for ( SqmPath target : targetPaths ) { + Assignable assignable = (Assignable) target.accept( this ); + insertValuesStatement.addTargetColumnReferences( assignable.getColumnReferences() ); + } + + List valuesList = sqmStatement.getValuesList(); + for ( SqmValues sqmValues : valuesList ) { + insertValuesStatement.getValuesList().add( visitValues( sqmValues ) ); + } + + return insertValuesStatement; + } + finally { + popProcessingStateStack(); + } } @Override - public Object visitInsertValuesStatement(SqmInsertValuesStatement statement) { - throw new AssertionFailure( "InsertStatement not supported" ); + public Values visitValues(SqmValues sqmValues) { + Values values = new Values(); + //noinspection rawtypes + for ( SqmExpression expression : sqmValues.getExpressions() ) { + values.getExpressions().add( (Expression) expression.accept( this ) ); + } + return values; } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Select statement + @Override public SelectStatement visitSelectStatement(SqmSelectStatement statement) { - throw new AssertionFailure( "SelectStatement not supported" ); + Map cteStatements = this.visitCteContainer( statement ); + final QueryPart queryPart = visitQueryPart( statement.getQueryPart() ); + return new SelectStatement( statement.isWithRecursive(), cteStatements, queryPart, domainResults ); } @Override - public CteStatement visitCteStatement(SqmCteStatement sqmCteStatement) { - final CteTable cteTable = createCteTable( sqmCteStatement ); + public DynamicInstantiation visitDynamicInstantiation(SqmDynamicInstantiation sqmDynamicInstantiation) { + final SqmDynamicInstantiationTarget instantiationTarget = sqmDynamicInstantiation.getInstantiationTarget(); + final DynamicInstantiationNature instantiationNature = instantiationTarget.getNature(); + final JavaTypeDescriptor targetTypeDescriptor = interpretInstantiationTarget( instantiationTarget ); - return new CteStatement( - visitQuerySpec( sqmCteStatement.getCteDefinition() ), - sqmCteStatement.getCteLabel(), - cteTable, - visitCteConsumer( sqmCteStatement.getCteConsumer() ) + final DynamicInstantiation dynamicInstantiation = new DynamicInstantiation<>( + instantiationNature, + targetTypeDescriptor ); - } - protected CteTable createCteTable(SqmCteStatement sqmCteStatement) { - final SqmCteTable sqmCteTable = sqmCteStatement.getCteTable(); - final List sqmCteColumns = sqmCteTable.getColumns(); - final List sqlCteColumns = new ArrayList<>( sqmCteColumns.size() ); - - for ( final SqmCteTableColumn sqmCteTableColumn : sqmCteColumns ) { - sqlCteColumns.add( - new CteColumn( - sqmCteTableColumn.getColumnName(), - sqmCteTableColumn.getType() - ) + for ( SqmDynamicInstantiationArgument sqmArgument : sqmDynamicInstantiation.getArguments() ) { + final DomainResultProducer argumentResultProducer = (DomainResultProducer) sqmArgument.getSelectableNode() + .accept( this ); + dynamicInstantiation.addArgument( + sqmArgument.getAlias(), + argumentResultProducer ); } - return new CteTable( - sqlCteColumns, + dynamicInstantiation.complete(); + + return dynamicInstantiation; + } + + @SuppressWarnings("unchecked") + private JavaTypeDescriptor interpretInstantiationTarget(SqmDynamicInstantiationTarget instantiationTarget) { + final Class targetJavaType; + + if ( instantiationTarget.getNature() == DynamicInstantiationNature.LIST ) { + targetJavaType = (Class) List.class; + } + else if ( instantiationTarget.getNature() == DynamicInstantiationNature.MAP ) { + targetJavaType = (Class) Map.class; + } + else { + targetJavaType = instantiationTarget.getJavaType(); + } + + return getCreationContext().getDomainModel() + .getTypeConfiguration() + .getJavaTypeDescriptorRegistry() + .getDescriptor( targetJavaType ); + } + + @Override + public CteStatement visitCteStatement(SqmCteStatement sqmCteStatement) { + final CteTable cteTable = createCteTable( + sqmCteStatement.getCteTable(), getCreationContext().getSessionFactory() ); + + return new CteStatement( + cteTable, + visitStatement( sqmCteStatement.getCteDefinition() ), + sqmCteStatement.getSearchClauseKind(), + visitSearchBySpecifications( cteTable, sqmCteStatement.getSearchBySpecifications() ), + visitCycleColumns( cteTable, sqmCteStatement.getCycleColumns() ), + findCteColumn( cteTable, sqmCteStatement.getCycleMarkColumn() ), + sqmCteStatement.getCycleValue(), + sqmCteStatement.getNoCycleValue() + ); + } + + protected List visitSearchBySpecifications( + CteTable cteTable, + List searchBySpecifications) { + if ( searchBySpecifications == null || searchBySpecifications.isEmpty() ) { + return null; + } + final int size = searchBySpecifications.size(); + final List searchClauseSpecifications = new ArrayList<>( size ); + for ( int i = 0; i < size; i++ ) { + final SqmSearchClauseSpecification specification = searchBySpecifications.get( i ); + forEachCteColumn( + cteTable, + specification.getCteColumn(), + cteColumn -> { + searchClauseSpecifications.add( + new SearchClauseSpecification( + cteColumn, + specification.getSortOrder(), + specification.getNullPrecedence() + ) + ); + } + ); + } + + return searchClauseSpecifications; + } + + protected CteColumn findCteColumn(CteTable cteTable, SqmCteTableColumn cteColumn) { + if ( cteColumn == null ) { + return null; + } + final List cteColumns = cteTable.getCteColumns(); + final int size = cteColumns.size(); + for ( int i = 0; i < size; i++ ) { + final CteColumn column = cteColumns.get( i ); + if ( cteColumn.getColumnName().equals( column.getColumnExpression() ) ) { + return column; + } + } + throw new IllegalArgumentException( + String.format( + "Couldn't find cte column %s in cte %s!", + cteColumn.getColumnName(), + cteTable.getTableExpression() + ) + ); + } + + protected void forEachCteColumn(CteTable cteTable, SqmCteTableColumn cteColumn, Consumer consumer) { + final List cteColumns = cteTable.getCteColumns(); + final int size = cteColumns.size(); + for ( int i = 0; i < size; i++ ) { + final CteColumn column = cteColumns.get( i ); + if ( cteColumn.getColumnName().equals( column.getColumnExpression() ) ) { + consumer.accept( column ); + } + } + } + + protected List visitCycleColumns(CteTable cteTable, List cycleColumns) { + if ( cycleColumns == null || cycleColumns.isEmpty() ) { + return null; + } + final int size = cycleColumns.size(); + final List columns = new ArrayList<>( size ); + for ( int i = 0; i < size; i++ ) { + forEachCteColumn( + cteTable, + cycleColumns.get( i ), + columns::add + ); + } + return columns; + } + + public static CteTable createCteTable(SqmCteTable sqmCteTable, SessionFactoryImplementor factory) { + final List sqmCteColumns = sqmCteTable.getColumns(); + final List sqlCteColumns = new ArrayList<>( sqmCteColumns.size() ); + + for ( int i = 0; i < sqmCteColumns.size(); i++ ) { + final SqmCteTableColumn sqmCteTableColumn = sqmCteColumns.get( i ); + ModelPart modelPart = sqmCteTableColumn.getType(); + if ( modelPart instanceof Association ) { + modelPart = ( (Association) modelPart ).getForeignKeyDescriptor(); + } + if ( modelPart instanceof EmbeddableValuedModelPart ) { + modelPart.forEachJdbcType( + (index, jdbcMapping) -> { + sqlCteColumns.add( + new CteColumn( + sqmCteTableColumn.getColumnName() + "_" + index, + jdbcMapping + ) + ); + } + ); + } + else { + sqlCteColumns.add( + new CteColumn( + sqmCteTableColumn.getColumnName(), + ( (BasicValuedMapping) modelPart ).getJdbcMapping() + ) + ); + } + } + + return new CteTable( + sqmCteTable.getCteName(), + sqlCteColumns, + factory + ); } @Override - public CteConsumer visitCteConsumer(SqmCteConsumer consumer) { - return (CteConsumer) super.visitCteConsumer( consumer ); + public Map visitCteContainer(SqmCteContainer consumer) { + final Collection> sqmCteStatements = consumer.getCteStatements(); + final Map cteStatements = new LinkedHashMap<>( sqmCteStatements.size() ); + for ( SqmCteStatement sqmCteStatement : sqmCteStatements ) { + final CteStatement cteStatement = visitCteStatement( sqmCteStatement ); + cteStatements.put( cteStatement.getCteTable().getTableExpression(), cteStatement ); + } + return cteStatements; + } + + @Override + public QueryPart visitQueryPart(SqmQueryPart queryPart) { + return (QueryPart) super.visitQueryPart( queryPart ); + } + + @Override + public QueryGroup visitQueryGroup(SqmQueryGroup queryGroup) { + final List> queryParts = queryGroup.getQueryParts(); + final int size = queryParts.size(); + final List newQueryParts = new ArrayList<>( size ); + final QueryGroup group = new QueryGroup( + getProcessingStateStack().isEmpty(), + queryGroup.getSetOperator(), + newQueryParts + ); + final SqlAstQueryPartProcessingStateImpl processingState = new SqlAstQueryPartProcessingStateImpl( + group, + getCurrentProcessingState(), + this, + DelegatingSqlSelectionForSqmSelectionCollector::new, + currentClauseStack::getCurrent + ); + final DelegatingSqlSelectionForSqmSelectionCollector collector = (DelegatingSqlSelectionForSqmSelectionCollector) processingState + .getSqlExpressionResolver(); + pushProcessingState( processingState ); + + try { + newQueryParts.add( visitQueryPart( queryParts.get( 0 ) ) ); + collector.setSqlSelectionForSqmSelectionCollector( + (SqlSelectionForSqmSelectionCollector) lastPoppedProcessingState.getSqlExpressionResolver() + ); + for ( int i = 1; i < size; i++ ) { + newQueryParts.add( visitQueryPart( queryParts.get( i ) ) ); + } + visitOrderByOffsetAndFetch( queryGroup, group ); + return group; + } + finally { + popProcessingStateStack(); + } } @Override public QuerySpec visitQuerySpec(SqmQuerySpec sqmQuerySpec) { - final QuerySpec sqlQuerySpec = new QuerySpec( processingStateStack.isEmpty(), sqmQuerySpec.getFromClause().getNumberOfRoots() ); + final QuerySpec sqlQuerySpec = new QuerySpec( + getProcessingStateStack().isEmpty(), + sqmQuerySpec.getFromClause().getNumberOfRoots() + ); final SqmSelectClause selectClause = sqmQuerySpec.getSelectClause(); + Predicate originalAdditionalRestrictions = additionalRestrictions; additionalRestrictions = null; - processingStateStack.push( - new SqlAstQuerySpecProcessingStateImpl( + pushProcessingState( + new SqlAstQueryPartProcessingStateImpl( sqlQuerySpec, - processingStateStack.getCurrent(), + getCurrentProcessingState(), this, - r -> new SqlSelectionForSqmSelectionCollector( + r -> new SqlSelectionForSqmSelectionResolver( r, selectClause.getSelectionItems() .size() @@ -443,7 +1183,10 @@ public abstract class BaseSqmToSqlAstConverter ); try { - prepareQuerySpec( sqlQuerySpec ); + final boolean topLevel = sqlQuerySpec.isRoot(); + if ( topLevel ) { + orderByFragmentConsumer = new StandardOrderByFragmentConsumer(); + } // we want to visit the from-clause first visitFromClause( sqmQuerySpec.getFromClause() ); @@ -461,44 +1204,108 @@ public abstract class BaseSqmToSqlAstConverter } } - if ( additionalRestrictions != null ) { - sqlQuerySpec.applyPredicate( additionalRestrictions ); - } - sqlQuerySpec.setGroupByClauseExpressions( visitGroupByClause( sqmQuerySpec.getGroupByClauseExpressions() ) ); if ( sqmQuerySpec.getHavingClausePredicate() != null ) { sqlQuerySpec.setHavingClauseRestrictions( visitHavingClause( sqmQuerySpec.getHavingClausePredicate() ) ); } - if ( sqmQuerySpec.getOrderByClause() != null ) { - currentClauseStack.push( Clause.ORDER ); - try { - for ( SqmSortSpecification sortSpecification : sqmQuerySpec.getOrderByClause().getSortSpecifications() ) { - sqlQuerySpec.addSortSpecification( visitSortSpecification( sortSpecification ) ); - } - } - finally { - currentClauseStack.pop(); - } + visitOrderByOffsetAndFetch( sqmQuerySpec, sqlQuerySpec ); + + if ( topLevel && statement instanceof SqmSelectStatement ) { + orderByFragmentConsumer.visitFragments( + (orderByFragment, tableGroup) -> { + orderByFragment.apply( sqlQuerySpec, tableGroup, this ); + } + ); + orderByFragmentConsumer = null; + applyCollectionFilterPredicates( sqlQuerySpec ); } - sqlQuerySpec.setLimitClauseExpression( visitLimitExpression( sqmQuerySpec.getLimitExpression() ) ); - sqlQuerySpec.setOffsetClauseExpression( visitOffsetExpression( sqmQuerySpec.getOffsetExpression() ) ); - - postProcessQuerySpec( sqlQuerySpec ); - joinPathBySqmJoinFullPath.clear(); return sqlQuerySpec; } finally { - processingStateStack.pop(); + if ( additionalRestrictions != null ) { + sqlQuerySpec.applyPredicate( additionalRestrictions ); + } + additionalRestrictions = originalAdditionalRestrictions; + popProcessingStateStack(); } } - protected void prepareQuerySpec(QuerySpec sqlQuerySpec) { + protected void visitOrderByOffsetAndFetch(SqmQueryPart sqmQueryPart, QueryPart sqlQueryPart) { + if ( sqmQueryPart.getOrderByClause() != null ) { + currentClauseStack.push( Clause.ORDER ); + try { + for ( SqmSortSpecification sortSpecification : sqmQueryPart.getOrderByClause() + .getSortSpecifications() ) { + sqlQueryPart.addSortSpecification( visitSortSpecification( sortSpecification ) ); + } + } + finally { + currentClauseStack.pop(); + } + } + + sqlQueryPart.setOffsetClauseExpression( visitOffsetExpression( sqmQueryPart.getOffsetExpression() ) ); + sqlQueryPart.setFetchClauseExpression( + visitFetchExpression( sqmQueryPart.getFetchExpression() ), + sqmQueryPart.getFetchClauseType() + ); } - protected void postProcessQuerySpec(QuerySpec sqlQuerySpec) { + 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 ) { + final TableGroup root = roots.get( 0 ); + final ModelPartContainer modelPartContainer = root.getModelPart(); + final EntityPersister entityPersister = modelPartContainer.findContainingEntityMapping().getEntityPersister(); + assert entityPersister instanceof Joinable; + final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate( + getLoadQueryInfluencers(), (Joinable) entityPersister, root + ); + if ( filterPredicate != null ) { + sqlQuerySpec.applyPredicate( filterPredicate ); + } + if ( CollectionHelper.isNotEmpty( collectionFilterPredicates ) ) { + root.getTableGroupJoins().forEach( + tableGroupJoin -> { + collectionFilterPredicates.forEach( (alias, predicate) -> { + if ( tableGroupJoin.getJoinedGroup().getGroupAlias().equals( alias ) ) { + tableGroupJoin.applyPredicate( predicate ); + } + } ); + } + ); + } + } } @Override @@ -519,10 +1326,56 @@ public abstract class BaseSqmToSqlAstConverter } @Override - public Object visitSelection(SqmSelection selection) { + public Void visitSelection(SqmSelection sqmSelection) { currentSqlSelectionCollector().next(); - selection.getSelectableNode().accept( this ); - return selection; + final DomainResultProducer resultProducer = resolveDomainResultProducer( sqmSelection ); + + if ( domainResults != null ) { + final Stack processingStateStack = getProcessingStateStack(); + final boolean collectDomainResults; + if ( processingStateStack.depth() == 1) { + collectDomainResults = true; + } + else { + final SqlAstProcessingState current = processingStateStack.getCurrent(); + // Since we only want to create domain results for the first/left-most query spec within query groups, + // we have to check if the current query spec is the left-most. + // This is the case when all upper level in-flight query groups are still empty + collectDomainResults = processingStateStack.findCurrentFirst( + processingState -> { + if ( !( processingState instanceof SqlAstQueryPartProcessingState ) ) { + return Boolean.FALSE; + } + if ( processingState == current ) { + return null; + } + final QueryPart part = ( (SqlAstQueryPartProcessingState) processingState ).getInflightQueryPart(); + if ( part instanceof QueryGroup ) { + if ( ( (QueryGroup) part ).getQueryParts().isEmpty() ) { + return null; + } + } + return Boolean.FALSE; + } + ) == null; + } + if ( collectDomainResults ) { + final DomainResult domainResult = resultProducer.createDomainResult( + sqmSelection.getAlias(), + this + ); + + domainResults.add( domainResult ); + } + else { + resultProducer.applySqlSelections( this ); + } + } + return null; + } + + private DomainResultProducer resolveDomainResultProducer(SqmSelection sqmSelection) { + return (DomainResultProducer) sqmSelection.getSelectableNode().accept( this ); } protected Expression resolveGroupOrOrderByExpression(SqmExpression groupByClauseExpression) { @@ -590,8 +1443,16 @@ public abstract class BaseSqmToSqlAstConverter ); } + public QuerySpec visitOffsetAndFetchExpressions(QuerySpec sqlQuerySpec, SqmQuerySpec sqmQuerySpec) { + final Expression offsetExpression = visitOffsetExpression( sqmQuerySpec.getOffsetExpression() ); + final Expression fetchExpression = visitFetchExpression( sqmQuerySpec.getFetchExpression() ); + sqlQuerySpec.setOffsetClauseExpression( offsetExpression ); + sqlQuerySpec.setFetchClauseExpression( fetchExpression, sqmQuerySpec.getFetchClauseType() ); + return sqlQuerySpec; + } + @Override - public Expression visitOffsetExpression(SqmExpression expression) { + public Expression visitOffsetExpression(SqmExpression expression) { if ( expression == null ) { return null; } @@ -606,12 +1467,12 @@ public abstract class BaseSqmToSqlAstConverter } @Override - public Expression visitLimitExpression(SqmExpression expression) { + public Expression visitFetchExpression(SqmExpression expression) { if ( expression == null ) { return null; } - currentClauseStack.push( Clause.LIMIT ); + currentClauseStack.push( Clause.FETCH ); try { return (Expression) expression.accept( this ); } @@ -643,23 +1504,122 @@ public abstract class BaseSqmToSqlAstConverter @SuppressWarnings("WeakerAccess") protected void consumeFromClauseRoot(SqmRoot sqmRoot) { log.tracef( "Resolving SqmRoot [%s] to TableGroup", sqmRoot ); - + final FromClauseIndex fromClauseIndex = getFromClauseIndex(); if ( fromClauseIndex.isResolved( sqmRoot ) ) { log.tracef( "Already resolved SqmRoot [%s] to TableGroup", sqmRoot ); } + final SqlExpressionResolver sqlExpressionResolver = getSqlExpressionResolver(); + final TableGroup tableGroup; + if ( sqmRoot.isCorrelated() ) { + final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory(); + final TableGroup parentTableGroup = fromClauseIndex.findTableGroup( + sqmRoot.getCorrelationParent().getNavigablePath() + ); + final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() ); + if ( sqmRoot.containsOnlyInnerJoins() ) { + // If we have just inner joins against a correlated root, we can render the joins as references + final SqlAliasBase sqlAliasBase = sqlAliasBaseManager.createSqlAliasBase( parentTableGroup.getGroupAlias() ); + tableGroup = new CorrelatedTableGroup( + parentTableGroup, + sqlAliasBase, + currentQuerySpec(), + predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( + additionalRestrictions, + predicate + ), + sessionFactory + ); - final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() ); + log.tracef( "Resolved SqmRoot [%s] to correlated TableGroup [%s]", sqmRoot, tableGroup ); - final TableGroup tableGroup = entityDescriptor.createRootTableGroup( - sqmRoot.getNavigablePath(), - sqmRoot.getExplicitAlias(), - true, - LockMode.NONE, - sqlAliasBaseManager, - getSqlExpressionResolver(), - () -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( additionalRestrictions, predicate ), - creationContext - ); + consumeExplicitJoins( sqmRoot, tableGroup ); + consumeImplicitJoins( sqmRoot, tableGroup ); + return; + } + else { + // If we have non-inner joins against a correlated root, we must render the root with a correlation predicate + tableGroup = entityDescriptor.createRootTableGroup( + sqmRoot.getNavigablePath(), + sqmRoot.getExplicitAlias(), + true, + LockMode.NONE, + sqlAliasBaseManager, + sqlExpressionResolver, + () -> predicate -> {}, + creationContext + ); + final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); + final int jdbcTypeCount = identifierMapping.getJdbcTypeCount(); + if ( jdbcTypeCount == 1 ) { + identifierMapping.forEachSelection( + (selectionIndex, selectionMapping) -> { + additionalRestrictions = SqlAstTreeHelper.combinePredicates( + additionalRestrictions, + new ComparisonPredicate( + new ColumnReference( + parentTableGroup.getTableReference( selectionMapping.getContainingTableExpression() ), + selectionMapping, + sessionFactory + ), + ComparisonOperator.EQUAL, + new ColumnReference( + tableGroup.getTableReference( selectionMapping.getContainingTableExpression() ), + selectionMapping, + sessionFactory + ) + ) + ); + } + ); + } + else { + final List lhs = new ArrayList<>( jdbcTypeCount ); + final List rhs = new ArrayList<>( jdbcTypeCount ); + identifierMapping.forEachSelection( + (selectionIndex, selectionMapping) -> { + lhs.add( + new ColumnReference( + parentTableGroup.getTableReference( selectionMapping.getContainingTableExpression() ), + selectionMapping, + sessionFactory + ) + ); + rhs.add( + new ColumnReference( + tableGroup.getTableReference( selectionMapping.getContainingTableExpression() ), + selectionMapping, + sessionFactory + ) + ); + } + ); + additionalRestrictions = SqlAstTreeHelper.combinePredicates( + additionalRestrictions, + new ComparisonPredicate( + new SqlTuple( lhs, identifierMapping ), + ComparisonOperator.EQUAL, + new SqlTuple( rhs, identifierMapping ) + ) + ); + } + } + } + else { + final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() ); + tableGroup = entityDescriptor.createRootTableGroup( + sqmRoot.getNavigablePath(), + sqmRoot.getExplicitAlias(), + true, + LockMode.NONE, + sqlAliasBaseManager, + sqlExpressionResolver, + () -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( + additionalRestrictions, + predicate + ), + creationContext + ); + } log.tracef( "Resolved SqmRoot [%s] to new TableGroup [%s]", sqmRoot, tableGroup ); @@ -674,7 +1634,7 @@ public abstract class BaseSqmToSqlAstConverter return creationContext.getDomainModel().getEntityDescriptor( entityDomainType.getHibernateEntityName() ); } - protected void consumeExplicitJoins(SqmFrom sqmFrom, TableGroup lhsTableGroup) { + protected void consumeExplicitJoins(SqmFrom sqmFrom, TableGroup lhsTableGroup) { if ( log.isTraceEnabled() ) { log.tracef( "Visiting explicit joins for `%s`", sqmFrom.getNavigablePath() ); } @@ -684,7 +1644,7 @@ public abstract class BaseSqmToSqlAstConverter } @SuppressWarnings("WeakerAccess") - protected void consumeExplicitJoin(SqmJoin sqmJoin, TableGroup lhsTableGroup) { + protected void consumeExplicitJoin(SqmJoin sqmJoin, TableGroup lhsTableGroup) { if ( sqmJoin instanceof SqmAttributeJoin ) { consumeAttributeJoin( ( (SqmAttributeJoin) sqmJoin ), lhsTableGroup ); } @@ -699,7 +1659,7 @@ public abstract class BaseSqmToSqlAstConverter } } - private void consumeAttributeJoin(SqmAttributeJoin sqmJoin, TableGroup lhsTableGroup) { + private void consumeAttributeJoin(SqmAttributeJoin sqmJoin, TableGroup lhsTableGroup) { final SqmPathSource pathSource = sqmJoin.getReferencedPathSource(); @@ -758,7 +1718,7 @@ public abstract class BaseSqmToSqlAstConverter joinedTableGroup = joinedTableGroupJoin.getJoinedGroup(); lhsTableGroup.addTableGroupJoin( joinedTableGroupJoin ); - fromClauseIndex.register( sqmJoin, joinedTableGroup, joinPath ); + getFromClauseIndex().register( sqmJoin, joinedTableGroup, joinPath ); // add any additional join restrictions if ( sqmJoin.getJoinPredicate() != null ) { @@ -767,7 +1727,7 @@ public abstract class BaseSqmToSqlAstConverter } if ( joinedTableGroupJoin == null ) { - throw new IllegalStateException( ); + throw new IllegalStateException(); } joinedTableGroupJoin.applyPredicate( @@ -806,7 +1766,10 @@ public abstract class BaseSqmToSqlAstConverter determineLockMode( sqmJoin.getExplicitAlias() ), sqlAliasBaseManager, getSqlExpressionResolver(), - () -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( additionalRestrictions, predicate ), + () -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( + additionalRestrictions, + predicate + ), getCreationContext() ); @@ -818,7 +1781,7 @@ public abstract class BaseSqmToSqlAstConverter lhsTableGroup.addTableGroupJoin( tableGroupJoin ); - fromClauseIndex.register( sqmJoin, tableGroup ); + getFromClauseIndex().register( sqmJoin, tableGroup ); consumeExplicitJoins( sqmJoin, tableGroupJoin.getJoinedGroup() ); consumeImplicitJoins( sqmJoin, tableGroupJoin.getJoinedGroup() ); @@ -834,10 +1797,13 @@ public abstract class BaseSqmToSqlAstConverter determineLockMode( sqmJoin.getExplicitAlias() ), sqlAliasBaseManager, getSqlExpressionResolver(), - () -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( additionalRestrictions, predicate ), + () -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( + additionalRestrictions, + predicate + ), getCreationContext() ); - fromClauseIndex.register( sqmJoin, tableGroup ); + getFromClauseIndex().register( sqmJoin, tableGroup ); final TableGroupJoin tableGroupJoin = new TableGroupJoin( sqmJoin.getNavigablePath(), @@ -868,8 +1834,8 @@ public abstract class BaseSqmToSqlAstConverter if ( log.isTraceEnabled() ) { log.tracef( "Starting implicit join handling for `%s`", joinedPath.getNavigablePath() ); } - - assert getFromClauseAccess().findTableGroup( joinedPath.getLhs().getNavigablePath() ) == tableGroup; + final FromClauseIndex fromClauseIndex = getFromClauseIndex(); + assert fromClauseIndex.findTableGroup( joinedPath.getLhs().getNavigablePath() ) == tableGroup; final ModelPart subPart = tableGroup.getModelPart().findSubPart( joinedPath.getReferencedPathSource().getPathName(), @@ -903,7 +1869,7 @@ public abstract class BaseSqmToSqlAstConverter @Override public TableGroup visitRootPath(SqmRoot sqmRoot) { - final TableGroup resolved = fromClauseIndex.findTableGroup( sqmRoot.getNavigablePath() ); + final TableGroup resolved = getFromClauseAccess().findTableGroup( sqmRoot.getNavigablePath() ); if ( resolved != null ) { log.tracef( "SqmRoot [%s] resolved to existing TableGroup [%s]", sqmRoot, resolved ); return resolved; @@ -917,7 +1883,7 @@ public abstract class BaseSqmToSqlAstConverter // todo (6.0) : have this resolve to TableGroup instead? // - trying to remove tracking of TableGroupJoin in the x-refs - final TableGroup existing = fromClauseIndex.findTableGroup( sqmJoin.getNavigablePath() ); + final TableGroup existing = getFromClauseAccess().findTableGroup( sqmJoin.getNavigablePath() ); if ( existing != null ) { log.tracef( "SqmAttributeJoin [%s] resolved to existing TableGroup [%s]", sqmJoin, existing ); return existing; @@ -927,12 +1893,17 @@ public abstract class BaseSqmToSqlAstConverter } private QuerySpec currentQuerySpec() { - final SqlAstQuerySpecProcessingState processingState = (SqlAstQuerySpecProcessingState) getProcessingStateStack().getCurrent(); - return processingState.getInflightQuerySpec(); + return currentQueryPart().getLastQuerySpec(); + } + + private QueryPart currentQueryPart() { + final SqlAstQueryPartProcessingState processingState = (SqlAstQueryPartProcessingState) getProcessingStateStack() + .getCurrent(); + return processingState.getInflightQueryPart(); } protected SqlSelectionForSqmSelectionCollector currentSqlSelectionCollector() { - return (SqlSelectionForSqmSelectionCollector) getProcessingStateStack().getCurrent().getSqlExpressionResolver(); + return (SqlSelectionForSqmSelectionCollector) getCurrentProcessingState().getSqlExpressionResolver(); } @Override @@ -940,7 +1911,7 @@ public abstract class BaseSqmToSqlAstConverter // todo (6.0) : have this resolve to TableGroup instead? // - trying to remove tracking of TableGroupJoin in the x-refs - final TableGroup existing = fromClauseIndex.findTableGroup( sqmJoin.getNavigablePath() ); + final TableGroup existing = getFromClauseAccess().findTableGroup( sqmJoin.getNavigablePath() ); if ( existing != null ) { log.tracef( "SqmCrossJoin [%s] resolved to existing TableGroup [%s]", sqmJoin, existing ); return existing; @@ -954,7 +1925,7 @@ public abstract class BaseSqmToSqlAstConverter // todo (6.0) : have this resolve to TableGroup instead? // - trying to remove tracking of TableGroupJoin in the x-refs - final TableGroup existing = fromClauseIndex.findTableGroup( sqmJoin.getNavigablePath() ); + final TableGroup existing = getFromClauseAccess().findTableGroup( sqmJoin.getNavigablePath() ); if ( existing != null ) { log.tracef( "SqmEntityJoin [%s] resolved to existing TableGroup [%s]", sqmJoin, existing ); return existing; @@ -1015,7 +1986,7 @@ public abstract class BaseSqmToSqlAstConverter // nanoseconds to the given unit MappingModelExpressable durationType = scaledExpression.getExpressionType(); - Duration duration = new Duration( scaledExpression, NANOSECOND, (BasicValuedMapping) durationType); + Duration duration = new Duration( scaledExpression, NANOSECOND, (BasicValuedMapping) durationType ); TemporalUnit appliedUnit = appliedByUnit.getUnit().getUnit(); BasicValuedMapping scalarType = (BasicValuedMapping) appliedByUnit.getNodeType(); @@ -1042,7 +2013,7 @@ public abstract class BaseSqmToSqlAstConverter @Override public SqmPathInterpretation visitEntityValuedPath(SqmEntityValuedSimplePath sqmPath) { - return EntityValuedPathInterpretation.from( sqmPath, this); + return EntityValuedPathInterpretation.from( sqmPath, this ); } @Override @@ -1070,7 +2041,7 @@ public abstract class BaseSqmToSqlAstConverter ); } - private final Map> jdbcParamsBySqmParam = new IdentityHashMap<>(); + private final Map> jdbcParamsBySqmParam = new IdentityHashMap<>(); private final JdbcParameters jdbcParameters = new JdbcParametersImpl(); @Override @@ -1099,7 +2070,7 @@ public abstract class BaseSqmToSqlAstConverter final QueryParameterImplementor queryParameter = domainParameterXref.getQueryParameter( sqmParameter ); final QueryParameterBinding binding = domainParameterBindings.getBinding( queryParameter ); - binding.setType(valueMapping); + binding.setType( valueMapping ); return new SqmParameterInterpretation( sqmParameter, queryParameter, @@ -1110,7 +2081,8 @@ public abstract class BaseSqmToSqlAstConverter } protected MappingModelExpressable resolveMappingExpressable(SqmExpressable nodeType) { - final MappingModelExpressable valueMapping = getCreationContext().getDomainModel().resolveMappingExpressable( nodeType ); + final MappingModelExpressable valueMapping = getCreationContext().getDomainModel().resolveMappingExpressable( + nodeType ); if ( valueMapping == null ) { final Supplier currentExpressableSupplier = inferableTypeAccessStack.getCurrent(); @@ -1150,7 +2122,8 @@ public abstract class BaseSqmToSqlAstConverter log.debugf( "Determining mapping-model type for generalized SqmExpression : %s", sqmExpression ); final SqmExpressable nodeType = sqmExpression.getNodeType(); - final MappingModelExpressable valueMapping = getCreationContext().getDomainModel().resolveMappingExpressable( nodeType ); + final MappingModelExpressable valueMapping = getCreationContext().getDomainModel().resolveMappingExpressable( + nodeType ); if ( valueMapping == null ) { final Supplier currentExpressableSupplier = inferableTypeAccessStack.getCurrent(); @@ -1270,7 +2243,7 @@ public abstract class BaseSqmToSqlAstConverter shallownessStack.push( Shallowness.FUNCTION ); try { //noinspection unchecked - return sqmFunction.convertToSqlAst(this); + return sqmFunction.convertToSqlAst( this ); } finally { shallownessStack.pop(); @@ -1735,7 +2708,7 @@ public abstract class BaseSqmToSqlAstConverter try { return new UnaryOperation( interpret( expression.getOperation() ), - toSqlExpression( expression.getOperand().accept(this) ), + toSqlExpression( expression.getOperand().accept( this ) ), (BasicValuedMapping) determineValueMapping( expression.getOperand() ) ); } @@ -1762,24 +2735,24 @@ public abstract class BaseSqmToSqlAstConverter TemporalType temporalTypeToRight = typeConfiguration.getSqlTemporalType( rightOperand.getNodeType() ); boolean temporalTypeSomewhereToLeft = adjustedTimestamp != null || temporalTypeToLeft != null; - if (temporalTypeToLeft != null && durationToRight) { - if (adjustmentScale != null || negativeAdjustment) { + if ( temporalTypeToLeft != null && durationToRight ) { + if ( adjustmentScale != null || negativeAdjustment ) { //we can't distribute a scale over a date/timestamp - throw new SemanticException("scalar multiplication of temporal value"); + throw new SemanticException( "scalar multiplication of temporal value" ); } } - if (durationToRight && temporalTypeSomewhereToLeft) { + if ( durationToRight && temporalTypeSomewhereToLeft ) { return transformDurationArithmetic( expression ); } - else if (temporalTypeToLeft != null && temporalTypeToRight != null) { + else if ( temporalTypeToLeft != null && temporalTypeToRight != null ) { return transformDatetimeArithmetic( expression ); } - else if (durationToRight && appliedByUnit!=null) { + else if ( durationToRight && appliedByUnit != null ) { return new BinaryArithmeticExpression( - toSqlExpression( leftOperand.accept(this) ), + toSqlExpression( leftOperand.accept( this ) ), expression.getOperator(), - toSqlExpression( rightOperand.accept(this) ), + toSqlExpression( rightOperand.accept( this ) ), //after distributing the 'by unit' operator //we always get a Long value back (BasicValuedMapping) appliedByUnit.getNodeType() @@ -1849,7 +2822,7 @@ public abstract class BaseSqmToSqlAstConverter SqmExpressable timestampType = adjustedTimestampType; adjustedTimestamp = toSqlExpression( expression.getLeftHandOperand().accept( this ) ); MappingModelExpressable type = adjustedTimestamp.getExpressionType(); - if (type instanceof SqmExpressable) { + if ( type instanceof SqmExpressable ) { adjustedTimestampType = (SqmExpressable) type; } // else if (type instanceof BasicValuedMapping) { @@ -1859,14 +2832,14 @@ public abstract class BaseSqmToSqlAstConverter // else we know it has not been transformed adjustedTimestampType = expression.getLeftHandOperand().getNodeType(); } - if (operator == SUBTRACT) { + if ( operator == SUBTRACT ) { negativeAdjustment = !negativeAdjustment; } try { return expression.getRightHandOperand().accept( this ); } finally { - if (operator == SUBTRACT) { + if ( operator == SUBTRACT ) { negativeAdjustment = !negativeAdjustment; } adjustedTimestamp = timestamp; @@ -1884,7 +2857,7 @@ public abstract class BaseSqmToSqlAstConverter // x * (d1 - d2) => x * d1 - x * d2 // -x * (d1 + d2) => - x * d1 - x * d2 // -x * (d1 - d2) => - x * d1 + x * d2 - Expression duration = toSqlExpression( expression.getLeftHandOperand().accept(this) ); + Expression duration = toSqlExpression( expression.getLeftHandOperand().accept( this ) ); Expression scale = adjustmentScale; boolean negate = negativeAdjustment; adjustmentScale = applyScale( duration ); @@ -1897,7 +2870,7 @@ public abstract class BaseSqmToSqlAstConverter negativeAdjustment = negate; } default: - throw new SemanticException("illegal operator for a duration " + operator); + throw new SemanticException( "illegal operator for a duration " + operator ); } } @@ -1910,7 +2883,7 @@ public abstract class BaseSqmToSqlAstConverter // operator expressions with two dates or // timestamps are ill-formed if ( operator != SUBTRACT ) { - throw new SemanticException("illegal operator for temporal type: " + operator); + throw new SemanticException( "illegal operator for temporal type: " + operator ); } // a difference between two dates or two @@ -1918,20 +2891,22 @@ public abstract class BaseSqmToSqlAstConverter // must apply the scale, and the 'by unit' // ts1 - ts2 - Expression left = cleanly(() -> toSqlExpression( expression.getLeftHandOperand().accept(this) )); - Expression right = cleanly(() -> toSqlExpression( expression.getRightHandOperand().accept(this) )); + Expression left = cleanly( () -> toSqlExpression( expression.getLeftHandOperand().accept( this ) ) ); + Expression right = cleanly( () -> toSqlExpression( expression.getRightHandOperand().accept( this ) ) ); TypeConfiguration typeConfiguration = getCreationContext().getDomainModel().getTypeConfiguration(); - TemporalType leftTimestamp = typeConfiguration.getSqlTemporalType( expression.getLeftHandOperand().getNodeType() ) ; + TemporalType leftTimestamp = typeConfiguration.getSqlTemporalType( expression.getLeftHandOperand().getNodeType() ); TemporalType rightTimestamp = typeConfiguration.getSqlTemporalType( expression.getRightHandOperand().getNodeType() ); // when we're dealing with Dates, we use // DAY as the smallest unit, otherwise we // use a platform-specific granularity - TemporalUnit baseUnit = (rightTimestamp == TemporalType.TIMESTAMP || leftTimestamp == TemporalType.TIMESTAMP) ? NATIVE : DAY; + TemporalUnit baseUnit = ( rightTimestamp == TemporalType.TIMESTAMP || leftTimestamp == TemporalType.TIMESTAMP ) ? + NATIVE : + DAY; - if (adjustedTimestamp != null) { + if ( adjustedTimestamp != null ) { if ( appliedByUnit != null ) { throw new IllegalStateException(); } @@ -1942,30 +2917,34 @@ public abstract class BaseSqmToSqlAstConverter // temporal type, so we must use it for both // the diff, and then the subsequent add - DurationUnit unit = new DurationUnit( baseUnit, basicType(Integer.class) ); + DurationUnit unit = new DurationUnit( baseUnit, basicType( Integer.class ) ); Expression scaledMagnitude = applyScale( timestampdiff().expression( (AllowableFunctionReturnType) expression.getNodeType(), - unit, right, left ) ); + unit, right, left + ) ); return timestampadd().expression( (AllowableFunctionReturnType) adjustedTimestampType, //TODO should be adjustedTimestamp.getType() - unit, scaledMagnitude, adjustedTimestamp ); + unit, scaledMagnitude, adjustedTimestamp + ); } - else if (appliedByUnit != null) { + else if ( appliedByUnit != null ) { // we're immediately converting the resulting // duration to a scalar in the given unit - DurationUnit unit = (DurationUnit) appliedByUnit.getUnit().accept(this); + DurationUnit unit = (DurationUnit) appliedByUnit.getUnit().accept( this ); return applyScale( timestampdiff().expression( (AllowableFunctionReturnType) expression.getNodeType(), - unit, right, left ) ); + unit, right, left + ) ); } else { // a plain "bare" Duration - DurationUnit unit = new DurationUnit( baseUnit, basicType(Integer.class) ); + DurationUnit unit = new DurationUnit( baseUnit, basicType( Integer.class ) ); BasicValuedMapping durationType = (BasicValuedMapping) expression.getNodeType(); Expression scaledMagnitude = applyScale( timestampdiff().expression( (AllowableFunctionReturnType) expression.getNodeType(), - unit, right, left) ); + unit, right, left + ) ); return new Duration( scaledMagnitude, baseUnit, durationType ); } } @@ -1978,14 +2957,14 @@ public abstract class BaseSqmToSqlAstConverter return (TimestampaddFunction) getCreationContext().getSessionFactory() .getQueryEngine().getSqmFunctionRegistry() - .findFunctionDescriptor("timestampadd"); + .findFunctionDescriptor( "timestampadd" ); } private TimestampdiffFunction timestampdiff() { return (TimestampdiffFunction) getCreationContext().getSessionFactory() .getQueryEngine().getSqmFunctionRegistry() - .findFunctionDescriptor("timestampdiff"); + .findFunctionDescriptor( "timestampdiff" ); } private T cleanly(Supplier supplier) { @@ -2060,7 +3039,7 @@ public abstract class BaseSqmToSqlAstConverter @SuppressWarnings("unchecked") static boolean isOne(Expression scale) { return scale instanceof QueryLiteral - && ((QueryLiteral) scale).getLiteralValue().longValue() == 1L; + && ( (QueryLiteral) scale ).getLiteralValue().longValue() == 1L; } @Override @@ -2068,8 +3047,8 @@ public abstract class BaseSqmToSqlAstConverter //TODO: do we need to temporarily set appliedByUnit // to null before we recurse down the tree? // and what about scale? - Expression magnitude = toSqlExpression( toDuration.getMagnitude().accept(this) ); - DurationUnit unit = (DurationUnit) toDuration.getUnit().accept(this); + Expression magnitude = toSqlExpression( toDuration.getMagnitude().accept( this ) ); + DurationUnit unit = (DurationUnit) toDuration.getUnit().accept( this ); // let's start by applying the propagated scale // so we don't forget to do it in what follows @@ -2084,7 +3063,8 @@ public abstract class BaseSqmToSqlAstConverter } return timestampadd().expression( (AllowableFunctionReturnType) adjustedTimestampType, //TODO should be adjustedTimestamp.getType() - unit, scaledMagnitude, adjustedTimestamp ); + unit, scaledMagnitude, adjustedTimestamp + ); } else { BasicValuedMapping durationType = (BasicValuedMapping) toDuration.getNodeType(); @@ -2118,8 +3098,8 @@ public abstract class BaseSqmToSqlAstConverter } @Override - public QuerySpec visitSubQueryExpression(SqmSubQuery sqmSubQuery) { - return visitQuerySpec( sqmSubQuery.getQuerySpec() ); + public QueryPart visitSubQueryExpression(SqmSubQuery sqmSubQuery) { + return visitQueryPart( sqmSubQuery.getQueryPart() ); // // final ExpressableType expressableType = determineExpressableType( sqmSubQuery ); // @@ -2133,23 +3113,23 @@ public abstract class BaseSqmToSqlAstConverter } @Override - public CaseSimpleExpression visitSimpleCaseExpression(SqmCaseSimple expression) { + public CaseSimpleExpression visitSimpleCaseExpression(SqmCaseSimple expression) { SqmExpressable resultType = expression.getNodeType(); List whenFragments = new ArrayList<>( expression.getWhenFragments().size() ); for ( SqmCaseSimple.WhenFragment whenFragment : expression.getWhenFragments() ) { resultType = QueryHelper.highestPrecedenceType2( resultType, whenFragment.getResult().getNodeType() ); whenFragments.add( - new CaseSimpleExpression.WhenFragment( - (Expression) whenFragment.getCheckValue().accept(this), - (Expression) whenFragment.getResult().accept(this) - ) + new CaseSimpleExpression.WhenFragment( + (Expression) whenFragment.getCheckValue().accept( this ), + (Expression) whenFragment.getResult().accept( this ) + ) ); } Expression otherwise = null; if ( expression.getOtherwise() != null ) { resultType = QueryHelper.highestPrecedenceType2( resultType, expression.getOtherwise().getNodeType() ); - otherwise = (Expression) expression.getOtherwise().accept(this ); + otherwise = (Expression) expression.getOtherwise().accept( this ); } final CaseSimpleExpression result = new CaseSimpleExpression( @@ -2169,17 +3149,17 @@ public abstract class BaseSqmToSqlAstConverter for ( SqmCaseSearched.WhenFragment whenFragment : expression.getWhenFragments() ) { resultType = QueryHelper.highestPrecedenceType2( resultType, whenFragment.getResult().getNodeType() ); whenFragments.add( - new CaseSearchedExpression.WhenFragment( - (Predicate) whenFragment.getPredicate().accept(this), - (Expression) whenFragment.getResult().accept(this) - ) + new CaseSearchedExpression.WhenFragment( + (Predicate) whenFragment.getPredicate().accept( this ), + (Expression) whenFragment.getResult().accept( this ) + ) ); } Expression otherwise = null; if ( expression.getOtherwise() != null ) { resultType = QueryHelper.highestPrecedenceType2( resultType, expression.getOtherwise().getNodeType() ); - otherwise = (Expression) expression.getOtherwise().accept(this ); + otherwise = (Expression) expression.getOtherwise().accept( this ); } final CaseSearchedExpression result = new CaseSearchedExpression( @@ -2216,7 +3196,7 @@ public abstract class BaseSqmToSqlAstConverter expressions.add( (Expression) groupingExpressions.get( i ).accept( this ) ); } return new Summarization( - getSummarizationKind(sqmSummarization.getKind()), + getSummarizationKind( sqmSummarization.getKind() ), expressions ); } @@ -2231,6 +3211,24 @@ public abstract class BaseSqmToSqlAstConverter throw new UnsupportedOperationException( "Unsupported summarization: " + kind ); } + @Override + public Expression visitEntityTypeLiteralExpression(SqmLiteralEntityType sqmExpression) { + final EntityDomainType nodeType = sqmExpression.getNodeType(); + final EntityPersister mappingDescriptor = getCreationContext().getDomainModel() + .getEntityDescriptor( nodeType.getHibernateEntityName() ); + + return new EntityTypeLiteral( mappingDescriptor ); + } + + @Override + public Expression visitSqmPathEntityTypeExpression(SqmPathEntityType sqmExpression) { + return BasicValuedPathInterpretation.from( + sqmExpression, + this, + this + ); + } + @Override public Object visitEnumLiteral(SqmEnumLiteral sqmEnumLiteral) { return new QueryLiteral<>( @@ -2303,7 +3301,8 @@ public abstract class BaseSqmToSqlAstConverter @Override public InSubQueryPredicate visitMemberOfPredicate(SqmMemberOfPredicate predicate) { final SqmPath pluralPath = predicate.getPluralPath(); - final PluralAttributeMapping mappingModelExpressable = (PluralAttributeMapping) determineValueMapping(pluralPath); + final PluralAttributeMapping mappingModelExpressable = (PluralAttributeMapping) determineValueMapping( + pluralPath ); if ( mappingModelExpressable.getElementDescriptor() instanceof EntityCollectionPart ) { inferableTypeAccessStack.push( @@ -2332,12 +3331,13 @@ public abstract class BaseSqmToSqlAstConverter ); } - private QuerySpec createMemberOfSubQuery(SqmPath pluralPath, PluralAttributeMapping mappingModelExpressable) { + private QueryPart createMemberOfSubQuery(SqmPath pluralPath, PluralAttributeMapping mappingModelExpressable) { + final FromClauseAccess parentFromClauseAccess = getFromClauseAccess(); final QuerySpec querySpec = new QuerySpec( false ); - processingStateStack.push( - new SqlAstQuerySpecProcessingStateImpl( + pushProcessingState( + new SqlAstQueryPartProcessingStateImpl( querySpec, - processingStateStack.getCurrent(), + getCurrentProcessingState(), this, currentClauseStack::getCurrent ) @@ -2355,7 +3355,7 @@ public abstract class BaseSqmToSqlAstConverter creationContext ); - fromClauseIndex.registerTableGroup( pluralPath.getNavigablePath(), rootTableGroup ); + getFromClauseAccess().registerTableGroup( pluralPath.getNavigablePath(), rootTableGroup ); querySpec.getFromClause().addRoot( rootTableGroup ); @@ -2369,7 +3369,7 @@ public abstract class BaseSqmToSqlAstConverter ); final Predicate predicate = mappingModelExpressable.getKeyDescriptor().generateJoinPredicate( - getFromClauseAccess().findTableGroup( pluralPath.getNavigablePath().getParent() ), + parentFromClauseAccess.findTableGroup( pluralPath.getNavigablePath().getParent() ), rootTableGroup, null, getSqlExpressionResolver(), @@ -2378,7 +3378,7 @@ public abstract class BaseSqmToSqlAstConverter querySpec.applyPredicate( predicate ); } finally { - processingStateStack.pop(); + popProcessingStateStack(); } return querySpec; @@ -2420,62 +3420,65 @@ public abstract class BaseSqmToSqlAstConverter public Object visitIsEmptyPredicate(SqmEmptinessPredicate predicate) { final QuerySpec subQuerySpec = new QuerySpec( false, 1 ); - final SqlAstProcessingState parentState = getProcessingStateStack().getCurrent(); - + final FromClauseAccess parentFromClauseAccess = getFromClauseAccess(); final SqlAstProcessingStateImpl subQueryState = new SqlAstProcessingStateImpl( - parentState, + getCurrentProcessingState(), this, currentClauseStack::getCurrent ); - getProcessingStateStack().push( subQueryState ); + pushProcessingState( subQueryState ); + try { + final SqmPluralValuedSimplePath sqmPluralPath = predicate.getPluralPath(); - final SqmPluralValuedSimplePath sqmPluralPath = predicate.getPluralPath(); + final NavigablePath pluralPathNavPath = sqmPluralPath.getNavigablePath(); + final NavigablePath parentNavPath = pluralPathNavPath.getParent(); + assert parentNavPath != null; - final NavigablePath pluralPathNavPath = sqmPluralPath.getNavigablePath(); - final NavigablePath parentNavPath = pluralPathNavPath.getParent(); - assert parentNavPath != null; + final TableGroup parentTableGroup = parentFromClauseAccess.getTableGroup( parentNavPath ); - final TableGroup parentTableGroup = parentState - .getSqlAstCreationState() - .getFromClauseAccess() - .getTableGroup( parentNavPath ); + subQueryState.getSqlAstCreationState().getFromClauseAccess().registerTableGroup( + parentNavPath, + parentTableGroup + ); - subQueryState.getSqlAstCreationState().getFromClauseAccess().registerTableGroup( parentNavPath, parentTableGroup ); - - final SqmPathInterpretation sqmPathInterpretation = visitPluralValuedPath( sqmPluralPath ); + final SqmPathInterpretation sqmPathInterpretation = visitPluralValuedPath( sqmPluralPath ); - final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) sqmPathInterpretation.getExpressionType(); + final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) sqmPathInterpretation.getExpressionType(); - // note : do not add to `parentTableGroup` as a join + // note : do not add to `parentTableGroup` as a join - final TableGroupJoin tableGroupJoin = pluralAttributeMapping.createTableGroupJoin( - pluralPathNavPath, - parentTableGroup, - sqmPluralPath.getExplicitAlias(), - SqlAstJoinType.LEFT, - LockMode.NONE, - sqlAliasBaseManager, - subQueryState, - creationContext - ); + final TableGroupJoin tableGroupJoin = pluralAttributeMapping.createTableGroupJoin( + pluralPathNavPath, + parentTableGroup, + sqmPluralPath.getExplicitAlias(), + SqlAstJoinType.LEFT, + LockMode.NONE, + sqlAliasBaseManager, + subQueryState, + creationContext + ); - final TableGroup collectionTableGroup = tableGroupJoin.getJoinedGroup(); + final TableGroup collectionTableGroup = tableGroupJoin.getJoinedGroup(); - subQuerySpec.getFromClause().addRoot( collectionTableGroup ); - subQuerySpec.applyPredicate( tableGroupJoin.getPredicate() ); + subQuerySpec.getFromClause().addRoot( collectionTableGroup ); + subQuerySpec.applyPredicate( tableGroupJoin.getPredicate() ); - final ForeignKeyDescriptor collectionKeyDescriptor = pluralAttributeMapping.getKeyDescriptor(); - final int jdbcTypeCount = collectionKeyDescriptor.getJdbcTypeCount(); - assert jdbcTypeCount > 0; + final ForeignKeyDescriptor collectionKeyDescriptor = pluralAttributeMapping.getKeyDescriptor(); + final int jdbcTypeCount = collectionKeyDescriptor.getJdbcTypeCount(); + assert jdbcTypeCount > 0; - final JdbcLiteral jdbcLiteral = new JdbcLiteral<>( 1, StandardBasicTypes.INTEGER ); - subQuerySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl(1,0, jdbcLiteral ) - ); + final JdbcLiteral jdbcLiteral = new JdbcLiteral<>( 1, StandardBasicTypes.INTEGER ); + subQuerySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( 1, 0, jdbcLiteral ) + ); - return new ExistsPredicate( subQuerySpec ); + return new ExistsPredicate( subQuerySpec ); + } + finally { + popProcessingStateStack(); + } } @Override @@ -2594,7 +3597,9 @@ public abstract class BaseSqmToSqlAstConverter return inPredicate; } - private InListPredicate processInListWithSingleParameter(SqmInListPredicate sqmPredicate, SqmParameter sqmParameter) { + private InListPredicate processInListWithSingleParameter( + SqmInListPredicate sqmPredicate, + SqmParameter sqmParameter) { assert sqmParameter.allowMultiValuedBinding(); if ( sqmParameter instanceof JpaCriteriaParameter ) { @@ -2608,7 +3613,7 @@ public abstract class BaseSqmToSqlAstConverter final QueryParameterImplementor domainParam = domainParameterXref.getQueryParameter( sqmParameter ); final QueryParameterBinding domainParamBinding = domainParameterBindings.getBinding( domainParam ); - if ( ! domainParamBinding.isMultiValued() ) { + if ( !domainParamBinding.isMultiValued() ) { // triggers normal processing return null; } @@ -2645,11 +3650,13 @@ public abstract class BaseSqmToSqlAstConverter return inListPredicate; } - private InListPredicate processInSingleCriteriaParameter(SqmInListPredicate sqmPredicate, JpaCriteriaParameter jpaCriteriaParameter) { + private InListPredicate processInSingleCriteriaParameter( + SqmInListPredicate sqmPredicate, + JpaCriteriaParameter jpaCriteriaParameter) { assert jpaCriteriaParameter.allowsMultiValuedBinding(); final QueryParameterBinding domainParamBinding = domainParameterBindings.getBinding( jpaCriteriaParameter ); - if ( ! domainParamBinding.isMultiValued() ) { + if ( !domainParamBinding.isMultiValued() ) { return null; } @@ -2661,7 +3668,8 @@ public abstract class BaseSqmToSqlAstConverter () -> determineValueMapping( sqmPredicate.getTestExpression() ) ); - final SqmJpaCriteriaParameterWrapper sqmWrapper = jpaCriteriaParamResolutions.get( jpaCriteriaParameter ).get(); + final SqmJpaCriteriaParameterWrapper sqmWrapper = jpaCriteriaParamResolutions.get( jpaCriteriaParameter ) + .get(); try { boolean first = true; @@ -2691,7 +3699,7 @@ public abstract class BaseSqmToSqlAstConverter public InSubQueryPredicate visitInSubQueryPredicate(SqmInSubQueryPredicate predicate) { return new InSubQueryPredicate( (Expression) predicate.getTestExpression().accept( this ), - (QuerySpec) predicate.getSubQueryExpression().accept( this ), + (QueryPart) predicate.getSubQueryExpression().accept( this ), predicate.isNegated() ); } @@ -2713,7 +3721,7 @@ public abstract class BaseSqmToSqlAstConverter @Override public Object visitExistsPredicate(SqmExistsPredicate predicate) { - return new ExistsPredicate( (QuerySpec) predicate.getExpression().accept( this ) ); + return new ExistsPredicate( (QueryPart) predicate.getExpression().accept( this ) ); } @Override @@ -2722,25 +3730,325 @@ public abstract class BaseSqmToSqlAstConverter } @Override - public List visitFetches(FetchParent fetchParent) { - return Collections.emptyList(); + public Object visitFullyQualifiedClass(Class namedClass) { + throw new NotYetImplementedFor6Exception(); + + // what exactly is the expected end result here? + +// final MetamodelImplementor metamodel = getSessionFactory().getMetamodel(); +// final TypeConfiguration typeConfiguration = getSessionFactory().getTypeConfiguration(); +// +// // see if it is an entity-type +// final EntityTypeDescriptor entityDescriptor = metamodel.findEntityDescriptor( namedClass ); +// if ( entityDescriptor != null ) { +// throw new NotYetImplementedFor6Exception( "Add support for entity type literals as SqlExpression" ); +// } +// +// +// final JavaTypeDescriptor jtd = typeConfiguration +// .getJavaTypeDescriptorRegistry() +// .getOrMakeJavaDescriptor( namedClass ); } - protected static class SqlSelectionForSqmSelectionCollector implements SqlExpressionResolver { + @Override + public List visitFetches(FetchParent fetchParent) { + final List fetches = CollectionHelper.arrayList( fetchParent.getReferencedMappingType().getNumberOfFetchables() ); + final List bagRoles = new ArrayList<>(); + + final BiConsumer fetchableBiConsumer = (fetchable, isKeyFetchable) -> { + final NavigablePath fetchablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() ); + + final Fetch biDirectionalFetch = fetchable.resolveCircularFetch( + fetchablePath, + fetchParent, + this + ); + + if ( biDirectionalFetch != null ) { + fetches.add( biDirectionalFetch ); + return; + } + + final boolean incrementFetchDepth = fetchable.incrementFetchDepth(); + try { + if ( incrementFetchDepth ) { + fetchDepth++; + } + final Fetch fetch = buildFetch( fetchablePath, fetchParent, fetchable, isKeyFetchable ); + + 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() ); + } + } + + fetches.add( fetch ); + } + } + finally { + if ( incrementFetchDepth ) { + fetchDepth--; + } + } + }; + +// 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 ); + } + return fetches; + } + + private Fetch buildFetch(NavigablePath fetchablePath, FetchParent fetchParent, Fetchable fetchable, boolean isKeyFetchable) { + // fetch has access to its parent in addition to the parent having its fetches. + // + // we could sever the parent -> fetch link ... it would not be "seen" while walking + // but it would still have access to its parent info - and be able to access its + // "initializing" state as part of AfterLoadAction + + final String alias; + LockMode lockMode = LockMode.READ; + FetchTiming fetchTiming = fetchable.getMappedFetchOptions().getTiming(); + boolean joined = false; + + EntityGraphTraversalState.TraversalResult traversalResult = null; + final FromClauseIndex fromClauseIndex = getFromClauseIndex(); + final SqmAttributeJoin fetchedJoin = fromClauseIndex.findFetchedJoinByPath( fetchablePath ); + + if ( fetchedJoin != null ) { + // there was an explicit fetch in the SQM + // there should be a TableGroupJoin registered for this `fetchablePath` already + // because it + assert fromClauseIndex.getTableGroup( fetchablePath ) != null; + +// + if ( fetchedJoin.isFetched() ) { + fetchTiming = FetchTiming.IMMEDIATE; + } + joined = true; + alias = fetchedJoin.getExplicitAlias(); + lockMode = determineLockMode( alias ); + } + else { + // 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.getFetchStrategy(); + joined = traversalResult.isJoined(); + } + else if ( getLoadQueryInfluencers().hasEnabledFetchProfiles() ) { + if ( fetchParent instanceof EntityResultGraphNode ) { + final EntityResultGraphNode entityFetchParent = (EntityResultGraphNode) fetchParent; + final EntityMappingType entityMappingType = entityFetchParent.getEntityValuedModelPart() + .getEntityMappingType(); + final String fetchParentEntityName = entityMappingType.getEntityName(); + final String fetchableRole = fetchParentEntityName + "." + fetchable.getFetchableName(); + + for ( String enabledFetchProfileName : getLoadQueryInfluencers().getEnabledFetchProfileNames() ) { + final FetchProfile enabledFetchProfile = getCreationContext().getSessionFactory() + .getFetchProfile( enabledFetchProfileName ); + final org.hibernate.engine.profile.Fetch profileFetch = enabledFetchProfile.getFetchByRole( + fetchableRole ); + + fetchTiming = FetchTiming.IMMEDIATE; + joined = joined || profileFetch.getStyle() == org.hibernate.engine.profile.Fetch.Style.JOIN; + } + } + } + } + + 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; + } + + // 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 ) { + fromClauseIndex.resolveTableGroup( + fetchablePath, + np -> { + // generate the join + final TableGroup lhs = fromClauseIndex.getTableGroup( fetchParent.getNavigablePath() ); + final TableGroupJoin tableGroupJoin = ( (TableGroupJoinProducer) fetchable ).createTableGroupJoin( + fetchablePath, + lhs, + alias, + SqlAstJoinType.LEFT, + LockMode.NONE, + this + ); + return tableGroupJoin.getJoinedGroup(); + } + ); + + } + } + + try { + final Fetch fetch = fetchable.generateFetch( + fetchParent, + fetchablePath, + fetchTiming, + joined, + lockMode, + alias, + this + ); + + if ( fetchable instanceof PluralAttributeMapping && fetch.getTiming() == FetchTiming.IMMEDIATE && joined ) { + final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable; + + final Joinable joinable = pluralAttributeMapping + .getCollectionDescriptor() + .getCollectionType() + .getAssociatedJoinable( getCreationContext().getSessionFactory() ); + final TableGroup tableGroup = fromClauseIndex.getTableGroup( fetchablePath ); + final FilterPredicate collectionFieldFilterPredicate = FilterHelper.createFilterPredicate( + getLoadQueryInfluencers(), + joinable, + tableGroup + ); + if ( collectionFieldFilterPredicate != null ) { + if ( collectionFilterPredicates == null ) { + collectionFilterPredicates = new HashMap<>(); + } + collectionFilterPredicates.put( tableGroup.getGroupAlias(), collectionFieldFilterPredicate ); + } + if ( pluralAttributeMapping.getCollectionDescriptor().isManyToMany() ) { + assert joinable instanceof CollectionPersister; + final Predicate manyToManyFilterPredicate = FilterHelper.createManyToManyFilterPredicate( + getLoadQueryInfluencers(), + ( CollectionPersister) joinable, + tableGroup + ); + if ( manyToManyFilterPredicate != null ) { + assert tableGroup.getTableReferenceJoins() != null && + tableGroup.getTableReferenceJoins().size() == 1; + tableGroup.getTableReferenceJoins().get( 0 ).applyPredicate( manyToManyFilterPredicate ); + } + } + + if ( orderByFragmentConsumer != null ) { + assert tableGroup.getModelPart() == pluralAttributeMapping; + + if ( pluralAttributeMapping.getOrderByFragment() != null ) { + orderByFragmentConsumer.accept( pluralAttributeMapping.getOrderByFragment(), tableGroup ); + } + + if ( pluralAttributeMapping.getManyToManyOrderByFragment() != null ) { + orderByFragmentConsumer.accept( pluralAttributeMapping.getManyToManyOrderByFragment(), tableGroup ); + } + } + } + + return fetch; + } + catch (RuntimeException e) { + throw new HibernateException( + String.format( + Locale.ROOT, + "Could not generate fetch : %s -> %s", + fetchParent.getNavigablePath(), + fetchable.getFetchableName() + ), + e + ); + } + finally { + if ( entityGraphTraversalState != null && traversalResult != null ) { + entityGraphTraversalState.backtrack( traversalResult.getPreviousContext() ); + } + } + } + + protected interface SqlSelectionForSqmSelectionCollector { + void next(); + List getSelections(int position); + } + + protected static class DelegatingSqlSelectionForSqmSelectionCollector implements SqlExpressionResolver, SqlSelectionForSqmSelectionCollector { + + private final SqlExpressionResolver delegate; + private SqlSelectionForSqmSelectionCollector sqlSelectionForSqmSelectionCollector; + + public DelegatingSqlSelectionForSqmSelectionCollector(SqlExpressionResolver delegate) { + this.delegate = delegate; + } + + @Override + public void next() { + throw new UnsupportedOperationException(); + } + + @Override + public List getSelections(int position) { + return sqlSelectionForSqmSelectionCollector.getSelections( position ); + } + + @Override + public Expression resolveSqlExpression(String key, Function creator) { + return delegate.resolveSqlExpression( key, creator ); + } + + @Override + public SqlSelection resolveSqlSelection( + Expression expression, + JavaTypeDescriptor javaTypeDescriptor, + TypeConfiguration typeConfiguration) { + return delegate.resolveSqlSelection( expression, javaTypeDescriptor, typeConfiguration ); + } + + public SqlSelectionForSqmSelectionCollector getSqlSelectionForSqmSelectionCollector() { + return sqlSelectionForSqmSelectionCollector; + } + + public void setSqlSelectionForSqmSelectionCollector(SqlSelectionForSqmSelectionCollector sqlSelectionForSqmSelectionCollector) { + this.sqlSelectionForSqmSelectionCollector = sqlSelectionForSqmSelectionCollector; + } + } + + protected static class SqlSelectionForSqmSelectionResolver implements SqlExpressionResolver, SqlSelectionForSqmSelectionCollector { private final SqlExpressionResolver delegate; private final List[] sqlSelectionsForSqmSelection; private int index = -1; - public SqlSelectionForSqmSelectionCollector(SqlExpressionResolver delegate, int sqmSelectionCount) { + public SqlSelectionForSqmSelectionResolver(SqlExpressionResolver delegate, int sqmSelectionCount) { this.delegate = delegate; sqlSelectionsForSqmSelection = new List[sqmSelectionCount]; } + @Override public void next() { index++; } + @Override public List getSelections(int position) { return sqlSelectionsForSqmSelection[position - 1]; } @@ -2751,7 +4059,10 @@ public abstract class BaseSqmToSqlAstConverter } @Override - public SqlSelection resolveSqlSelection(Expression expression, JavaTypeDescriptor javaTypeDescriptor, TypeConfiguration typeConfiguration) { + public SqlSelection resolveSqlSelection( + Expression expression, + JavaTypeDescriptor javaTypeDescriptor, + TypeConfiguration typeConfiguration) { SqlSelection selection = delegate.resolveSqlSelection( expression, javaTypeDescriptor, typeConfiguration ); List sqlSelectionList = sqlSelectionsForSqmSelection[index]; if ( sqlSelectionList == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/FromClauseIndex.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/FromClauseIndex.java index 9579c4506c..e9a979f3e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/FromClauseIndex.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/FromClauseIndex.java @@ -34,7 +34,8 @@ public class FromClauseIndex extends SimpleFromClauseAccessImpl { */ private Map fetchesByPath; - public FromClauseIndex() { + public FromClauseIndex(FromClauseIndex parent) { + super( parent ); } public void register(SqmPath sqmPath, TableGroup tableGroup) { @@ -80,7 +81,8 @@ public class FromClauseIndex extends SimpleFromClauseAccessImpl { } public boolean isResolved(SqmFrom fromElement) { - return tableGroupMap.containsKey( fromElement.getNavigablePath() ); + return tableGroupMap.containsKey( fromElement.getNavigablePath().getIdentifierForTableGroup() ) + || parent != null && ( (FromClauseIndex) parent ).isResolved( fromElement ); } public SqmAttributeJoin findFetchedJoinByPath(NavigablePath path) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SimpleSqmDeleteTranslator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SimpleSqmDeleteTranslator.java deleted file mode 100644 index 5e19565d83..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SimpleSqmDeleteTranslator.java +++ /dev/null @@ -1,17 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.sql; - -import org.hibernate.query.sqm.spi.JdbcParameterBySqmParameterAccess; -import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; - -/** - * @author Steve Ebersole - */ -public interface SimpleSqmDeleteTranslator extends SqmTranslator, SqmToSqlAstConverter, JdbcParameterBySqmParameterAccess { - SimpleSqmDeleteTranslation translate(SqmDeleteStatement statement); -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SimpleSqmUpdateTranslation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SimpleSqmUpdateTranslation.java deleted file mode 100644 index ef8b519a6e..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SimpleSqmUpdateTranslation.java +++ /dev/null @@ -1,39 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.sql; - -import java.util.List; -import java.util.Map; - -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.sql.ast.tree.update.UpdateStatement; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; - -/** - * @author Steve Ebersole - */ -public class SimpleSqmUpdateTranslation implements SqmTranslation { - private final UpdateStatement sqlAst; - private final Map> jdbcParamMap; - - public SimpleSqmUpdateTranslation( - UpdateStatement sqlAst, - Map> jdbcParamMap) { - this.sqlAst = sqlAst; - this.jdbcParamMap = jdbcParamMap; - } - - @Override - public UpdateStatement getSqlAst() { - return sqlAst; - } - - @Override - public Map> getJdbcParamsBySqmParam() { - return jdbcParamMap; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SimpleSqmUpdateTranslator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SimpleSqmUpdateTranslator.java deleted file mode 100644 index 276d14d954..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SimpleSqmUpdateTranslator.java +++ /dev/null @@ -1,17 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.sql; - -import org.hibernate.query.sqm.spi.JdbcParameterBySqmParameterAccess; -import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; - -/** - * @author Steve Ebersole - */ -public interface SimpleSqmUpdateTranslator extends SqmTranslator, SqmToSqlAstConverter, JdbcParameterBySqmParameterAccess { - SimpleSqmUpdateTranslation translate(SqmUpdateStatement sqmUpdate); -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmInsertTranslation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmInsertTranslation.java deleted file mode 100644 index 748bfcf0e8..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmInsertTranslation.java +++ /dev/null @@ -1,37 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.sql; - -import java.util.List; -import java.util.Map; - -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.insert.InsertStatement; - -/** - * @author Steve Ebersole - */ -public class SqmInsertTranslation { - private final InsertStatement sqlAst; - private final Map> jdbcParamMap; - - public SqmInsertTranslation( - InsertStatement sqlAst, - Map> jdbcParamMap) { - this.sqlAst = sqlAst; - this.jdbcParamMap = jdbcParamMap; - } - - public InsertStatement getSqlAst() { - return sqlAst; - } - - public Map> getJdbcParamsBySqmParam() { - return jdbcParamMap; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmInsertTranslator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmInsertTranslator.java deleted file mode 100644 index d0603b1ab7..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmInsertTranslator.java +++ /dev/null @@ -1,18 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.sql; - -import org.hibernate.query.sqm.spi.JdbcParameterBySqmParameterAccess; -import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; - -/** - * @author Steve Ebersole - */ -public interface SqmInsertTranslator extends SqmToSqlAstConverter, JdbcParameterBySqmParameterAccess, SqmTranslator { - SqmInsertTranslation translate(SqmInsertStatement statement); - -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmQuerySpecTranslation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmQuerySpecTranslation.java deleted file mode 100644 index 52b8c0ea96..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmQuerySpecTranslation.java +++ /dev/null @@ -1,37 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.sql; - -import java.util.List; -import java.util.Map; - -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; - -/** - * @author Steve Ebersole - */ -public class SqmQuerySpecTranslation { - private final QuerySpec sqlAst; - private final Map> jdbcParamsBySqmParam; - - public SqmQuerySpecTranslation( - QuerySpec sqlAst, - Map> jdbcParamsBySqmParam) { - this.sqlAst = sqlAst; - this.jdbcParamsBySqmParam = jdbcParamsBySqmParam; - } - - public QuerySpec getSqlAst() { - return sqlAst; - } - - public Map> getJdbcParamsBySqmParam() { - return jdbcParamsBySqmParam; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmSelectTranslation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmSelectTranslation.java deleted file mode 100644 index 3af678379a..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmSelectTranslation.java +++ /dev/null @@ -1,44 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.sql; - -import java.util.List; -import java.util.Map; - -import org.hibernate.query.sqm.sql.internal.StandardSqmSelectTranslator; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; - -/** - * Details of the result of interpreting an SQM SELECT AST into a SQL SELECT AST - * - * @see StandardSqmSelectTranslator#translate(org.hibernate.query.sqm.tree.select.SqmSelectStatement) - * - * @author Steve Ebersole - */ -public class SqmSelectTranslation implements SqmTranslation { - private final SelectStatement sqlAst; - private final Map> jdbcParamsBySqmParam; - - public SqmSelectTranslation( - SelectStatement sqlAst, - Map> jdbcParamsBySqmParam) { - this.sqlAst = sqlAst; - this.jdbcParamsBySqmParam = jdbcParamsBySqmParam; - } - - @Override - public SelectStatement getSqlAst() { - return sqlAst; - } - - @Override - public Map> getJdbcParamsBySqmParam() { - return jdbcParamsBySqmParam; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmSelectTranslator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmSelectTranslator.java deleted file mode 100644 index 39dbe93b8c..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmSelectTranslator.java +++ /dev/null @@ -1,20 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.sql; - -import org.hibernate.query.sqm.spi.JdbcParameterBySqmParameterAccess; -import org.hibernate.query.sqm.tree.select.SqmQuerySpec; -import org.hibernate.query.sqm.tree.select.SqmSelectStatement; -import org.hibernate.sql.ast.spi.FromClauseAccess; - -/** - * @author Steve Ebersole - */ -public interface SqmSelectTranslator extends SqmToSqlAstConverter, FromClauseAccess, JdbcParameterBySqmParameterAccess, SqmTranslator { - SqmSelectTranslation translate(SqmSelectStatement sqmStatement); - SqmQuerySpecTranslation translate(SqmQuerySpec sqmQuerySpec); -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslation.java index 3b6960e6aa..9ffe08ad71 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslation.java @@ -10,6 +10,8 @@ import java.util.List; import java.util.Map; import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.sql.ast.spi.FromClauseAccess; +import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.ast.tree.expression.JdbcParameter; @@ -18,7 +20,9 @@ import org.hibernate.sql.ast.tree.expression.JdbcParameter; * * @author Steve Ebersole */ -public interface SqmTranslation { - Statement getSqlAst(); +public interface SqmTranslation { + T getSqlAst(); Map> getJdbcParamsBySqmParam(); + SqlExpressionResolver getSqlExpressionResolver(); + FromClauseAccess getFromClauseAccess(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslator.java index 105807fcab..d67a83e24e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslator.java @@ -6,12 +6,14 @@ */ package org.hibernate.query.sqm.sql; -import org.hibernate.query.sqm.tree.cte.SqmCteStatement; -import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.query.sqm.spi.JdbcParameterBySqmParameterAccess; +import org.hibernate.sql.ast.spi.FromClauseAccess; +import org.hibernate.sql.ast.tree.Statement; /** * @author Steve Ebersole */ -public interface SqmTranslator { - CteStatement translate(SqmCteStatement sqmCte); +public interface SqmTranslator + extends SqmToSqlAstConverter, FromClauseAccess, JdbcParameterBySqmParameterAccess { + SqmTranslation translate(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslatorFactory.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslatorFactory.java index cd54f49744..42a3e8ce7c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslatorFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslatorFactory.java @@ -7,44 +7,55 @@ package org.hibernate.query.sqm.sql; import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; +import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; +import org.hibernate.query.sqm.tree.select.SqmSelectStatement; +import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.sql.ast.spi.SqlAstCreationContext; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; +import org.hibernate.sql.ast.tree.insert.InsertStatement; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.ast.tree.update.UpdateStatement; /** * Factory for various * @author Steve Ebersole */ public interface SqmTranslatorFactory { - SqmSelectTranslator createSelectTranslator( - QueryOptions queryOptions, - DomainParameterXref domainParameterXref, - QueryParameterBindings domainParameterBindings, - LoadQueryInfluencers influencers, - SqlAstCreationContext creationContext); - - SimpleSqmDeleteTranslator createSimpleDeleteTranslator( + SqmTranslator createSelectTranslator( + SqmSelectStatement sqmSelectStatement, QueryOptions queryOptions, DomainParameterXref domainParameterXref, QueryParameterBindings domainParameterBindings, LoadQueryInfluencers loadQueryInfluencers, SqlAstCreationContext creationContext); - SqmInsertTranslator createInsertTranslator( + SqmTranslator createSimpleDeleteTranslator( + SqmDeleteStatement sqmDeleteStatement, QueryOptions queryOptions, DomainParameterXref domainParameterXref, QueryParameterBindings domainParameterBindings, - LoadQueryInfluencers influencers, + LoadQueryInfluencers loadQueryInfluencers, SqlAstCreationContext creationContext); - SimpleSqmUpdateTranslator createSimpleUpdateTranslator( + SqmTranslator createInsertTranslator( + SqmInsertStatement sqmInsertStatement, + QueryOptions queryOptions, + DomainParameterXref domainParameterXref, + QueryParameterBindings domainParameterBindings, + LoadQueryInfluencers loadQueryInfluencers, + SqlAstCreationContext creationContext); + + SqmTranslator createSimpleUpdateTranslator( + SqmUpdateStatement sqmUpdateStatement, QueryOptions queryOptions, DomainParameterXref domainParameterXref, QueryParameterBindings queryParameterBindings, LoadQueryInfluencers loadQueryInfluencers, - SessionFactoryImplementor factory); + SqlAstCreationContext creationContext); // todo (6.0) : update, delete, etc converters... diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SimpleSqmDeleteTranslation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/StandardSqmTranslation.java similarity index 82% rename from hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SimpleSqmDeleteTranslation.java rename to hibernate-core/src/main/java/org/hibernate/query/sqm/sql/StandardSqmTranslation.java index 0e52133e7a..d59586fc7f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SimpleSqmDeleteTranslation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/StandardSqmTranslation.java @@ -12,20 +12,21 @@ import java.util.Map; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.spi.SqlExpressionResolver; -import org.hibernate.sql.ast.tree.delete.DeleteStatement; +import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.ast.tree.expression.JdbcParameter; /** - * @author Steve Ebersole + * @author Christian Beikov */ -public class SimpleSqmDeleteTranslation implements SqmTranslation { - private final DeleteStatement sqlAst; +public class StandardSqmTranslation implements SqmTranslation { + + private final T sqlAst; private final Map> jdbcParamMap; private final SqlExpressionResolver sqlExpressionResolver; private final FromClauseAccess fromClauseAccess; - public SimpleSqmDeleteTranslation( - DeleteStatement sqlAst, + public StandardSqmTranslation( + T sqlAst, Map> jdbcParamMap, SqlExpressionResolver sqlExpressionResolver, FromClauseAccess fromClauseAccess) { @@ -36,7 +37,7 @@ public class SimpleSqmDeleteTranslation implements SqmTranslation { } @Override - public DeleteStatement getSqlAst() { + public T getSqlAst() { return sqlAst; } @@ -45,10 +46,12 @@ public class SimpleSqmDeleteTranslation implements SqmTranslation { return jdbcParamMap; } + @Override public SqlExpressionResolver getSqlExpressionResolver() { return sqlExpressionResolver; } + @Override public FromClauseAccess getFromClauseAccess() { return fromClauseAccess; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/StandardSqmTranslatorFactory.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/StandardSqmTranslatorFactory.java index 8673eb69b3..5b26626816 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/StandardSqmTranslatorFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/StandardSqmTranslatorFactory.java @@ -7,15 +7,19 @@ package org.hibernate.query.sqm.sql; import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.sql.internal.StandardSqmDeleteTranslator; -import org.hibernate.query.sqm.sql.internal.StandardSqmInsertTranslator; -import org.hibernate.query.sqm.sql.internal.StandardSqmSelectTranslator; -import org.hibernate.query.sqm.sql.internal.StandardSqmUpdateTranslator; +import org.hibernate.query.sqm.sql.internal.StandardSqmTranslator; +import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; +import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; +import org.hibernate.query.sqm.tree.select.SqmSelectStatement; +import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.sql.ast.spi.SqlAstCreationContext; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; +import org.hibernate.sql.ast.tree.insert.InsertStatement; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.ast.tree.update.UpdateStatement; /** * Standard implementation of the SqmTranslatorFactory @@ -23,66 +27,76 @@ import org.hibernate.sql.ast.spi.SqlAstCreationContext; * @author Steve Ebersole */ public class StandardSqmTranslatorFactory implements SqmTranslatorFactory { + @Override - public SqmSelectTranslator createSelectTranslator( + public SqmTranslator createSelectTranslator( + SqmSelectStatement sqmSelectStatement, QueryOptions queryOptions, DomainParameterXref domainParameterXref, QueryParameterBindings domainParameterBindings, - LoadQueryInfluencers influencers, + LoadQueryInfluencers loadQueryInfluencers, SqlAstCreationContext creationContext) { - return new StandardSqmSelectTranslator( + return new StandardSqmTranslator<>( + sqmSelectStatement, queryOptions, domainParameterXref, domainParameterBindings, - influencers, + loadQueryInfluencers, creationContext ); } @Override - public SimpleSqmDeleteTranslator createSimpleDeleteTranslator( + public SqmTranslator createSimpleDeleteTranslator( + SqmDeleteStatement sqmDeleteStatement, QueryOptions queryOptions, DomainParameterXref domainParameterXref, QueryParameterBindings domainParameterBindings, LoadQueryInfluencers loadQueryInfluencers, SqlAstCreationContext creationContext) { - return new StandardSqmDeleteTranslator( - creationContext, + return new StandardSqmTranslator<>( + sqmDeleteStatement, queryOptions, - loadQueryInfluencers, domainParameterXref, - domainParameterBindings + domainParameterBindings, + loadQueryInfluencers, + creationContext ); } @Override - public SqmInsertTranslator createInsertTranslator( + public SqmTranslator createInsertTranslator( + SqmInsertStatement sqmInsertStatement, QueryOptions queryOptions, DomainParameterXref domainParameterXref, QueryParameterBindings domainParameterBindings, - LoadQueryInfluencers influencers, + LoadQueryInfluencers loadQueryInfluencers, SqlAstCreationContext creationContext) { - return new StandardSqmInsertTranslator( - creationContext, + return new StandardSqmTranslator<>( + sqmInsertStatement, queryOptions, domainParameterXref, - domainParameterBindings + domainParameterBindings, + loadQueryInfluencers, + creationContext ); } @Override - public SimpleSqmUpdateTranslator createSimpleUpdateTranslator( + public SqmTranslator createSimpleUpdateTranslator( + SqmUpdateStatement sqmUpdateStatement, QueryOptions queryOptions, DomainParameterXref domainParameterXref, - QueryParameterBindings queryParameterBindings, + QueryParameterBindings domainParameterBindings, LoadQueryInfluencers loadQueryInfluencers, - SessionFactoryImplementor factory) { - return new StandardSqmUpdateTranslator( - factory, + SqlAstCreationContext creationContext) { + return new StandardSqmTranslator<>( + sqmUpdateStatement, queryOptions, - loadQueryInfluencers, domainParameterXref, - queryParameterBindings + domainParameterBindings, + loadQueryInfluencers, + creationContext ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqlAstQuerySpecProcessingStateImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqlAstQueryPartProcessingStateImpl.java similarity index 77% rename from hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqlAstQuerySpecProcessingStateImpl.java rename to hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqlAstQueryPartProcessingStateImpl.java index 808536ebb1..2d7e8a8f3c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqlAstQuerySpecProcessingStateImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqlAstQueryPartProcessingStateImpl.java @@ -14,10 +14,11 @@ import java.util.function.Supplier; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlAstProcessingState; -import org.hibernate.sql.ast.spi.SqlAstQuerySpecProcessingState; +import org.hibernate.sql.ast.spi.SqlAstQueryPartProcessingState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.spi.TypeConfiguration; @@ -25,38 +26,36 @@ import org.hibernate.type.spi.TypeConfiguration; /** * @author Steve Ebersole */ -public class SqlAstQuerySpecProcessingStateImpl +public class SqlAstQueryPartProcessingStateImpl extends SqlAstProcessingStateImpl - implements SqlAstQuerySpecProcessingState { + implements SqlAstQueryPartProcessingState { - private final QuerySpec querySpec; + private final QueryPart queryPart; - public SqlAstQuerySpecProcessingStateImpl( - QuerySpec querySpec, + public SqlAstQueryPartProcessingStateImpl( + QueryPart queryPart, SqlAstProcessingState parent, SqlAstCreationState creationState, Supplier currentClauseAccess) { super( parent, creationState, currentClauseAccess ); - this.querySpec = querySpec; + this.queryPart = queryPart; } - public SqlAstQuerySpecProcessingStateImpl( - QuerySpec querySpec, + public SqlAstQueryPartProcessingStateImpl( + QueryPart queryPart, SqlAstProcessingState parent, SqlAstCreationState creationState, Function expressionResolverDecorator, Supplier currentClauseAccess) { super( parent, creationState, expressionResolverDecorator, currentClauseAccess ); - this.querySpec = querySpec; + this.queryPart = queryPart; } @Override - public QuerySpec getInflightQuerySpec() { - return querySpec; + public QueryPart getInflightQueryPart() { + return queryPart; } - - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // SqlExpressionResolver private Map sqlSelectionMap; @@ -94,7 +93,7 @@ public class SqlAstQuerySpecProcessingStateImpl sqlSelectionMap.put( expression, sqlSelection ); - querySpec.getSelectClause().addSqlSelection( sqlSelection ); + ( (QuerySpec) queryPart ).getSelectClause().addSqlSelection( sqlSelection ); return sqlSelection; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmDeleteTranslator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmDeleteTranslator.java deleted file mode 100644 index f159f1362a..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmDeleteTranslator.java +++ /dev/null @@ -1,137 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.sql.internal; - -import org.hibernate.HibernateException; -import org.hibernate.LockMode; -import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.FilterHelper; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.persister.entity.Joinable; -import org.hibernate.query.NavigablePath; -import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterBindings; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter; -import org.hibernate.query.sqm.sql.SimpleSqmDeleteTranslation; -import org.hibernate.query.sqm.sql.SimpleSqmDeleteTranslator; -import org.hibernate.query.sqm.tree.cte.SqmCteStatement; -import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; -import org.hibernate.sql.ast.Clause; -import org.hibernate.sql.ast.spi.SqlAstCreationContext; -import org.hibernate.sql.ast.spi.SqlAstTreeHelper; -import org.hibernate.sql.ast.tree.cte.CteStatement; -import org.hibernate.sql.ast.tree.delete.DeleteStatement; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.predicate.FilterPredicate; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.exec.spi.JdbcDelete; - -/** - * @author Steve Ebersole - */ -public class StandardSqmDeleteTranslator - extends BaseSqmToSqlAstConverter - implements SimpleSqmDeleteTranslator { - - public static JdbcDelete translate( - SqmDeleteStatement statement, - SessionFactoryImplementor factory) { - return null; - } - - public StandardSqmDeleteTranslator( - SqlAstCreationContext creationContext, - QueryOptions queryOptions, - LoadQueryInfluencers loadQueryInfluencers, - DomainParameterXref domainParameterXref, - QueryParameterBindings domainParameterBindings) { - super( creationContext, queryOptions, loadQueryInfluencers, domainParameterXref, domainParameterBindings ); - } - - @Override - public SimpleSqmDeleteTranslation translate(SqmDeleteStatement statement) { - SqlAstProcessingStateImpl processingState = new SqlAstProcessingStateImpl( - null, - this, - getCurrentClauseStack()::getCurrent - ); - - getProcessingStateStack().push( processingState ); - - final DeleteStatement deleteStatement = visitDeleteStatement( statement ); - - return new SimpleSqmDeleteTranslation( - deleteStatement, - getJdbcParamsBySqmParam(), - processingState.getSqlExpressionResolver(), - getFromClauseAccess() - ); - } - - @Override - public DeleteStatement visitDeleteStatement(SqmDeleteStatement statement) { - final String entityName = statement.getTarget().getEntityName(); - final EntityPersister entityDescriptor = getCreationContext().getDomainModel().getEntityDescriptor( entityName ); - assert entityDescriptor != null; - - try { - final NavigablePath rootPath = statement.getTarget().getNavigablePath(); - final TableGroup rootTableGroup = entityDescriptor.createRootTableGroup( - rootPath, - statement.getRoot().getAlias(), - false, - LockMode.WRITE, - stem -> getSqlAliasBaseGenerator().createSqlAliasBase( stem ), - getSqlExpressionResolver(), - () -> predicate -> additionalRestrictions = predicate, - getCreationContext() - ); - getFromClauseIndex().registerTableGroup( rootPath, rootTableGroup ); - - if ( ! rootTableGroup.getTableReferenceJoins().isEmpty() ) { - throw new HibernateException( "Not expecting multiple table references for an SQM DELETE" ); - } - - final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate( - getLoadQueryInfluencers(), - (Joinable) entityDescriptor - ); - if ( filterPredicate != null ) { - additionalRestrictions = SqlAstTreeHelper.combinePredicates( additionalRestrictions, filterPredicate ); - } - - Predicate suppliedPredicate = null; - final SqmWhereClause whereClause = statement.getWhereClause(); - if ( whereClause != null && whereClause.getPredicate() != null ) { - getCurrentClauseStack().push( Clause.WHERE ); - try { - suppliedPredicate = (Predicate) whereClause.getPredicate().accept( this ); - } - finally { - getCurrentClauseStack().pop(); - } - } - - return new DeleteStatement( - rootTableGroup.getPrimaryTableReference(), - SqlAstTreeHelper.combinePredicates( suppliedPredicate, additionalRestrictions ) - ); - } - finally { - getProcessingStateStack().pop(); - } - } - - @Override - public CteStatement translate(SqmCteStatement sqmCte) { - visitCteStatement( sqmCte ); - return null; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmInsertTranslator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmInsertTranslator.java deleted file mode 100644 index 748ef5d562..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmInsertTranslator.java +++ /dev/null @@ -1,237 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.sql.internal; - -import java.util.List; - -import org.hibernate.HibernateException; -import org.hibernate.LockMode; -import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.query.NavigablePath; -import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterBindings; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter; -import org.hibernate.query.sqm.sql.SqmInsertTranslation; -import org.hibernate.query.sqm.sql.SqmInsertTranslator; -import org.hibernate.query.sqm.tree.cte.SqmCteStatement; -import org.hibernate.query.sqm.tree.domain.SqmPath; -import org.hibernate.query.sqm.tree.expression.SqmExpression; -import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement; -import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; -import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement; -import org.hibernate.query.sqm.tree.insert.SqmValues; -import org.hibernate.query.sqm.tree.select.SqmQuerySpec; -import org.hibernate.query.sqm.tree.select.SqmSelectStatement; -import org.hibernate.query.sqm.tree.select.SqmSelection; -import org.hibernate.sql.ast.spi.SqlAstCreationContext; -import org.hibernate.sql.ast.tree.cte.CteStatement; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.insert.InsertStatement; -import org.hibernate.sql.ast.tree.insert.Values; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.ast.tree.update.Assignable; -import org.hibernate.sql.results.graph.DomainResult; - -/** - * @author Steve Ebersole - */ -public class StandardSqmInsertTranslator - extends BaseSqmToSqlAstConverter - implements SqmInsertTranslator { - - private final List domainResults = CollectionHelper.arrayList( 10 ); - - public StandardSqmInsertTranslator( - SqlAstCreationContext creationContext, - QueryOptions queryOptions, - DomainParameterXref domainParameterXref, - QueryParameterBindings domainParameterBindings) { - super( creationContext, queryOptions, null, domainParameterXref, domainParameterBindings ); - } - - @Override - public SqmInsertTranslation translate(SqmInsertStatement sqmStatement) { - InsertStatement sqlAst; - if ( sqmStatement instanceof SqmInsertSelectStatement ) { - sqlAst = visitInsertSelectStatement( (SqmInsertSelectStatement) sqmStatement ); - } - else { - sqlAst = visitInsertValuesStatement( (SqmInsertValuesStatement) sqmStatement ); - } - return new SqmInsertTranslation( sqlAst, getJdbcParamsBySqmParam() ); - } - - @Override - public CteStatement translate(SqmCteStatement sqmCte) { - return visitCteStatement( sqmCte ); - } - - @Override - public InsertStatement visitInsertSelectStatement(SqmInsertSelectStatement sqmStatement) { - final InsertStatement insertStatement = new InsertStatement(); - - final String entityName = sqmStatement.getTarget().getEntityName(); - final EntityPersister entityDescriptor = getCreationContext().getDomainModel().getEntityDescriptor( entityName ); - assert entityDescriptor != null; - - SqmQuerySpec selectQuerySpec = sqmStatement.getSelectQuerySpec(); - getProcessingStateStack().push( - new SqlAstProcessingStateImpl( - null, - this, - r -> new SqlSelectionForSqmSelectionCollector( - r, - selectQuerySpec.getSelectClause() - .getSelectionItems() - .size() - ), - getCurrentClauseStack()::getCurrent - ) - ); - - try { - final NavigablePath rootPath = sqmStatement.getTarget().getNavigablePath(); - final TableGroup rootTableGroup = entityDescriptor.createRootTableGroup( - rootPath, - sqmStatement.getTarget().getExplicitAlias(), - false, - LockMode.WRITE, - stem -> getSqlAliasBaseGenerator().createSqlAliasBase( stem ), - getSqlExpressionResolver(), - () -> predicate -> additionalRestrictions = predicate, - getCreationContext() - ); - - if ( ! rootTableGroup.getTableReferenceJoins().isEmpty() - || ! rootTableGroup.getTableGroupJoins().isEmpty() ) { - throw new HibernateException( "Not expecting multiple table references for an SQM INSERT-SELECT" ); - } - - getFromClauseIndex().registerTableGroup( rootPath, rootTableGroup ); - - insertStatement.setTargetTable( rootTableGroup.getPrimaryTableReference() ); - - List targetPaths = sqmStatement.getInsertionTargetPaths(); - for (SqmPath target : targetPaths) { - Assignable assignable = (Assignable) target.accept(this); - insertStatement.addTargetColumnReferences( assignable.getColumnReferences() ); - } - - insertStatement.setSourceSelectStatement( - visitQuerySpec( selectQuerySpec ) - ); - - return insertStatement; - } - finally { - getProcessingStateStack().pop(); - } - } - - @Override - public InsertStatement visitInsertValuesStatement(SqmInsertValuesStatement sqmStatement) { - final InsertStatement insertValuesStatement = new InsertStatement(); - - final String entityName = sqmStatement.getTarget().getEntityName(); - final EntityPersister entityDescriptor = getCreationContext().getDomainModel().getEntityDescriptor( entityName ); - assert entityDescriptor != null; - - getProcessingStateStack().push( - new SqlAstProcessingStateImpl( - null, - this, - getCurrentClauseStack()::getCurrent - ) - ); - - try { - final NavigablePath rootPath = sqmStatement.getTarget().getNavigablePath(); - final TableGroup rootTableGroup = entityDescriptor.createRootTableGroup( - rootPath, - sqmStatement.getTarget().getExplicitAlias(), - false, - LockMode.WRITE, - stem -> getSqlAliasBaseGenerator().createSqlAliasBase( stem ), - getSqlExpressionResolver(), - () -> predicate -> additionalRestrictions = predicate, - getCreationContext() - ); - - if ( ! rootTableGroup.getTableReferenceJoins().isEmpty() - || ! rootTableGroup.getTableGroupJoins().isEmpty() ) { - throw new HibernateException( "Not expecting multiple table references for an SQM INSERT-SELECT" ); - } - - getFromClauseIndex().registerTableGroup( rootPath, rootTableGroup ); - - insertValuesStatement.setTargetTable( rootTableGroup.getPrimaryTableReference() ); - - List targetPaths = sqmStatement.getInsertionTargetPaths(); - for (SqmPath target : targetPaths) { - Assignable assignable = (Assignable) target.accept(this); - insertValuesStatement.addTargetColumnReferences( assignable.getColumnReferences() ); - } - - List valuesList = sqmStatement.getValuesList(); - for ( SqmValues sqmValues : valuesList ) { - insertValuesStatement.getValuesList().add( visitValues( sqmValues ) ); - } - - return insertValuesStatement; - } - finally { - getProcessingStateStack().pop(); - } - } - - private DomainResultProducer resolveDomainResultProducer(SqmSelection sqmSelection) { - return (DomainResultProducer) sqmSelection.getSelectableNode().accept( this ); - } - - @Override - public Void visitSelection(SqmSelection sqmSelection) { - currentSqlSelectionCollector().next(); - final DomainResultProducer resultProducer = resolveDomainResultProducer( sqmSelection ); - -// if ( getProcessingStateStack().depth() > 1 ) { -// resultProducer.applySqlSelections( this ); -// } -// else { - - final DomainResult domainResult = resultProducer.createDomainResult( - sqmSelection.getAlias(), - this - ); - - domainResults.add( domainResult ); -// } - - return null; - } - - @Override - public Values visitValues(SqmValues sqmValues) { - Values values = new Values(); - //noinspection rawtypes - for ( SqmExpression expression : sqmValues.getExpressions() ) { - values.getExpressions().add( (Expression) expression.accept( this ) ); - } - return values; - } - - @Override - public SelectStatement visitSelectStatement(SqmSelectStatement statement) { - final QuerySpec querySpec = visitQuerySpec( statement.getQuerySpec() ); - - return new SelectStatement( querySpec, domainResults ); - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmSelectTranslator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmSelectTranslator.java deleted file mode 100644 index 54a7ac2eb3..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmSelectTranslator.java +++ /dev/null @@ -1,632 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.sql.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.BiConsumer; - -import org.hibernate.HibernateException; -import org.hibernate.LockMode; -import org.hibernate.LockOptions; -import org.hibernate.NotYetImplementedFor6Exception; -import org.hibernate.engine.FetchTiming; -import org.hibernate.engine.profile.FetchProfile; -import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.graph.spi.AppliedGraph; -import org.hibernate.internal.FilterHelper; -import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.internal.util.collections.Stack; -import org.hibernate.internal.util.collections.StandardStack; -import org.hibernate.loader.MultipleBagFetchException; -import org.hibernate.metamodel.CollectionClassification; -import org.hibernate.metamodel.mapping.CollectionPart; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.ModelPart; -import org.hibernate.metamodel.mapping.ModelPartContainer; -import org.hibernate.metamodel.mapping.PluralAttributeMapping; -import org.hibernate.metamodel.mapping.ordering.OrderByFragment; -import org.hibernate.metamodel.model.domain.EntityDomainType; -import org.hibernate.persister.collection.CollectionPersister; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.persister.entity.Joinable; -import org.hibernate.query.DynamicInstantiationNature; -import org.hibernate.query.NavigablePath; -import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterBindings; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter; -import org.hibernate.query.sqm.sql.SqmQuerySpecTranslation; -import org.hibernate.query.sqm.sql.SqmSelectTranslation; -import org.hibernate.query.sqm.sql.SqmSelectTranslator; -import org.hibernate.query.sqm.tree.cte.SqmCteStatement; -import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType; -import org.hibernate.query.sqm.tree.expression.SqmPathEntityType; -import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; -import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation; -import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiationArgument; -import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiationTarget; -import org.hibernate.query.sqm.tree.select.SqmQuerySpec; -import org.hibernate.query.sqm.tree.select.SqmSelectStatement; -import org.hibernate.query.sqm.tree.select.SqmSelection; -import org.hibernate.sql.ast.SqlAstJoinType; -import org.hibernate.sql.ast.spi.FromClauseAccess; -import org.hibernate.sql.ast.spi.SqlAstCreationContext; -import org.hibernate.sql.ast.spi.SqlAstCreationState; -import org.hibernate.sql.ast.tree.cte.CteStatement; -import org.hibernate.sql.ast.tree.expression.EntityTypeLiteral; -import org.hibernate.sql.ast.tree.expression.Expression; -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.FilterPredicate; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.EntityGraphTraversalState; -import org.hibernate.sql.results.graph.Fetch; -import org.hibernate.sql.results.graph.FetchParent; -import org.hibernate.sql.results.graph.Fetchable; -import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; -import org.hibernate.sql.results.graph.instantiation.internal.DynamicInstantiation; -import org.hibernate.sql.results.internal.StandardEntityGraphTraversalStateImpl; -import org.hibernate.type.descriptor.java.JavaTypeDescriptor; - -/** - * Interprets an SqmSelectStatement as a SQL-AST SelectQuery. - * - * @author Steve Ebersole - * @author John O'Hara - * @author Nathan Xu - */ -@SuppressWarnings("unchecked") -public class StandardSqmSelectTranslator - extends BaseSqmToSqlAstConverter - implements SqmSelectTranslator { - - // prepare for 10 root selections to avoid list growth in most cases - private final List domainResults = CollectionHelper.arrayList( 10 ); - - private final EntityGraphTraversalState entityGraphTraversalState; - - private int fetchDepth; - - private Map collectionFilterPredicates; - - public StandardSqmSelectTranslator( - QueryOptions queryOptions, - DomainParameterXref domainParameterXref, - QueryParameterBindings domainParameterBindings, - LoadQueryInfluencers fetchInfluencers, - SqlAstCreationContext creationContext) { - super( creationContext, queryOptions, fetchInfluencers, domainParameterXref, domainParameterBindings ); - - final AppliedGraph appliedGraph = queryOptions.getAppliedGraph(); - if ( appliedGraph != null && appliedGraph.getSemantic() != null && appliedGraph.getGraph() != null ) { - this.entityGraphTraversalState = new StandardEntityGraphTraversalStateImpl( - appliedGraph.getSemantic(), appliedGraph.getGraph() ); - } - else { - this.entityGraphTraversalState = null; - } - } - - @Override - public SqmSelectTranslation translate(SqmSelectStatement sqmStatement) { - return new SqmSelectTranslation( - visitSelectStatement( sqmStatement ), - getJdbcParamsBySqmParam() - ); - } - - @Override - public SqmQuerySpecTranslation translate(SqmQuerySpec querySpec) { - return new SqmQuerySpecTranslation( - visitQuerySpec( querySpec ), - getJdbcParamsBySqmParam() - ); - } - - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // DomainResultCreationState - - @Override - public SqlAstCreationState getSqlAstCreationState() { - return this; - } - - - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // walker - - private final Stack orderByFragmentConsumerStack = new StandardStack<>(); - - 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 ); - } - } - - @Override - public SelectStatement visitSelectStatement(SqmSelectStatement statement) { - final QuerySpec querySpec = visitQuerySpec( statement.getQuerySpec() ); - - return new SelectStatement( querySpec, domainResults ); - } - - @Override - protected void prepareQuerySpec(QuerySpec sqlQuerySpec) { - final boolean topLevel = orderByFragmentConsumerStack.isEmpty(); - if ( topLevel ) { - orderByFragmentConsumerStack.push( new StandardOrderByFragmentConsumer() ); - } - else { - orderByFragmentConsumerStack.push( null ); - } - } - - @Override - protected void postProcessQuerySpec(QuerySpec sqlQuerySpec) { - final List roots = sqlQuerySpec.getFromClause().getRoots(); - if ( roots != null && roots.size() == 1 ) { - final TableGroup root = roots.get( 0 ); - final ModelPartContainer modelPartContainer = root.getModelPart(); - final EntityPersister entityPersister = modelPartContainer.findContainingEntityMapping().getEntityPersister(); - assert entityPersister instanceof Joinable; - final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate( - getLoadQueryInfluencers(), (Joinable) entityPersister, root - ); - if ( filterPredicate != null ) { - sqlQuerySpec.applyPredicate( filterPredicate ); - } - if ( CollectionHelper.isNotEmpty( collectionFilterPredicates ) ) { - root.getTableGroupJoins().forEach( - tableGroupJoin -> { - collectionFilterPredicates.forEach( (alias, predicate) -> { - if ( tableGroupJoin.getJoinedGroup().getGroupAlias().equals( alias ) ) { - tableGroupJoin.applyPredicate( predicate ); - } - } ); - } - ); - } - } - - try { - final OrderByFragmentConsumer orderByFragmentConsumer = orderByFragmentConsumerStack.getCurrent(); - if ( orderByFragmentConsumer != null ) { - orderByFragmentConsumer.visitFragments( - (orderByFragment, tableGroup) -> { - orderByFragment.apply( sqlQuerySpec, tableGroup, this ); - } - ); - } - } - finally { - orderByFragmentConsumerStack.pop(); - } - } - - @Override - public Void visitSelection(SqmSelection sqmSelection) { - currentSqlSelectionCollector().next(); - final DomainResultProducer resultProducer = resolveDomainResultProducer( sqmSelection ); - - if ( getProcessingStateStack().depth() > 1 ) { - resultProducer.applySqlSelections( this ); - } - else { - - final DomainResult domainResult = resultProducer.createDomainResult( - sqmSelection.getAlias(), - this - ); - - domainResults.add( domainResult ); - } - - return null; - } - - private DomainResultProducer resolveDomainResultProducer(SqmSelection sqmSelection) { - return (DomainResultProducer) sqmSelection.getSelectableNode().accept( this ); - } - - @Override - public ModelPart resolveModelPart(NavigablePath navigablePath) { - // again, assume that the path refers to a TableGroup - return getFromClauseIndex().findTableGroup( navigablePath ).getModelPart(); - } - - @Override - public List visitFetches(FetchParent fetchParent) { - final List fetches = CollectionHelper.arrayList( fetchParent.getReferencedMappingType().getNumberOfFetchables() ); - final List bagRoles = new ArrayList<>(); - - final BiConsumer fetchableBiConsumer = (fetchable, isKeyFetchable) -> { - final NavigablePath fetchablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() ); - - final Fetch biDirectionalFetch = fetchable.resolveCircularFetch( - fetchablePath, - fetchParent, - StandardSqmSelectTranslator.this - ); - - if ( biDirectionalFetch != null ) { - fetches.add( biDirectionalFetch ); - return; - } - - final boolean incrementFetchDepth = fetchable.incrementFetchDepth(); - try { - if ( incrementFetchDepth ) { - fetchDepth++; - } - final Fetch fetch = buildFetch( fetchablePath, fetchParent, fetchable, isKeyFetchable ); - - 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() ); - } - } - - fetches.add( fetch ); - } - } - finally { - if ( incrementFetchDepth ) { - fetchDepth--; - } - } - }; - -// 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 ); - } - return fetches; - } - - private Fetch buildFetch(NavigablePath fetchablePath, FetchParent fetchParent, Fetchable fetchable, boolean isKeyFetchable) { - // fetch has access to its parent in addition to the parent having its fetches. - // - // we could sever the parent -> fetch link ... it would not be "seen" while walking - // but it would still have access to its parent info - and be able to access its - // "initializing" state as part of AfterLoadAction - - final String alias; - LockMode lockMode = LockMode.READ; - FetchTiming fetchTiming = fetchable.getMappedFetchOptions().getTiming(); - boolean joined = false; - - EntityGraphTraversalState.TraversalResult traversalResult = null; - - final SqmAttributeJoin fetchedJoin = getFromClauseIndex().findFetchedJoinByPath( fetchablePath ); - - if ( fetchedJoin != null ) { - // there was an explicit fetch in the SQM - // there should be a TableGroupJoin registered for this `fetchablePath` already - // because it - assert getFromClauseIndex().getTableGroup( fetchablePath ) != null; - -// - if ( fetchedJoin.isFetched() ) { - fetchTiming = FetchTiming.IMMEDIATE; - } - joined = true; - alias = fetchedJoin.getExplicitAlias(); - lockMode = determineLockMode( alias ); - } - else { - // 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.getFetchStrategy(); - joined = traversalResult.isJoined(); - } - else if ( getLoadQueryInfluencers().hasEnabledFetchProfiles() ) { - if ( fetchParent instanceof EntityResultGraphNode ) { - final EntityResultGraphNode entityFetchParent = (EntityResultGraphNode) fetchParent; - final EntityMappingType entityMappingType = entityFetchParent.getEntityValuedModelPart() - .getEntityMappingType(); - final String fetchParentEntityName = entityMappingType.getEntityName(); - final String fetchableRole = fetchParentEntityName + "." + fetchable.getFetchableName(); - - for ( String enabledFetchProfileName : getLoadQueryInfluencers().getEnabledFetchProfileNames() ) { - final FetchProfile enabledFetchProfile = getCreationContext().getSessionFactory() - .getFetchProfile( enabledFetchProfileName ); - final org.hibernate.engine.profile.Fetch profileFetch = enabledFetchProfile.getFetchByRole( - fetchableRole ); - - fetchTiming = FetchTiming.IMMEDIATE; - joined = joined || profileFetch.getStyle() == org.hibernate.engine.profile.Fetch.Style.JOIN; - } - } - } - } - - final TableGroup existingJoinedGroup = getFromClauseIndex().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; - } - - // 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 ) { - getFromClauseIndex().resolveTableGroup( - fetchablePath, - np -> { - // generate the join - final TableGroup lhs = getFromClauseIndex().getTableGroup( fetchParent.getNavigablePath() ); - final TableGroupJoin tableGroupJoin = ( (TableGroupJoinProducer) fetchable ).createTableGroupJoin( - fetchablePath, - lhs, - alias, - SqlAstJoinType.LEFT, - LockMode.NONE, - this - ); - return tableGroupJoin.getJoinedGroup(); - } - ); - - } - } - - try { - final Fetch fetch = fetchable.generateFetch( - fetchParent, - fetchablePath, - fetchTiming, - joined, - lockMode, - alias, - StandardSqmSelectTranslator.this - ); - - if ( fetchable instanceof PluralAttributeMapping && fetch.getTiming() == FetchTiming.IMMEDIATE && joined ) { - final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable; - - final Joinable joinable = pluralAttributeMapping - .getCollectionDescriptor() - .getCollectionType() - .getAssociatedJoinable( getCreationContext().getSessionFactory() ); - final TableGroup tableGroup = getFromClauseAccess().getTableGroup( fetchablePath ); - final FilterPredicate collectionFieldFilterPredicate = FilterHelper.createFilterPredicate( - getLoadQueryInfluencers(), - joinable, - tableGroup - ); - if ( collectionFieldFilterPredicate != null ) { - if ( collectionFilterPredicates == null ) { - collectionFilterPredicates = new HashMap<>(); - } - collectionFilterPredicates.put( tableGroup.getGroupAlias(),collectionFieldFilterPredicate ); - } - if ( pluralAttributeMapping.getCollectionDescriptor().isManyToMany() ) { - assert joinable instanceof CollectionPersister; - final Predicate manyToManyFilterPredicate = FilterHelper.createManyToManyFilterPredicate( - getLoadQueryInfluencers(), - ( CollectionPersister) joinable, - tableGroup - ); - if ( manyToManyFilterPredicate != null ) { - assert tableGroup.getTableReferenceJoins() != null && - tableGroup.getTableReferenceJoins().size() == 1; - tableGroup.getTableReferenceJoins().get( 0 ).applyPredicate( manyToManyFilterPredicate ); - } - } - - final OrderByFragmentConsumer orderByFragmentConsumer = orderByFragmentConsumerStack.getCurrent(); - if ( orderByFragmentConsumer != null ) { - - assert tableGroup.getModelPart() == pluralAttributeMapping; - - if ( pluralAttributeMapping.getOrderByFragment() != null ) { - orderByFragmentConsumer.accept( pluralAttributeMapping.getOrderByFragment(), tableGroup ); - } - - if ( pluralAttributeMapping.getManyToManyOrderByFragment() != null ) { - orderByFragmentConsumer.accept( pluralAttributeMapping.getManyToManyOrderByFragment(), tableGroup ); - } - } - } - - return fetch; - } - catch (RuntimeException e) { - throw new HibernateException( - String.format( - Locale.ROOT, - "Could not generate fetch : %s -> %s", - fetchParent.getNavigablePath(), - fetchable.getFetchableName() - ), - e - ); - } - finally { - if ( entityGraphTraversalState != null && traversalResult != null ) { - entityGraphTraversalState.backtrack( traversalResult.getPreviousContext() ); - } - } - } - - @Override - public FromClauseAccess getFromClauseAccess() { - return getFromClauseIndex(); - } - - @Override - public LockMode determineLockMode(String identificationVariable) { - final LockOptions lockOptions = getQueryOptions().getLockOptions(); - return lockOptions.getScope() || identificationVariable == null - ? lockOptions.getLockMode() - : lockOptions.getEffectiveLockMode( identificationVariable ); - } - - @Override - public DynamicInstantiation visitDynamicInstantiation(SqmDynamicInstantiation sqmDynamicInstantiation) { - final SqmDynamicInstantiationTarget instantiationTarget = sqmDynamicInstantiation.getInstantiationTarget(); - final DynamicInstantiationNature instantiationNature = instantiationTarget.getNature(); - final JavaTypeDescriptor targetTypeDescriptor = interpretInstantiationTarget( instantiationTarget ); - - final DynamicInstantiation dynamicInstantiation = new DynamicInstantiation( - instantiationNature, - targetTypeDescriptor - ); - - for ( SqmDynamicInstantiationArgument sqmArgument : sqmDynamicInstantiation.getArguments() ) { - final DomainResultProducer argumentResultProducer = (DomainResultProducer) sqmArgument.getSelectableNode().accept( this ); - dynamicInstantiation.addArgument( - sqmArgument.getAlias(), - argumentResultProducer - ); - } - - dynamicInstantiation.complete(); - - return dynamicInstantiation; - } - - @SuppressWarnings("unchecked") - private JavaTypeDescriptor interpretInstantiationTarget(SqmDynamicInstantiationTarget instantiationTarget) { - final Class targetJavaType; - - if ( instantiationTarget.getNature() == DynamicInstantiationNature.LIST ) { - targetJavaType = (Class) List.class; - } - else if ( instantiationTarget.getNature() == DynamicInstantiationNature.MAP ) { - targetJavaType = (Class) Map.class; - } - else { - targetJavaType = instantiationTarget.getJavaType(); - } - - return getCreationContext().getDomainModel() - .getTypeConfiguration() - .getJavaTypeDescriptorRegistry() - .getDescriptor( targetJavaType ); - } - - @Override - public Expression visitEntityTypeLiteralExpression(SqmLiteralEntityType sqmExpression) { - final EntityDomainType nodeType = sqmExpression.getNodeType(); - final EntityPersister mappingDescriptor = getCreationContext().getDomainModel().getEntityDescriptor( nodeType.getHibernateEntityName() ); - - return new EntityTypeLiteral( mappingDescriptor ); - } - - @Override - public Expression visitSqmPathEntityTypeExpression(SqmPathEntityType sqmExpression) { - return BasicValuedPathInterpretation.from( - sqmExpression, - this, - this - ); - } - - @Override - public Object visitFullyQualifiedClass(Class namedClass) { - throw new NotYetImplementedFor6Exception(); - - // what exactly is the expected end result here? - -// final MetamodelImplementor metamodel = getSessionFactory().getMetamodel(); -// final TypeConfiguration typeConfiguration = getSessionFactory().getTypeConfiguration(); -// -// // see if it is an entity-type -// final EntityTypeDescriptor entityDescriptor = metamodel.findEntityDescriptor( namedClass ); -// if ( entityDescriptor != null ) { -// throw new NotYetImplementedFor6Exception( "Add support for entity type literals as SqlExpression" ); -// } -// -// -// final JavaTypeDescriptor jtd = typeConfiguration -// .getJavaTypeDescriptorRegistry() -// .getOrMakeJavaDescriptor( namedClass ); - } - - @Override - public CteStatement translate(SqmCteStatement sqmCte) { - return visitCteStatement( sqmCte ); - } - - - // @Override -// public SqlSelection resolveSqlSelection(Expression expression) { -// return sqlSelectionByExpressionMap.get( expression ); -// } - - // @Override -// public DomainReferenceExpression visitAttributeReferenceExpression(AttributeBinding attributeBinding) { -// if ( attributeBinding instanceof PluralAttributeBinding ) { -// return getCurrentDomainReferenceExpressionBuilder().buildPluralAttributeExpression( -// this, -// (PluralAttributeBinding) attributeBinding -// ); -// } -// else { -// return getCurrentDomainReferenceExpressionBuilder().buildSingularAttributeExpression( -// this, -// (SingularAttributeBinding) attributeBinding -// ); -// } -// } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmTranslator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmTranslator.java new file mode 100644 index 0000000000..45ace5b5aa --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmTranslator.java @@ -0,0 +1,35 @@ +/* + * 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.sql.internal; + +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryParameterBindings; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter; +import org.hibernate.query.sqm.sql.SqmTranslation; +import org.hibernate.query.sqm.tree.SqmStatement; +import org.hibernate.sql.ast.spi.SqlAstCreationContext; +import org.hibernate.sql.ast.tree.Statement; + +/** + * The standard translator for SQM to SQL ASTs. + * + * @author Christian Beikov + */ +public class StandardSqmTranslator extends BaseSqmToSqlAstConverter { + + public StandardSqmTranslator( + SqmStatement statement, + QueryOptions queryOptions, + DomainParameterXref domainParameterXref, + QueryParameterBindings domainParameterBindings, + LoadQueryInfluencers fetchInfluencers, + SqlAstCreationContext creationContext) { + super( creationContext, statement, queryOptions, fetchInfluencers, domainParameterXref, domainParameterBindings ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmUpdateTranslator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmUpdateTranslator.java deleted file mode 100644 index 3b97f02c47..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmUpdateTranslator.java +++ /dev/null @@ -1,293 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.sql.internal; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; - -import org.hibernate.HibernateException; -import org.hibernate.LockMode; -import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.internal.FilterHelper; -import org.hibernate.metamodel.MappingMetamodel; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.persister.entity.Joinable; -import org.hibernate.query.NavigablePath; -import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterBindings; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter; -import org.hibernate.query.sqm.sql.SimpleSqmUpdateTranslation; -import org.hibernate.query.sqm.sql.SimpleSqmUpdateTranslator; -import org.hibernate.query.sqm.tree.cte.SqmCteStatement; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; -import org.hibernate.query.sqm.tree.update.SqmAssignment; -import org.hibernate.query.sqm.tree.update.SqmSetClause; -import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.sql.ast.Clause; -import org.hibernate.sql.ast.SqlTreeCreationLogger; -import org.hibernate.sql.ast.spi.SqlAliasBase; -import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; -import org.hibernate.sql.ast.spi.SqlAstCreationContext; -import org.hibernate.sql.ast.spi.SqlAstProcessingState; -import org.hibernate.sql.ast.spi.SqlAstTreeHelper; -import org.hibernate.sql.ast.tree.cte.CteStatement; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.predicate.FilterPredicate; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.ast.tree.update.UpdateStatement; -import org.hibernate.sql.exec.internal.JdbcParameterImpl; -import org.hibernate.type.spi.TypeConfiguration; - -/** - * @author Steve Ebersole - */ -public class StandardSqmUpdateTranslator - extends BaseSqmToSqlAstConverter - implements SimpleSqmUpdateTranslator { - - public StandardSqmUpdateTranslator( - SqlAstCreationContext creationContext, - QueryOptions queryOptions, - LoadQueryInfluencers loadQueryInfluencers, - DomainParameterXref domainParameterXref, - QueryParameterBindings domainParameterBindings) { - super( creationContext, queryOptions, loadQueryInfluencers, domainParameterXref, domainParameterBindings ); - } - - @Override - public CteStatement translate(SqmCteStatement sqmCte) { - return visitCteStatement( sqmCte ); - } - - @Override - public SimpleSqmUpdateTranslation translate(SqmUpdateStatement sqmUpdate) { - final UpdateStatement sqlUpdateAst = visitUpdateStatement( sqmUpdate ); - return new SimpleSqmUpdateTranslation( - sqlUpdateAst, - getJdbcParamsBySqmParam() - ); - } - - @Override - public UpdateStatement visitUpdateStatement(SqmUpdateStatement sqmStatement) { - final String entityName = sqmStatement.getTarget().getEntityName(); - final EntityPersister entityDescriptor = getCreationContext().getDomainModel().getEntityDescriptor( entityName ); - assert entityDescriptor != null; - - getProcessingStateStack().push( - new SqlAstProcessingStateImpl( - null, - this, - getCurrentClauseStack()::getCurrent - ) - ); - - try { - final NavigablePath rootPath = sqmStatement.getTarget().getNavigablePath(); - final TableGroup rootTableGroup = entityDescriptor.createRootTableGroup( - rootPath, - sqmStatement.getRoot().getAlias(), - false, - LockMode.WRITE, - getSqlAliasBaseGenerator(), - getSqlExpressionResolver(), - () -> predicate -> additionalRestrictions = predicate, - getCreationContext() - ); - - if ( ! rootTableGroup.getTableReferenceJoins().isEmpty() ) { - throw new HibernateException( "Not expecting multiple table references for an SQM DELETE" ); - } - - getFromClauseIndex().registerTableGroup( rootPath, rootTableGroup ); - - final List assignments = visitSetClause( sqmStatement.getSetClause() ); - - final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate( - getLoadQueryInfluencers(), - (Joinable) entityDescriptor - ); - if ( filterPredicate != null ) { - additionalRestrictions = SqlAstTreeHelper.combinePredicates( additionalRestrictions, filterPredicate ); - } - - Predicate suppliedPredicate = null; - final SqmWhereClause whereClause = sqmStatement.getWhereClause(); - if ( whereClause != null && whereClause.getPredicate() != null ) { - getCurrentClauseStack().push( Clause.WHERE ); - try { - suppliedPredicate = (Predicate) whereClause.getPredicate().accept( this ); - } - finally { - getCurrentClauseStack().pop(); - } - } - - return new UpdateStatement( - rootTableGroup.getPrimaryTableReference(), - assignments, - SqlAstTreeHelper.combinePredicates( suppliedPredicate, additionalRestrictions ) - ); - } - finally { - getProcessingStateStack().pop(); - } - } - - - @Override - public SqlAliasBaseGenerator getSqlAliasBaseGenerator() { - return SQL_ALIAS_BASE_GENERATOR; - } - - private static final SqlAliasBaseGenerator SQL_ALIAS_BASE_GENERATOR = new SqlAliasBaseGenerator() { - private final SqlAliasBase sqlAliasBase = new SqlAliasBase() { - @Override - public String getAliasStem() { - return null; - } - - @Override - public String generateNewAlias() { - return null; - } - }; - - @Override - public SqlAliasBase createSqlAliasBase(String stem) { - return sqlAliasBase; - } - }; - - @Override - public List visitSetClause(SqmSetClause setClause) { - final List assignments = new ArrayList<>(); - - for ( SqmAssignment sqmAssignment : setClause.getAssignments() ) { - final List targetColumnReferences = new ArrayList<>(); - - getProcessingStateStack().push( - new SqlAstProcessingStateImpl( - getProcessingStateStack().getCurrent(), - this, - getCurrentClauseStack()::getCurrent - ) { - @Override - public Expression resolveSqlExpression( - String key, - Function creator) { - final Expression expression = getParentState().getSqlExpressionResolver().resolveSqlExpression( key, creator ); - assert expression instanceof ColumnReference; - - targetColumnReferences.add( (ColumnReference) expression ); - - return expression; - } - } - ); - - final SqmPathInterpretation assignedPathInterpretation; - try { - assignedPathInterpretation = (SqmPathInterpretation) sqmAssignment.getTargetPath().accept( this ); - } - finally { - getProcessingStateStack().pop(); - } - - inferableTypeAccessStack.push( assignedPathInterpretation::getExpressionType ); - - final List valueColumnReferences = new ArrayList<>(); - getProcessingStateStack().push( - new SqlAstProcessingStateImpl( - getProcessingStateStack().getCurrent(), - this, - getCurrentClauseStack()::getCurrent - ) { - @Override - public Expression resolveSqlExpression( - String key, - Function creator) { - final Expression expression = getParentState().getSqlExpressionResolver().resolveSqlExpression( key, creator ); - assert expression instanceof ColumnReference; - - valueColumnReferences.add( (ColumnReference) expression ); - - return expression; - } - } - ); - - try { - - if ( sqmAssignment.getValue() instanceof SqmParameter ) { - final SqmParameter sqmParameter = (SqmParameter) sqmAssignment.getValue(); - final List jdbcParametersForSqm = new ArrayList<>(); - - // create one JdbcParameter for each column in the assigned path - assignedPathInterpretation.getExpressionType().forEachSelection( - (columnIndex, selection) -> { - final JdbcParameter jdbcParameter = new JdbcParameterImpl( selection.getJdbcMapping() ); - jdbcParametersForSqm.add( jdbcParameter ); - assignments.add( - new Assignment( - new ColumnReference( - // we do not want a qualifier (table alias) here - (String) null, - selection, - getCreationContext().getSessionFactory() - ), - jdbcParameter - ) - ); - } - ); - - getJdbcParamsBySqmParam().put( sqmParameter, jdbcParametersForSqm ); - } - else { - final MappingMetamodel domainModel = getCreationContext().getDomainModel(); - final TypeConfiguration typeConfiguration = domainModel.getTypeConfiguration(); - - final Expression valueExpression = (Expression) sqmAssignment.getValue().accept( this ); - - final int valueExprJdbcCount = valueExpression.getExpressionType().getJdbcTypeCount(); - final int assignedPathJdbcCount = assignedPathInterpretation.getExpressionType().getJdbcTypeCount(); - - if ( valueExprJdbcCount != assignedPathJdbcCount ) { - SqlTreeCreationLogger.LOGGER.debugf( - "JDBC type count does not match in UPDATE assignment between the assigned-path and the assigned-value; " + - "this will likely lead to problems executing the query" - ); - } - - assert assignedPathJdbcCount == valueExprJdbcCount; - - for (ColumnReference columnReference : targetColumnReferences) { - assignments.add( - new Assignment( columnReference, valueExpression ) - ); - } - } - } - finally { - getProcessingStateStack().pop(); - inferableTypeAccessStack.pop(); - } - - } - - return assignments; - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java index a15e13e75b..54ae7b8e20 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java @@ -6,8 +6,13 @@ */ package org.hibernate.query.sqm.tree; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmQuerySource; +import org.hibernate.query.sqm.tree.cte.SqmCteStatement; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.select.SqmSubQuery; @@ -17,6 +22,8 @@ import org.hibernate.query.sqm.tree.select.SqmSubQuery; public abstract class AbstractSqmDmlStatement extends AbstractSqmStatement implements SqmDmlStatement { + private final Map> cteStatements = new LinkedHashMap<>(); + private boolean withRecursiveCte; private SqmRoot target; public AbstractSqmDmlStatement(SqmQuerySource querySource, NodeBuilder nodeBuilder) { @@ -28,6 +35,33 @@ public abstract class AbstractSqmDmlStatement this.target = target; } + @Override + public boolean isWithRecursive() { + return withRecursiveCte; + } + + @Override + public void setWithRecursive(boolean withRecursiveCte) { + this.withRecursiveCte = withRecursiveCte; + } + + @Override + public Collection> getCteStatements() { + return cteStatements.values(); + } + + @Override + public SqmCteStatement getCteStatement(String cteLabel) { + return cteStatements.get( cteLabel ); + } + + @Override + public void addCteStatement(SqmCteStatement cteStatement) { + if ( cteStatements.putIfAbsent( cteStatement.getCteTable().getCteName(), cteStatement ) != null ) { + throw new IllegalArgumentException( "A CTE with the label " + cteStatement.getCteTable().getCteName() + " already exists!" ); + } + } + @Override public SqmRoot getTarget() { return target; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmDmlStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmDmlStatement.java index 27649c5973..b9d9287a6b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmDmlStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmDmlStatement.java @@ -7,7 +7,7 @@ package org.hibernate.query.sqm.tree; import org.hibernate.query.criteria.JpaManipulationCriteria; -import org.hibernate.query.sqm.tree.cte.SqmCteConsumer; +import org.hibernate.query.sqm.tree.cte.SqmCteContainer; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -19,7 +19,7 @@ import org.hibernate.query.sqm.tree.from.SqmRoot; * * @author Steve Ebersole */ -public interface SqmDmlStatement extends SqmStatement, SqmCteConsumer, JpaManipulationCriteria { +public interface SqmDmlStatement extends SqmStatement, SqmCteContainer, JpaManipulationCriteria { /** * Get the root path that is the target of the DML statement. */ diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteConsumer.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteConsumer.java deleted file mode 100644 index 4af6c6c0b3..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteConsumer.java +++ /dev/null @@ -1,15 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.tree.cte; - -import org.hibernate.query.sqm.tree.SqmNode; - -/** - * @author Steve Ebersole - */ -public interface SqmCteConsumer extends SqmNode { -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteContainer.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteContainer.java new file mode 100644 index 0000000000..7aa630735f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteContainer.java @@ -0,0 +1,27 @@ +/* + * 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.tree.cte; + +import java.util.Collection; + +import org.hibernate.query.sqm.tree.SqmNode; + +/** + * @author Christian Beikov + */ +public interface SqmCteContainer extends SqmNode { + + boolean isWithRecursive(); + + void setWithRecursive(boolean recursive); + + Collection> getCteStatements(); + + SqmCteStatement getCteStatement(String cteLabel); + + void addCteStatement(SqmCteStatement cteStatement); +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteStatement.java index 382c0b8d79..1511c5e5da 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteStatement.java @@ -6,67 +6,97 @@ */ package org.hibernate.query.sqm.tree.cte; -import org.hibernate.query.criteria.JpaPredicate; -import org.hibernate.query.criteria.JpaSubQuery; +import java.util.List; + +import org.hibernate.CteSearchClauseKind; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; -import org.hibernate.query.sqm.SqmQuerySource; -import org.hibernate.query.sqm.tree.AbstractSqmStatement; +import org.hibernate.query.sqm.tree.AbstractSqmNode; import org.hibernate.query.sqm.tree.SqmStatement; -import org.hibernate.query.sqm.tree.select.SqmQuerySpec; -import org.hibernate.query.sqm.tree.select.SqmSubQuery; +import org.hibernate.query.sqm.tree.SqmVisitableNode; +import org.hibernate.query.sqm.tree.select.SqmQueryPart; /** * @author Steve Ebersole + * @author Christian Beikov */ -public class SqmCteStatement extends AbstractSqmStatement implements SqmStatement { +public class SqmCteStatement extends AbstractSqmNode implements SqmVisitableNode { + private final SqmCteContainer cteContainer; private final SqmCteTable cteTable; - private final String cteLabel; - private final SqmQuerySpec cteDefinition; - private final SqmCteConsumer cteConsumer; + private final SqmStatement cteDefinition; + private final CteSearchClauseKind searchClauseKind; + private final List searchBySpecifications; + private final List cycleColumns; + private final SqmCteTableColumn cycleMarkColumn; + private final char cycleValue; + private final char noCycleValue; public SqmCteStatement( SqmCteTable cteTable, - String cteLabel, - SqmQuerySpec cteDefinition, - SqmCteConsumer cteConsumer, - SqmQuerySource querySource, + SqmStatement cteDefinition, NodeBuilder nodeBuilder) { - super( querySource, nodeBuilder ); + super( nodeBuilder ); this.cteTable = cteTable; - this.cteLabel = cteLabel; this.cteDefinition = cteDefinition; - this.cteConsumer = cteConsumer; + this.cteContainer = null; + this.searchClauseKind = null; + this.searchBySpecifications = null; + this.cycleColumns = null; + this.cycleMarkColumn = null; + this.cycleValue = '\0'; + this.noCycleValue = '\0'; + } + + public SqmCteStatement( + SqmCteTable cteTable, + SqmStatement cteDefinition, + SqmCteContainer cteContainer) { + super( cteContainer.nodeBuilder() ); + this.cteTable = cteTable; + this.cteDefinition = cteDefinition; + this.cteContainer = cteContainer; + this.searchClauseKind = null; + this.searchBySpecifications = null; + this.cycleColumns = null; + this.cycleMarkColumn = null; + this.cycleValue = '\0'; + this.noCycleValue = '\0'; } public SqmCteTable getCteTable() { return cteTable; } - public String getCteLabel() { - return cteLabel; - } - - public SqmQuerySpec getCteDefinition() { + public SqmStatement getCteDefinition() { return cteDefinition; } - public SqmCteConsumer getCteConsumer() { - return cteConsumer; + public SqmCteContainer getCteContainer() { + return cteContainer; } - @Override - public JpaSubQuery subquery(Class type) { - return new SqmSubQuery<>( - this, - new SqmQuerySpec<>( nodeBuilder() ), - nodeBuilder() - ); + public CteSearchClauseKind getSearchClauseKind() { + return searchClauseKind; } - @Override - public JpaPredicate getRestriction() { - return null; + public List getSearchBySpecifications() { + return searchBySpecifications; + } + + public List getCycleColumns() { + return cycleColumns; + } + + public SqmCteTableColumn getCycleMarkColumn() { + return cycleMarkColumn; + } + + public char getCycleValue() { + return cycleValue; + } + + public char getNoCycleValue() { + return noCycleValue; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteTable.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteTable.java index 914e72f9cf..60e76cc395 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteTable.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteTable.java @@ -6,16 +6,40 @@ */ package org.hibernate.query.sqm.tree.cte; +import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping; +import org.hibernate.sql.ast.tree.cte.CteColumn; + /** * @author Steve Ebersole + * @author Christian Beikov */ public class SqmCteTable { private final String cteName; private final List columns; + public SqmCteTable(String cteName, EntityMappingType entityDescriptor) { + final int numberOfColumns = entityDescriptor.getIdentifierMapping().getJdbcTypeCount(); + final List columns = new ArrayList<>( numberOfColumns ); + final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); + final String idName; + if ( identifierMapping instanceof SingleAttributeIdentifierMapping ) { + idName = ( (SingleAttributeIdentifierMapping) identifierMapping ).getAttributeName(); + } + else { + idName = "id"; + } + columns.add( new SqmCteTableColumn( this, idName, identifierMapping ) ); + this.cteName = cteName; + this.columns = columns; + } + public SqmCteTable(String cteName, List columns) { this.cteName = cteName; this.columns = columns; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteTableColumn.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteTableColumn.java index 3221957c18..a3a27cc686 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteTableColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteTableColumn.java @@ -6,26 +6,24 @@ */ package org.hibernate.query.sqm.tree.cte; -import org.hibernate.type.BasicType; +import org.hibernate.metamodel.mapping.ModelPart; /** * @author Steve Ebersole + * @author Christian Beikov */ public class SqmCteTableColumn { private final SqmCteTable cteTable; private final String columnName; - private final BasicType typeExpressable; - private final boolean allowNulls; + private final ModelPart typeExpressable; public SqmCteTableColumn( SqmCteTable cteTable, String columnName, - BasicType typeExpressable, - boolean allowNulls) { + ModelPart typeExpressable) { this.cteTable = cteTable; this.columnName = columnName; this.typeExpressable = typeExpressable; - this.allowNulls = allowNulls; } public SqmCteTable getCteTable() { @@ -36,11 +34,8 @@ public class SqmCteTableColumn { return columnName; } - public BasicType getType() { + public ModelPart getType() { return typeExpressable; } - public boolean isAllowNulls() { - return allowNulls; - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmSearchClauseSpecification.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmSearchClauseSpecification.java new file mode 100644 index 0000000000..e8450c3f3e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmSearchClauseSpecification.java @@ -0,0 +1,37 @@ +/* + * 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.tree.cte; + +import org.hibernate.NullPrecedence; +import org.hibernate.SortOrder; + +/** + * @author Christian Beikov + */ +public class SqmSearchClauseSpecification { + private final SqmCteTableColumn cteColumn; + private final SortOrder sortOrder; + private final NullPrecedence nullPrecedence; + + public SqmSearchClauseSpecification(SqmCteTableColumn cteColumn, SortOrder sortOrder, NullPrecedence nullPrecedence) { + this.cteColumn = cteColumn; + this.sortOrder = sortOrder; + this.nullPrecedence = nullPrecedence; + } + + public SqmCteTableColumn getCteColumn() { + return cteColumn; + } + + public SortOrder getSortOrder() { + return sortOrder; + } + + public NullPrecedence getNullPrecedence() { + return nullPrecedence; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/delete/SqmDeleteStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/delete/SqmDeleteStatement.java index 7ea85c30fb..083f35b1fd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/delete/SqmDeleteStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/delete/SqmDeleteStatement.java @@ -19,7 +19,6 @@ import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmQuerySource; import org.hibernate.query.sqm.tree.AbstractSqmDmlStatement; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; -import org.hibernate.query.sqm.tree.cte.SqmCteConsumer; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; @@ -29,7 +28,7 @@ import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; */ public class SqmDeleteStatement extends AbstractSqmDmlStatement - implements SqmDeleteOrUpdateStatement, SqmCteConsumer, JpaCriteriaDelete { + implements SqmDeleteOrUpdateStatement, JpaCriteriaDelete { private final SqmQuerySource querySource; private SqmWhereClause whereClause; 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 3eef229246..24513c8935 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 @@ -21,6 +21,7 @@ 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.SqmFrom; +import org.hibernate.query.sqm.tree.from.SqmJoin; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; @@ -154,4 +155,5 @@ public abstract class AbstractSqmAttributeJoin public JoinType getJoinType() { return getSqmJoinType().getCorrespondingJpaJoinType(); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmCorrelatedFrom.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmCorrelatedFrom.java deleted file mode 100644 index 86b821f5d4..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmCorrelatedFrom.java +++ /dev/null @@ -1,47 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query.sqm.tree.domain; - -import org.hibernate.query.sqm.NodeBuilder; -import org.hibernate.query.sqm.tree.from.SqmFrom; - -/** - * @author Steve Ebersole - */ -public abstract class AbstractSqmCorrelatedFrom - extends AbstractSqmFrom - implements SqmPathWrapper, SqmFrom { - private SqmFrom correlationParent; - - public AbstractSqmCorrelatedFrom( - SqmFrom correlationParent, - NodeBuilder criteriaBuilder) { - super( - correlationParent.getNavigablePath(), - correlationParent.getReferencedPathSource(), - (SqmFrom) correlationParent.getLhs(), - null, - criteriaBuilder - ); - this.correlationParent = correlationParent; - } - - @Override - public SqmFrom getCorrelationParent() { - return correlationParent; - } - - @Override - public SqmPath getWrappedPath() { - return getCorrelationParent(); - } - - @Override - public boolean isCorrelated() { - return true; - } -} 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 4ecebfb9ab..f0119141ce 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 @@ -23,7 +23,6 @@ import javax.persistence.metamodel.PluralAttribute; import javax.persistence.metamodel.SetAttribute; import javax.persistence.metamodel.SingularAttribute; -import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.metamodel.model.domain.BagPersistentAttribute; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.ListPersistentAttribute; @@ -34,7 +33,6 @@ import org.hibernate.metamodel.model.domain.SetPersistentAttribute; import org.hibernate.metamodel.model.domain.SingularPersistentAttribute; import org.hibernate.query.NavigablePath; import org.hibernate.query.criteria.JpaPath; -import org.hibernate.query.criteria.JpaSubQuery; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.SemanticException; import org.hibernate.query.sqm.SqmPathSource; @@ -57,7 +55,6 @@ import org.hibernate.query.sqm.tree.from.SqmRoot; public abstract class AbstractSqmFrom extends AbstractSqmPath implements SqmFrom { private String alias; - private SqmFrom correlationParent; private List> joins; protected AbstractSqmFrom( @@ -91,6 +88,16 @@ public abstract class AbstractSqmFrom extends AbstractSqmPath implements this.alias = alias; } + /** + * Intended for use with {@link SqmCorrelatedRootJoin} through {@link SqmRoot} + */ + protected AbstractSqmFrom( + NavigablePath navigablePath, + SqmPathSource referencedNavigable, + NodeBuilder nodeBuilder) { + super( navigablePath, referencedNavigable, null, nodeBuilder ); + } + @Override public String getExplicitAlias() { return alias; @@ -157,19 +164,16 @@ public abstract class AbstractSqmFrom extends AbstractSqmPath implements @Override public SqmFrom getCorrelationParent() { - return correlationParent; + throw new IllegalStateException( "Not correlated" ); } + public abstract SqmCorrelation createCorrelation(); + @Override public boolean isCorrelated() { return false; } - @Override - public SqmFrom correlateTo(JpaSubQuery subquery) { - throw new NotYetImplementedFor6Exception(); - } - @Override @SuppressWarnings("unchecked") public Set> getJoins() { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java index 6b491980ed..9685803e07 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java @@ -20,7 +20,6 @@ import org.hibernate.query.PathException; import org.hibernate.query.criteria.JpaCollectionJoin; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaPredicate; -import org.hibernate.query.criteria.JpaSubQuery; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.sqm.tree.SqmJoinType; @@ -117,8 +116,8 @@ public class SqmBagJoin extends AbstractSqmPluralJoin, E> } @Override - public SqmBagJoin correlateTo(JpaSubQuery subquery) { - return (SqmBagJoin) super.correlateTo( subquery ); + public SqmCorrelatedBagJoin createCorrelation() { + return new SqmCorrelatedBagJoin<>( this ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java new file mode 100644 index 0000000000..2b6bae0e20 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java @@ -0,0 +1,87 @@ +/* + * 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.tree.domain; + +import org.hibernate.metamodel.model.domain.BagPersistentAttribute; +import org.hibernate.query.hql.spi.SqmCreationProcessingState; +import org.hibernate.query.hql.spi.SqmPathRegistry; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmJoinType; +import org.hibernate.query.sqm.tree.from.SqmFrom; +import org.hibernate.query.sqm.tree.from.SqmRoot; + +/** + * @author Christian Beikov + */ +public class SqmCorrelatedBagJoin extends SqmBagJoin implements SqmCorrelation { + + private final SqmCorrelatedRootJoin correlatedRootJoin; + private final SqmBagJoin correlationParent; + + public SqmCorrelatedBagJoin(SqmBagJoin correlationParent) { + super( + correlationParent.getLhs(), + correlationParent.getAttribute(), + null, + SqmJoinType.INNER, + false, + correlationParent.nodeBuilder() + ); + this.correlatedRootJoin = SqmCorrelatedRootJoin.create( correlationParent, this ); + this.correlationParent = correlationParent; + } + + private SqmCorrelatedBagJoin( + SqmFrom lhs, + BagPersistentAttribute attribute, + String alias, + SqmJoinType sqmJoinType, + boolean fetched, + NodeBuilder nodeBuilder, + SqmCorrelatedRootJoin correlatedRootJoin, + SqmBagJoin correlationParent) { + super( lhs, attribute, alias, sqmJoinType, fetched, nodeBuilder ); + this.correlatedRootJoin = correlatedRootJoin; + this.correlationParent = correlationParent; + } + + @Override + public SqmBagJoin getCorrelationParent() { + return correlationParent; + } + + @Override + public SqmPath getWrappedPath() { + return correlationParent; + } + + @Override + public boolean isCorrelated() { + return true; + } + + @Override + public SqmRoot getCorrelatedRoot() { + return correlatedRootJoin; + } + + @Override + public SqmCorrelatedBagJoin makeCopy(SqmCreationProcessingState creationProcessingState) { + final SqmPathRegistry pathRegistry = creationProcessingState.getPathRegistry(); + //noinspection unchecked + return new SqmCorrelatedBagJoin<>( + pathRegistry.findFromByPath( getLhs().getNavigablePath() ), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + isFetched(), + nodeBuilder(), + (SqmCorrelatedRootJoin) pathRegistry.findFromByPath( correlatedRootJoin.getNavigablePath() ), + (SqmBagJoin) pathRegistry.findFromByPath( correlationParent.getNavigablePath() ) + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java new file mode 100644 index 0000000000..e376c6ae50 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java @@ -0,0 +1,76 @@ +/* + * 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.tree.domain; + +import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.query.hql.spi.SqmCreationProcessingState; +import org.hibernate.query.hql.spi.SqmPathRegistry; +import org.hibernate.query.sqm.tree.from.SqmCrossJoin; +import org.hibernate.query.sqm.tree.from.SqmRoot; + +/** + * @author Christian Beikov + */ +public class SqmCorrelatedCrossJoin extends SqmCrossJoin implements SqmCorrelation { + + private final SqmCorrelatedRootJoin correlatedRootJoin; + private final SqmCrossJoin correlationParent; + + public SqmCorrelatedCrossJoin(SqmCrossJoin correlationParent) { + super( + correlationParent.getReferencedPathSource(), + null, + correlationParent.getRoot() + ); + this.correlatedRootJoin = SqmCorrelatedRootJoin.create( correlationParent, this ); + this.correlationParent = correlationParent; + } + + private SqmCorrelatedCrossJoin( + EntityDomainType joinedEntityDescriptor, + String alias, + SqmRoot sqmRoot, + SqmCorrelatedRootJoin correlatedRootJoin, + SqmCrossJoin correlationParent) { + super( joinedEntityDescriptor, alias, sqmRoot ); + this.correlatedRootJoin = correlatedRootJoin; + this.correlationParent = correlationParent; + } + + @Override + public SqmCrossJoin getCorrelationParent() { + return correlationParent; + } + + @Override + public SqmPath getWrappedPath() { + return correlationParent; + } + + @Override + public boolean isCorrelated() { + return true; + } + + @Override + public SqmRoot getCorrelatedRoot() { + return correlatedRootJoin; + } + + @Override + public SqmCorrelatedCrossJoin makeCopy(SqmCreationProcessingState creationProcessingState) { + final SqmPathRegistry pathRegistry = creationProcessingState.getPathRegistry(); + //noinspection unchecked + return new SqmCorrelatedCrossJoin<>( + getReferencedPathSource(), + getExplicitAlias(), + (SqmRoot) pathRegistry.findFromByPath( getRoot().getNavigablePath() ), + (SqmCorrelatedRootJoin) pathRegistry.findFromByPath( correlatedRootJoin.getNavigablePath() ), + (SqmCrossJoin) pathRegistry.findFromByPath( correlationParent.getNavigablePath() ) + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java new file mode 100644 index 0000000000..5b59243fbe --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java @@ -0,0 +1,80 @@ +/* + * 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.tree.domain; + +import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.query.hql.spi.SqmCreationProcessingState; +import org.hibernate.query.hql.spi.SqmPathRegistry; +import org.hibernate.query.sqm.tree.SqmJoinType; +import org.hibernate.query.sqm.tree.from.SqmEntityJoin; +import org.hibernate.query.sqm.tree.from.SqmRoot; + +/** + * @author Christian Beikov + */ +public class SqmCorrelatedEntityJoin extends SqmEntityJoin implements SqmCorrelation { + + private final SqmCorrelatedRootJoin correlatedRootJoin; + private final SqmEntityJoin correlationParent; + + public SqmCorrelatedEntityJoin(SqmEntityJoin correlationParent) { + super( + correlationParent.getReferencedPathSource(), + null, + SqmJoinType.INNER, + correlationParent.getRoot() + ); + this.correlatedRootJoin = SqmCorrelatedRootJoin.create( correlationParent, this ); + this.correlationParent = correlationParent; + } + + public SqmCorrelatedEntityJoin( + EntityDomainType joinedEntityDescriptor, + String alias, + SqmJoinType joinType, + SqmRoot sqmRoot, + SqmCorrelatedRootJoin correlatedRootJoin, + SqmEntityJoin correlationParent) { + super( joinedEntityDescriptor, alias, joinType, sqmRoot ); + this.correlatedRootJoin = correlatedRootJoin; + this.correlationParent = correlationParent; + } + + @Override + public SqmEntityJoin getCorrelationParent() { + return correlationParent; + } + + @Override + public SqmPath getWrappedPath() { + return correlationParent; + } + + @Override + public boolean isCorrelated() { + return true; + } + + @Override + public SqmRoot getCorrelatedRoot() { + return correlatedRootJoin; + } + + @Override + public SqmCorrelatedEntityJoin makeCopy(SqmCreationProcessingState creationProcessingState) { + final SqmPathRegistry pathRegistry = creationProcessingState.getPathRegistry(); + //noinspection unchecked + return new SqmCorrelatedEntityJoin<>( + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + (SqmRoot) pathRegistry.findFromByPath( getRoot().getNavigablePath() ), + (SqmCorrelatedRootJoin) pathRegistry.findFromByPath( correlatedRootJoin.getNavigablePath() ), + (SqmEntityJoin) pathRegistry.findFromByPath( correlationParent.getNavigablePath() ) + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java new file mode 100644 index 0000000000..efbf429e1c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java @@ -0,0 +1,87 @@ +/* + * 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.tree.domain; + +import org.hibernate.metamodel.model.domain.ListPersistentAttribute; +import org.hibernate.query.hql.spi.SqmCreationProcessingState; +import org.hibernate.query.hql.spi.SqmPathRegistry; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmJoinType; +import org.hibernate.query.sqm.tree.from.SqmFrom; +import org.hibernate.query.sqm.tree.from.SqmRoot; + +/** + * @author Christian Beikov + */ +public class SqmCorrelatedListJoin extends SqmListJoin implements SqmCorrelation { + + private final SqmCorrelatedRootJoin correlatedRootJoin; + private final SqmListJoin correlationParent; + + public SqmCorrelatedListJoin(SqmListJoin correlationParent) { + super( + correlationParent.getLhs(), + correlationParent.getAttribute(), + null, + SqmJoinType.INNER, + false, + correlationParent.nodeBuilder() + ); + this.correlatedRootJoin = SqmCorrelatedRootJoin.create( correlationParent, this ); + this.correlationParent = correlationParent; + } + + private SqmCorrelatedListJoin( + SqmFrom lhs, + ListPersistentAttribute attribute, + String alias, + SqmJoinType sqmJoinType, + boolean fetched, + NodeBuilder nodeBuilder, + SqmCorrelatedRootJoin correlatedRootJoin, + SqmListJoin correlationParent) { + super( lhs, attribute, alias, sqmJoinType, fetched, nodeBuilder ); + this.correlatedRootJoin = correlatedRootJoin; + this.correlationParent = correlationParent; + } + + @Override + public SqmListJoin getCorrelationParent() { + return correlationParent; + } + + @Override + public SqmPath getWrappedPath() { + return correlationParent; + } + + @Override + public boolean isCorrelated() { + return true; + } + + @Override + public SqmRoot getCorrelatedRoot() { + return correlatedRootJoin; + } + + @Override + public SqmCorrelatedListJoin makeCopy(SqmCreationProcessingState creationProcessingState) { + final SqmPathRegistry pathRegistry = creationProcessingState.getPathRegistry(); + //noinspection unchecked + return new SqmCorrelatedListJoin<>( + pathRegistry.findFromByPath( getLhs().getNavigablePath() ), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + isFetched(), + nodeBuilder(), + (SqmCorrelatedRootJoin) pathRegistry.findFromByPath( correlatedRootJoin.getNavigablePath() ), + (SqmListJoin) pathRegistry.findFromByPath( correlationParent.getNavigablePath() ) + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java new file mode 100644 index 0000000000..958606ab35 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java @@ -0,0 +1,87 @@ +/* + * 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.tree.domain; + +import org.hibernate.metamodel.model.domain.MapPersistentAttribute; +import org.hibernate.query.hql.spi.SqmCreationProcessingState; +import org.hibernate.query.hql.spi.SqmPathRegistry; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmJoinType; +import org.hibernate.query.sqm.tree.from.SqmFrom; +import org.hibernate.query.sqm.tree.from.SqmRoot; + +/** + * @author Christian Beikov + */ +public class SqmCorrelatedMapJoin extends SqmMapJoin implements SqmCorrelation { + + private final SqmCorrelatedRootJoin correlatedRootJoin; + private final SqmMapJoin correlationParent; + + public SqmCorrelatedMapJoin(SqmMapJoin correlationParent) { + super( + correlationParent.getLhs(), + correlationParent.getAttribute(), + null, + SqmJoinType.INNER, + false, + correlationParent.nodeBuilder() + ); + this.correlatedRootJoin = SqmCorrelatedRootJoin.create( correlationParent, this ); + this.correlationParent = correlationParent; + } + + private SqmCorrelatedMapJoin( + SqmFrom lhs, + MapPersistentAttribute attribute, + String alias, + SqmJoinType sqmJoinType, + boolean fetched, + NodeBuilder nodeBuilder, + SqmCorrelatedRootJoin correlatedRootJoin, + SqmMapJoin correlationParent) { + super( lhs, attribute, alias, sqmJoinType, fetched, nodeBuilder ); + this.correlatedRootJoin = correlatedRootJoin; + this.correlationParent = correlationParent; + } + + @Override + public SqmMapJoin getCorrelationParent() { + return correlationParent; + } + + @Override + public SqmPath getWrappedPath() { + return correlationParent; + } + + @Override + public boolean isCorrelated() { + return true; + } + + @Override + public SqmRoot getCorrelatedRoot() { + return correlatedRootJoin; + } + + @Override + public SqmCorrelatedMapJoin makeCopy(SqmCreationProcessingState creationProcessingState) { + final SqmPathRegistry pathRegistry = creationProcessingState.getPathRegistry(); + //noinspection unchecked + return new SqmCorrelatedMapJoin<>( + pathRegistry.findFromByPath( getLhs().getNavigablePath() ), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + isFetched(), + nodeBuilder(), + (SqmCorrelatedRootJoin) pathRegistry.findFromByPath( correlatedRootJoin.getNavigablePath() ), + (SqmMapJoin) pathRegistry.findFromByPath( correlationParent.getNavigablePath() ) + ); + } +} 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 869ab34be5..9cf91922de 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,23 +6,18 @@ */ package org.hibernate.query.sqm.tree.domain; -import org.hibernate.query.criteria.JpaSubQuery; -import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; -import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmRoot; -import org.hibernate.query.sqm.tree.select.SqmSubQuery; /** * @author Steve Ebersole */ public class SqmCorrelatedRoot extends SqmRoot implements SqmPathWrapper, SqmCorrelation { - private SqmRoot correlationParent; - public SqmCorrelatedRoot( - SqmRoot correlationParent, - NodeBuilder nodeBuilder) { - super( correlationParent.getReferencedPathSource(), correlationParent.getAlias(), nodeBuilder ); + private final SqmRoot correlationParent; + + public SqmCorrelatedRoot(SqmRoot correlationParent) { + super( correlationParent.getReferencedPathSource(), null, correlationParent.nodeBuilder() ); this.correlationParent = correlationParent; } @@ -42,11 +37,8 @@ public class SqmCorrelatedRoot extends SqmRoot implements SqmPathWrapper correlateTo(JpaSubQuery subquery) { - final SqmSubQuery sqmSubQuery = (SqmSubQuery) subquery; - final SqmCorrelatedRoot correlation = new SqmCorrelatedRoot<>( this, nodeBuilder() ); - sqmSubQuery.getQuerySpec().getFromClause().addRoot( correlation ); - return correlation; + public SqmRoot getCorrelatedRoot() { + return this; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java new file mode 100644 index 0000000000..a6780ec9e4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java @@ -0,0 +1,80 @@ +/* + * 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.tree.domain; + +import org.hibernate.query.NavigablePath; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.from.SqmFrom; +import org.hibernate.query.sqm.tree.from.SqmJoin; +import org.hibernate.query.sqm.tree.from.SqmRoot; + +/** + * @author Steve Ebersole + */ +public class SqmCorrelatedRootJoin extends SqmRoot implements SqmCorrelation { + + private final SqmJoin correlationParent; + + public SqmCorrelatedRootJoin( + NavigablePath navigablePath, + SqmPathSource referencedNavigable, + NodeBuilder nodeBuilder, + SqmJoin correlationParent) { + super( navigablePath, referencedNavigable, nodeBuilder ); + this.correlationParent = correlationParent; + } + + public static > SqmCorrelatedRootJoin create(J correlationParent, J correlatedJoin) { + final SqmFrom parentPath = (SqmFrom) correlationParent.getParentPath(); + final SqmCorrelatedRootJoin rootJoin; + if ( parentPath == null ) { + rootJoin = new SqmCorrelatedRootJoin<>( + null, + null, + correlationParent.nodeBuilder(), + correlationParent + ); + } + else { + rootJoin = new SqmCorrelatedRootJoin<>( + parentPath.getNavigablePath(), + parentPath.getReferencedPathSource(), + correlationParent.nodeBuilder(), + correlationParent + ); + } + rootJoin.addSqmJoin( correlatedJoin ); + return rootJoin; + } + + @Override + public SqmRoot getCorrelationParent() { + return null; + } + + @Override + public SqmPath getWrappedPath() { + return getCorrelationParent(); + } + + @Override + public boolean isCorrelated() { + return true; + } + + @Override + public SqmRoot getCorrelatedRoot() { + return this; + } + + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitCorrelation( this ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java new file mode 100644 index 0000000000..b99e89a062 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java @@ -0,0 +1,87 @@ +/* + * 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.tree.domain; + +import org.hibernate.metamodel.model.domain.SetPersistentAttribute; +import org.hibernate.query.hql.spi.SqmCreationProcessingState; +import org.hibernate.query.hql.spi.SqmPathRegistry; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmJoinType; +import org.hibernate.query.sqm.tree.from.SqmFrom; +import org.hibernate.query.sqm.tree.from.SqmRoot; + +/** + * @author Christian Beikov + */ +public class SqmCorrelatedSetJoin extends SqmSetJoin implements SqmCorrelation { + + private final SqmCorrelatedRootJoin correlatedRootJoin; + private final SqmSetJoin correlationParent; + + public SqmCorrelatedSetJoin(SqmSetJoin correlationParent) { + super( + correlationParent.getLhs(), + correlationParent.getAttribute(), + null, + SqmJoinType.INNER, + false, + correlationParent.nodeBuilder() + ); + this.correlatedRootJoin = SqmCorrelatedRootJoin.create( correlationParent, this ); + this.correlationParent = correlationParent; + } + + private SqmCorrelatedSetJoin( + SqmFrom lhs, + SetPersistentAttribute attribute, + String alias, + SqmJoinType sqmJoinType, + boolean fetched, + NodeBuilder nodeBuilder, + SqmCorrelatedRootJoin correlatedRootJoin, + SqmSetJoin correlationParent) { + super( lhs, attribute, alias, sqmJoinType, fetched, nodeBuilder ); + this.correlatedRootJoin = correlatedRootJoin; + this.correlationParent = correlationParent; + } + + @Override + public SqmSetJoin getCorrelationParent() { + return correlationParent; + } + + @Override + public SqmPath getWrappedPath() { + return correlationParent; + } + + @Override + public boolean isCorrelated() { + return true; + } + + @Override + public SqmRoot getCorrelatedRoot() { + return correlatedRootJoin; + } + + @Override + public SqmCorrelatedSetJoin makeCopy(SqmCreationProcessingState creationProcessingState) { + final SqmPathRegistry pathRegistry = creationProcessingState.getPathRegistry(); + //noinspection unchecked + return new SqmCorrelatedSetJoin<>( + pathRegistry.findFromByPath( getLhs().getNavigablePath() ), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + isFetched(), + nodeBuilder(), + (SqmCorrelatedRootJoin) pathRegistry.findFromByPath( correlatedRootJoin.getNavigablePath() ), + (SqmSetJoin) pathRegistry.findFromByPath( correlationParent.getNavigablePath() ) + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java new file mode 100644 index 0000000000..d3ba4a4eb3 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java @@ -0,0 +1,87 @@ +/* + * 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.tree.domain; + +import org.hibernate.metamodel.model.domain.SingularPersistentAttribute; +import org.hibernate.query.hql.spi.SqmCreationProcessingState; +import org.hibernate.query.hql.spi.SqmPathRegistry; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmJoinType; +import org.hibernate.query.sqm.tree.from.SqmFrom; +import org.hibernate.query.sqm.tree.from.SqmRoot; + +/** + * @author Christian Beikov + */ +public class SqmCorrelatedSingularJoin extends SqmSingularJoin implements SqmCorrelation { + + private final SqmCorrelatedRootJoin correlatedRootJoin; + private final SqmSingularJoin correlationParent; + + public SqmCorrelatedSingularJoin(SqmSingularJoin correlationParent) { + super( + correlationParent.getLhs(), + correlationParent.getAttribute(), + null, + SqmJoinType.INNER, + false, + correlationParent.nodeBuilder() + ); + this.correlatedRootJoin = SqmCorrelatedRootJoin.create( correlationParent, this ); + this.correlationParent = correlationParent; + } + + private SqmCorrelatedSingularJoin( + SqmFrom lhs, + SingularPersistentAttribute joinedNavigable, + String alias, + SqmJoinType joinType, + boolean fetched, + NodeBuilder nodeBuilder, + SqmCorrelatedRootJoin correlatedRootJoin, + SqmSingularJoin correlationParent) { + super( lhs, joinedNavigable, alias, joinType, fetched, nodeBuilder ); + this.correlatedRootJoin = correlatedRootJoin; + this.correlationParent = correlationParent; + } + + @Override + public SqmSingularJoin getCorrelationParent() { + return correlationParent; + } + + @Override + public SqmPath getWrappedPath() { + return correlationParent; + } + + @Override + public boolean isCorrelated() { + return true; + } + + @Override + public SqmRoot getCorrelatedRoot() { + return correlatedRootJoin; + } + + @Override + public SqmCorrelatedSingularJoin makeCopy(SqmCreationProcessingState creationProcessingState) { + final SqmPathRegistry pathRegistry = creationProcessingState.getPathRegistry(); + //noinspection unchecked + return new SqmCorrelatedSingularJoin<>( + pathRegistry.findFromByPath( getLhs().getNavigablePath() ), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + isFetched(), + nodeBuilder(), + (SqmCorrelatedRootJoin) pathRegistry.findFromByPath( correlatedRootJoin.getNavigablePath() ), + (SqmSingularJoin) pathRegistry.findFromByPath( correlationParent.getNavigablePath() ) + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelation.java index 3e33ab3a44..ee24b8c713 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelation.java @@ -7,6 +7,7 @@ package org.hibernate.query.sqm.tree.domain; import org.hibernate.query.sqm.tree.from.SqmFrom; +import org.hibernate.query.sqm.tree.from.SqmRoot; /** * Specialization of {@link SqmFrom} for sub-query correlations @@ -14,5 +15,6 @@ import org.hibernate.query.sqm.tree.from.SqmFrom; * @see org.hibernate.query.criteria.JpaSubQuery#correlate * @author Steve Ebersole */ -public interface SqmCorrelation extends SqmFrom, SqmPathWrapper { +public interface SqmCorrelation extends SqmFrom, SqmPathWrapper { + SqmRoot getCorrelatedRoot(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java index 28e6e9ce2c..9b98c594ad 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java @@ -21,7 +21,6 @@ import org.hibernate.query.PathException; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaListJoin; import org.hibernate.query.criteria.JpaPredicate; -import org.hibernate.query.criteria.JpaSubQuery; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.sqm.tree.SqmJoinType; @@ -45,11 +44,6 @@ public class SqmListJoin super( lhs, listAttribute, alias, sqmJoinType, fetched, nodeBuilder ); } - @Override - public ListPersistentAttribute getModel() { - return (ListPersistentAttribute) super.getModel(); - } - @Override public ListPersistentAttribute getReferencedPathSource() { //noinspection unchecked @@ -61,6 +55,17 @@ public class SqmListJoin return getNodeJavaTypeDescriptor(); } + @Override + public ListPersistentAttribute getModel() { + return getReferencedPathSource(); + } + + @Override + public ListPersistentAttribute getAttribute() { + //noinspection unchecked + return (ListPersistentAttribute) super.getAttribute(); + } + @Override public SqmPath index() { final String navigableName = "{index}"; @@ -96,8 +101,8 @@ public class SqmListJoin } @Override - public SqmListJoin correlateTo(JpaSubQuery subquery) { - return (SqmListJoin) super.correlateTo( subquery ); + public SqmCorrelatedListJoin createCorrelation() { + return new SqmCorrelatedListJoin<>( this ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java index b8bff082d5..b0f3d6c7cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java @@ -20,7 +20,6 @@ import org.hibernate.query.PathException; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaMapJoin; import org.hibernate.query.criteria.JpaPredicate; -import org.hibernate.query.criteria.JpaSubQuery; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.hql.spi.SqmCreationProcessingState; @@ -32,12 +31,12 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; /** * @author Steve Ebersole */ -public class SqmMapJoin - extends AbstractSqmPluralJoin,V> - implements JpaMapJoin { +public class SqmMapJoin + extends AbstractSqmPluralJoin, V> + implements JpaMapJoin { public SqmMapJoin( SqmFrom lhs, - MapPersistentAttribute pluralValuedNavigable, + MapPersistentAttribute pluralValuedNavigable, String alias, SqmJoinType sqmJoinType, boolean fetched, @@ -46,7 +45,7 @@ public class SqmMapJoin } @Override - public MapPersistentAttribute getReferencedPathSource() { + public MapPersistentAttribute getReferencedPathSource() { //noinspection unchecked return(MapPersistentAttribute) super.getReferencedPathSource(); } @@ -57,10 +56,16 @@ public class SqmMapJoin } @Override - public MapPersistentAttribute getModel() { + public MapPersistentAttribute getModel() { return (MapPersistentAttribute) super.getModel(); } + @Override + public MapPersistentAttribute getAttribute() { + //noinspection unchecked + return (MapPersistentAttribute) super.getAttribute(); + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // JPA @@ -144,37 +149,37 @@ public class SqmMapJoin } @Override - public SqmMapJoin on(JpaExpression restriction) { + public SqmMapJoin on(JpaExpression restriction) { return (SqmMapJoin) super.on( restriction ); } @Override - public SqmMapJoin on(Expression restriction) { + public SqmMapJoin on(Expression restriction) { return (SqmMapJoin) super.on( restriction ); } @Override - public SqmMapJoin on(JpaPredicate... restrictions) { + public SqmMapJoin on(JpaPredicate... restrictions) { return (SqmMapJoin) super.on( restrictions ); } @Override - public SqmMapJoin on(Predicate... restrictions) { + public SqmMapJoin on(Predicate... restrictions) { return (SqmMapJoin) super.on( restrictions ); } @Override - public SqmMapJoin correlateTo(JpaSubQuery subquery) { - return (SqmMapJoin) super.correlateTo( subquery ); + public SqmCorrelatedMapJoin createCorrelation() { + return new SqmCorrelatedMapJoin<>( this ); } @Override - public SqmTreatedMapJoin treatAs(Class treatJavaType) throws PathException { + public SqmTreatedMapJoin treatAs(Class treatJavaType) throws PathException { return treatAs( nodeBuilder().getDomainModel().entity( treatJavaType ) ); } @Override - public SqmTreatedMapJoin treatAs(EntityDomainType treatTarget) throws PathException { + public SqmTreatedMapJoin treatAs(EntityDomainType treatTarget) throws PathException { //noinspection unchecked return new SqmTreatedMapJoin( this, treatTarget, null ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java index 0031dc11cc..95df70839b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java @@ -20,9 +20,8 @@ import org.hibernate.query.PathException; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaPredicate; import org.hibernate.query.criteria.JpaSetJoin; -import org.hibernate.query.criteria.JpaSubQuery; -import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.hql.spi.SqmCreationProcessingState; +import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmFrom; @@ -86,8 +85,8 @@ public class SqmSetJoin } @Override - public SqmSetJoin correlateTo(JpaSubQuery subquery) { - return (SqmSetJoin) super.correlateTo( subquery ); + public SqmCorrelatedSetJoin createCorrelation() { + return new SqmCorrelatedSetJoin<>( this ); } @Override @@ -108,27 +107,27 @@ public class SqmSetJoin } @Override - public SqmSingularJoin fetch(SingularAttribute attribute, JoinType jt) { + public SqmSingularJoin fetch(SingularAttribute attribute, JoinType jt) { throw new NotYetImplementedFor6Exception(); } @Override - public SqmAttributeJoin fetch(PluralAttribute attribute) { + public SqmAttributeJoin fetch(PluralAttribute attribute) { throw new NotYetImplementedFor6Exception(); } @Override - public SqmAttributeJoin fetch(PluralAttribute attribute, JoinType jt) { + public SqmAttributeJoin fetch(PluralAttribute attribute, JoinType jt) { throw new NotYetImplementedFor6Exception(); } @Override - public SqmAttributeJoin fetch(String attributeName) { + public SqmAttributeJoin fetch(String attributeName) { throw new NotYetImplementedFor6Exception(); } @Override - public SqmAttributeJoin fetch(String attributeName, JoinType jt) { + public SqmAttributeJoin fetch(String attributeName, JoinType jt) { throw new NotYetImplementedFor6Exception(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java index 358926a957..fa0d61c969 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java @@ -43,6 +43,17 @@ public class SqmSingularJoin extends AbstractSqmAttributeJoin implemen return getNodeJavaTypeDescriptor(); } + @Override + public SingularPersistentAttribute getModel() { + return getReferencedPathSource(); + } + + @Override + public SingularPersistentAttribute getAttribute() { + //noinspection unchecked + return (SingularPersistentAttribute) super.getAttribute(); + } + @Override public SqmTreatedSingularJoin treatAs(Class treatJavaType) throws PathException { return treatAs( nodeBuilder().getDomainModel().entity( treatJavaType ) ); @@ -54,6 +65,11 @@ public class SqmSingularJoin extends AbstractSqmAttributeJoin implemen return new SqmTreatedSingularJoin( this, treatTarget, null ); } + @Override + public SqmCorrelatedSingularJoin createCorrelation() { + return new SqmCorrelatedSingularJoin<>( this ); + } + @Override public String toString() { return String.format( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedCrossJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedCrossJoin.java index ca07ae38ba..f792af1a40 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedCrossJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedCrossJoin.java @@ -12,7 +12,7 @@ import org.hibernate.query.sqm.tree.from.SqmCrossJoin; /** * @author Steve Ebersole */ -public class SqmTreatedCrossJoin extends SqmCrossJoin implements SqmTreatedPath { +public class SqmTreatedCrossJoin extends SqmCrossJoin implements SqmTreatedPath { private final SqmCrossJoin wrappedPath; private final EntityDomainType treatTarget; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCrossJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCrossJoin.java index 1a21c4182b..5e086e8950 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCrossJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCrossJoin.java @@ -8,9 +8,12 @@ package org.hibernate.query.sqm.tree.from; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.PathException; +import org.hibernate.query.hql.spi.SqmCreationProcessingState; +import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.domain.AbstractSqmFrom; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedCrossJoin; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmTreatedCrossJoin; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; @@ -20,7 +23,7 @@ import static org.hibernate.query.sqm.spi.SqmCreationHelper.buildRootNavigablePa /** * @author Steve Ebersole */ -public class SqmCrossJoin extends AbstractSqmFrom implements SqmJoin { +public class SqmCrossJoin extends AbstractSqmFrom implements SqmJoin { private final SqmRoot sqmRoot; public SqmCrossJoin( @@ -76,8 +79,12 @@ public class SqmCrossJoin extends AbstractSqmFrom implements SqmJoin createCorrelation() { + return new SqmCorrelatedCrossJoin<>( this ); + } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // JPA @Override @@ -89,4 +96,13 @@ public class SqmCrossJoin extends AbstractSqmFrom implements SqmJoin SqmTreatedCrossJoin treatAs(EntityDomainType treatTarget) throws PathException { return new SqmTreatedCrossJoin<>( this, null, treatTarget ); } + + public SqmCrossJoin makeCopy(SqmCreationProcessingState creationProcessingState) { + final SqmPathRegistry pathRegistry = creationProcessingState.getPathRegistry(); + return new SqmCrossJoin<>( + getReferencedPathSource(), + getExplicitAlias(), + (SqmRoot) pathRegistry.findFromByPath( getRoot().getNavigablePath() ) + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java index d9753f816d..6e3d00b1d1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java @@ -9,11 +9,14 @@ package org.hibernate.query.sqm.tree.from; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.PathException; import org.hibernate.query.criteria.JpaEntityJoin; +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.SemanticQueryWalker; import org.hibernate.query.sqm.spi.SqmCreationHelper; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.domain.AbstractSqmJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedEntityJoin; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmTreatedEntityJoin; import org.hibernate.query.sqm.tree.expression.SqmExpression; @@ -22,7 +25,7 @@ import org.hibernate.query.sqm.tree.predicate.SqmPredicate; /** * @author Steve Ebersole */ -public class SqmEntityJoin extends AbstractSqmJoin implements SqmQualifiedJoin, JpaEntityJoin { +public class SqmEntityJoin extends AbstractSqmJoin implements SqmQualifiedJoin, JpaEntityJoin { private final SqmRoot sqmRoot; private SqmPredicate joinPredicate; @@ -105,4 +108,19 @@ public class SqmEntityJoin extends AbstractSqmJoin implements SqmQualifi public SqmTreatedEntityJoin treatAs(EntityDomainType treatTarget) throws PathException { return new SqmTreatedEntityJoin<>( this, treatTarget, null, getSqmJoinType() ); } + + @Override + public SqmCorrelatedEntityJoin createCorrelation() { + return new SqmCorrelatedEntityJoin<>( this ); + } + + public SqmEntityJoin makeCopy(SqmCreationProcessingState creationProcessingState) { + final SqmPathRegistry pathRegistry = creationProcessingState.getPathRegistry(); + return new SqmEntityJoin<>( + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + (SqmRoot) pathRegistry.findFromByPath( getRoot().getNavigablePath() ) + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java index eb640dd54c..9b15998f05 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java @@ -22,7 +22,7 @@ import org.hibernate.internal.util.collections.CollectionHelper; * @author Steve Ebersole */ public class SqmFromClause { - private List domainRoots; + private List> domainRoots; public SqmFromClause() { } @@ -35,7 +35,7 @@ public class SqmFromClause { * Immutable view of the domain roots. Use {@link #setRoots} or {@link #addRoot} to * mutate the roots */ - public List getRoots() { + public List> getRoots() { return domainRoots == null ? Collections.emptyList() : Collections.unmodifiableList( domainRoots ); } @@ -43,14 +43,14 @@ public class SqmFromClause { * Inject the complete set of domain roots */ @SuppressWarnings({"WeakerAccess", "unused"}) - public void setRoots(List domainRoots) { + public void setRoots(List> domainRoots) { this.domainRoots = domainRoots; } /** * Add roots incrementally */ - public void addRoot(SqmRoot root) { + public void addRoot(SqmRoot root) { if ( domainRoots == null ) { domainRoots = new ArrayList<>(); } @@ -61,7 +61,7 @@ public class SqmFromClause { /** * Visit the domain roots */ - public void visitRoots(Consumer consumer) { + public void visitRoots(Consumer> consumer) { if ( domainRoots != null ) { domainRoots.forEach( consumer ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java index 32faf870fa..ded4dfbee6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java @@ -8,14 +8,17 @@ package org.hibernate.query.sqm.tree.from; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.query.NavigablePath; import org.hibernate.query.PathException; import org.hibernate.query.criteria.JpaEntityJoin; import org.hibernate.query.criteria.JpaRoot; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.sql.internal.DomainResultProducer; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.domain.AbstractSqmFrom; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedRoot; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.domain.SqmTreatedRoot; @@ -34,6 +37,13 @@ public class SqmRoot extends AbstractSqmFrom implements JpaRoot, Doma super( entityType, alias, nodeBuilder ); } + protected SqmRoot( + NavigablePath navigablePath, + SqmPathSource referencedNavigable, + NodeBuilder nodeBuilder) { + super( navigablePath, referencedNavigable, nodeBuilder ); + } + @Override public SqmPath getLhs() { // a root has no LHS @@ -80,6 +90,19 @@ public class SqmRoot extends AbstractSqmFrom implements JpaRoot, Doma return getReferencedPathSource(); } + @Override + public SqmCorrelatedRoot createCorrelation() { + return new SqmCorrelatedRoot<>( this ); + } + + public boolean containsOnlyInnerJoins() { + for ( SqmJoin sqmJoin : getSqmJoins() ) { + if ( sqmJoin.getSqmJoinType() != SqmJoinType.INNER ) { + return false; + } + } + return true; + } @Override public JpaEntityJoin join(Class entityJavaType) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertSelectStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertSelectStatement.java index 4dd93688e6..18bb6955cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertSelectStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertSelectStatement.java @@ -9,20 +9,21 @@ package org.hibernate.query.sqm.tree.insert; import org.hibernate.query.criteria.JpaCriteriaInsertSelect; import org.hibernate.query.criteria.JpaPredicate; import org.hibernate.query.sqm.NodeBuilder; -import org.hibernate.query.sqm.SqmQuerySource; import org.hibernate.query.sqm.SemanticQueryWalker; -import org.hibernate.query.sqm.tree.select.SqmQuerySpec; +import org.hibernate.query.sqm.SqmQuerySource; import org.hibernate.query.sqm.tree.from.SqmRoot; +import org.hibernate.query.sqm.tree.select.SqmQueryPart; +import org.hibernate.query.sqm.tree.select.SqmQuerySpec; /** * @author Steve Ebersole */ public class SqmInsertSelectStatement extends AbstractSqmInsertStatement implements JpaCriteriaInsertSelect { - private SqmQuerySpec selectQuerySpec; + private SqmQueryPart selectQueryPart; public SqmInsertSelectStatement(SqmRoot targetRoot, NodeBuilder nodeBuilder) { super( targetRoot, SqmQuerySource.HQL, nodeBuilder ); - this.selectQuerySpec = new SqmQuerySpec<>( nodeBuilder ); + this.selectQueryPart = new SqmQuerySpec( nodeBuilder ); } public SqmInsertSelectStatement(Class targetEntity, NodeBuilder nodeBuilder) { @@ -35,15 +36,15 @@ public class SqmInsertSelectStatement extends AbstractSqmInsertStatement i SqmQuerySource.CRITERIA, nodeBuilder ); - this.selectQuerySpec = new SqmQuerySpec<>( nodeBuilder ); + this.selectQueryPart = new SqmQuerySpec<>( nodeBuilder ); } - public SqmQuerySpec getSelectQuerySpec() { - return selectQuerySpec; + public SqmQueryPart getSelectQueryPart() { + return selectQueryPart; } - public void setSelectQuerySpec(SqmQuerySpec selectQuerySpec) { - this.selectQuerySpec = selectQuerySpec; + public void setSelectQueryPart(SqmQueryPart selectQueryPart) { + this.selectQueryPart = selectQueryPart; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java index 4e9e16f85f..56da812446 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java @@ -7,7 +7,10 @@ package org.hibernate.query.sqm.tree.select; import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; import javax.persistence.criteria.Expression; import javax.persistence.criteria.Predicate; @@ -18,6 +21,8 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.tree.AbstractSqmNode; +import org.hibernate.query.sqm.tree.cte.SqmCteContainer; +import org.hibernate.query.sqm.tree.cte.SqmCteStatement; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; @@ -27,43 +32,78 @@ import org.hibernate.query.sqm.tree.predicate.SqmPredicate; @SuppressWarnings("unchecked") public abstract class AbstractSqmSelectQuery extends AbstractSqmNode - implements SqmSelectQuery { - private SqmQuerySpec sqmQuerySpec; - private Class resultType; + implements SqmSelectQuery, SqmCteContainer { + private final Map> cteStatements = new LinkedHashMap<>(); + private boolean withRecursive; + private SqmQueryPart sqmQueryPart; + private Class resultType; public AbstractSqmSelectQuery(Class resultType, NodeBuilder builder) { super( builder ); - this.sqmQuerySpec = new SqmQuerySpec<>( builder ); + this.sqmQueryPart = new SqmQuerySpec<>( builder ); this.resultType = resultType; } - - public AbstractSqmSelectQuery(SqmQuerySpec sqmQuerySpec, NodeBuilder builder) { - this( (Class) sqmQuerySpec.getSelectClause().getJavaType(), builder ); + public AbstractSqmSelectQuery(SqmQueryPart queryPart, Class resultType, NodeBuilder builder) { + super( builder ); + this.resultType = resultType; + setQueryPart( queryPart ); } @Override - public Class getResultType() { + public boolean isWithRecursive() { + return withRecursive; + } + + @Override + public void setWithRecursive(boolean withRecursive) { + this.withRecursive = withRecursive; + } + + @Override + public Collection> getCteStatements() { + return cteStatements.values(); + } + + @Override + public SqmCteStatement getCteStatement(String cteLabel) { + return cteStatements.get( cteLabel ); + } + + @Override + public void addCteStatement(SqmCteStatement cteStatement) { + if ( cteStatements.putIfAbsent( cteStatement.getCteTable().getCteName(), cteStatement ) != null ) { + throw new IllegalArgumentException( "A CTE with the label " + cteStatement.getCteTable().getCteName() + " already exists!" ); + } + } + + @Override + public Class getResultType() { return resultType; } - protected void setResultType(Class resultType) { + protected void setResultType(Class resultType) { this.resultType = resultType; } @Override public SqmQuerySpec getQuerySpec() { - return sqmQuerySpec; + return sqmQueryPart.getFirstQuerySpec(); } - public void setQuerySpec(SqmQuerySpec sqmQuerySpec) { - this.sqmQuerySpec = sqmQuerySpec; + @Override + public SqmQueryPart getQueryPart() { + return sqmQueryPart; + } + + public void setQueryPart(SqmQueryPart sqmQueryPart) { + this.sqmQueryPart = sqmQueryPart; } @Override @SuppressWarnings("unchecked") public Set> getRoots() { - return (Set) sqmQuerySpec.getRoots(); + return (Set) getQuerySpec().getRoots(); } @Override @@ -79,7 +119,7 @@ public abstract class AbstractSqmSelectQuery } private SqmRoot addRoot(SqmRoot root) { - sqmQuerySpec.addRoot( root ); + getQuerySpec().addRoot( root ); return root; } @@ -94,18 +134,18 @@ public abstract class AbstractSqmSelectQuery @Override public boolean isDistinct() { - return sqmQuerySpec.isDistinct(); + return getQuerySpec().isDistinct(); } @Override public SqmSelectQuery distinct(boolean distinct) { - sqmQuerySpec.setDistinct( distinct ); + getQuerySpec().setDistinct( distinct ); return this; } @Override public JpaSelection getSelection() { - return sqmQuerySpec.getSelection(); + return getQuerySpec().getSelection(); } @@ -114,18 +154,18 @@ public abstract class AbstractSqmSelectQuery @Override public SqmPredicate getRestriction() { - return sqmQuerySpec.getRestriction(); + return getQuerySpec().getRestriction(); } @Override public SqmSelectQuery where(Expression restriction) { - sqmQuerySpec.setRestriction( restriction ); + getQuerySpec().setRestriction( restriction ); return this; } @Override public SqmSelectQuery where(Predicate... restrictions) { - sqmQuerySpec.setRestriction( restrictions ); + getQuerySpec().setRestriction( restrictions ); return this; } @@ -136,7 +176,7 @@ public abstract class AbstractSqmSelectQuery @Override @SuppressWarnings("unchecked") public List> getGroupList() { - return (List) sqmQuerySpec.getGroupingExpressions(); + return (List) getQuerySpec().getGroupingExpressions(); } @Override @@ -153,18 +193,18 @@ public abstract class AbstractSqmSelectQuery @Override public SqmPredicate getGroupRestriction() { - return sqmQuerySpec.getGroupRestriction(); + return getQuerySpec().getGroupRestriction(); } @Override public SqmSelectQuery having(Expression booleanExpression) { - sqmQuerySpec.setGroupRestriction( nodeBuilder().wrap( booleanExpression ) ); + getQuerySpec().setGroupRestriction( nodeBuilder().wrap( booleanExpression ) ); return this; } @Override public SqmSelectQuery having(Predicate... predicates) { - sqmQuerySpec.setGroupRestriction( nodeBuilder().wrap( predicates ) ); + getQuerySpec().setGroupRestriction( nodeBuilder().wrap( predicates ) ); return this; } 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 new file mode 100644 index 0000000000..8bf7398509 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryGroup.java @@ -0,0 +1,70 @@ +/* + * 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.tree.select; + +import java.util.List; + +import org.hibernate.SetOperator; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; + +/** + * A grouped list of queries connected through a certain set operator. + * + * @author Christian Beikov + */ +public class SqmQueryGroup extends SqmQueryPart { + + private final List> queryParts; + private SetOperator setOperator; + + public SqmQueryGroup(SqmQueryPart queryPart) { + this( queryPart.nodeBuilder(), null, CollectionHelper.listOf( queryPart ) ); + } + + public SqmQueryGroup( + NodeBuilder nodeBuilder, + SetOperator setOperator, + List> queryParts) { + super( nodeBuilder ); + this.setOperator = setOperator; + this.queryParts = queryParts; + } + + @Override + public SqmQuerySpec getFirstQuerySpec() { + return queryParts.get( 0 ).getFirstQuerySpec(); + } + + @Override + public SqmQuerySpec getLastQuerySpec() { + return queryParts.get( queryParts.size() - 1 ).getLastQuerySpec(); + } + + @Override + public boolean isSimpleQueryPart() { + return setOperator == null && queryParts.size() == 1 && queryParts.get( 0 ).isSimpleQueryPart(); + } + + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitQueryGroup( this ); + } + + public List> getQueryParts() { + return queryParts; + } + + public SetOperator getSetOperator() { + return setOperator; + } + + public void setSetOperator(SetOperator setOperator) { + this.setOperator = setOperator; + } +} 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 new file mode 100644 index 0000000000..48d2fb374f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryPart.java @@ -0,0 +1,136 @@ +/* + * 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.tree.select; + +import java.util.Collections; +import java.util.List; + +import org.hibernate.FetchClauseType; +import org.hibernate.query.criteria.JpaExpression; +import org.hibernate.query.criteria.JpaOrder; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmVisitableNode; +import org.hibernate.query.sqm.tree.expression.SqmExpression; +import org.hibernate.type.StandardBasicTypes; + +/** + * Defines the ordering and fetch/offset part of a query which is shared with query groups. + * + * @author Christian Beikov + */ +public abstract class SqmQueryPart implements SqmVisitableNode { + private final NodeBuilder nodeBuilder; + + private SqmOrderByClause orderByClause; + + private SqmExpression offsetExpression; + private SqmExpression fetchExpression; + private FetchClauseType fetchClauseType = FetchClauseType.ROWS_ONLY; + + public SqmQueryPart(NodeBuilder nodeBuilder) { + this.nodeBuilder = nodeBuilder; + } + + public abstract SqmQuerySpec getFirstQuerySpec(); + + public abstract SqmQuerySpec getLastQuerySpec(); + + public abstract boolean isSimpleQueryPart(); + + @Override + public NodeBuilder nodeBuilder() { + return nodeBuilder; + } + + public SqmOrderByClause getOrderByClause() { + return orderByClause; + } + + public void setOrderByClause(SqmOrderByClause orderByClause) { + this.orderByClause = orderByClause; + } + + public SqmExpression getFetchExpression() { + return fetchExpression; + } + + public SqmExpression getOffsetExpression() { + return offsetExpression; + } + + public void setOffsetExpression(SqmExpression offsetExpression) { + if ( offsetExpression != null ) { + offsetExpression.applyInferableType( StandardBasicTypes.INTEGER ); + } + this.offsetExpression = offsetExpression; + } + + public void setFetchExpression(SqmExpression fetchExpression) { + setFetchExpression( fetchExpression, FetchClauseType.ROWS_ONLY ); + } + + public void setFetchExpression(SqmExpression fetchExpression, FetchClauseType fetchClauseType) { + if ( fetchExpression == null ) { + this.fetchExpression = null; + this.fetchClauseType = null; + } + else { + if ( fetchClauseType == null ) { + throw new IllegalArgumentException( "Fetch clause may not be null!" ); + } + fetchExpression.applyInferableType( StandardBasicTypes.INTEGER ); + this.fetchExpression = fetchExpression; + this.fetchClauseType = fetchClauseType; + } + } + + public FetchClauseType getFetchClauseType() { + return fetchClauseType; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // JPA + + public List getSortSpecifications() { + if ( getOrderByClause() == null ) { + return Collections.emptyList(); + } + + return getOrderByClause().getSortSpecifications(); + } + + public SqmQueryPart setSortSpecifications(List sortSpecifications) { + if ( getOrderByClause() == null ) { + setOrderByClause( new SqmOrderByClause() ); + } + + //noinspection unchecked + getOrderByClause().setSortSpecifications( (List) sortSpecifications ); + + return this; + } + + @SuppressWarnings("unchecked") + public SqmExpression getOffset() { + return getOffsetExpression(); + } + + public SqmQueryPart setOffset(JpaExpression offset) { + setOffsetExpression( (SqmExpression) offset ); + return this; + } + + @SuppressWarnings("unchecked") + public SqmExpression getFetch() { + return getFetchExpression(); + } + + public SqmQueryPart setFetch(JpaExpression fetch) { + setFetchExpression( (SqmExpression) fetch ); + return this; + } +} 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 6f1dcea127..a33dfaf764 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,6 +14,7 @@ import java.util.Set; import javax.persistence.criteria.Expression; import javax.persistence.criteria.Predicate; +import org.hibernate.FetchClauseType; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaOrder; import org.hibernate.query.criteria.JpaPredicate; @@ -21,25 +22,26 @@ import org.hibernate.query.criteria.JpaQueryStructure; import org.hibernate.query.criteria.JpaRoot; 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.cte.SqmCteConsumer; import org.hibernate.query.sqm.tree.expression.SqmExpression; +import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; +import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmFromClause; import org.hibernate.query.sqm.tree.from.SqmFromClauseContainer; +import org.hibernate.query.sqm.tree.from.SqmJoin; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; import org.hibernate.query.sqm.tree.predicate.SqmWhereClauseContainer; -import org.hibernate.type.StandardBasicTypes; /** * Defines the commonality between a root query and a subquery. * * @author Steve Ebersole */ -public class SqmQuerySpec implements SqmCteConsumer, SqmNode, SqmFromClauseContainer, SqmWhereClauseContainer, JpaQueryStructure { - private final NodeBuilder nodeBuilder; - +public class SqmQuerySpec extends SqmQueryPart + implements SqmNode, SqmFromClauseContainer, SqmWhereClauseContainer, JpaQueryStructure { private SqmFromClause fromClause; private SqmSelectClause selectClause; private SqmWhereClause whereClause; @@ -47,18 +49,28 @@ public class SqmQuerySpec implements SqmCteConsumer, SqmNode, SqmFromClauseCo private List> groupByClauseExpressions = Collections.emptyList(); private SqmPredicate havingClausePredicate; - private SqmOrderByClause orderByClause; - - private SqmExpression limitExpression; - private SqmExpression offsetExpression; - public SqmQuerySpec(NodeBuilder nodeBuilder) { - this.nodeBuilder = nodeBuilder; + super( nodeBuilder ); } @Override - public NodeBuilder nodeBuilder() { - return nodeBuilder; + public X accept(SemanticQueryWalker walker) { + return walker.visitQuerySpec( this ); + } + + @Override + public SqmQuerySpec getFirstQuerySpec() { + return this; + } + + @Override + public SqmQuerySpec getLastQuerySpec() { + return this; + } + + @Override + public boolean isSimpleQueryPart() { + return true; } @Override @@ -70,6 +82,23 @@ public class SqmQuerySpec implements SqmCteConsumer, SqmNode, SqmFromClauseCo this.fromClause = fromClause; } + public boolean containsCollectionFetches() { + final List> fromNodes = new ArrayList<>( fromClause.getRoots() ); + while ( !fromNodes.isEmpty() ) { + final SqmFrom fromNode = fromNodes.remove( fromNodes.size() - 1 ); + for ( SqmJoin sqmJoin : fromNode.getSqmJoins() ) { + if ( sqmJoin instanceof SqmAttributeJoin ) { + final SqmAttributeJoin join = (SqmAttributeJoin) sqmJoin; + if ( join.isFetched() && join.getAttribute().isCollection() ) { + return true; + } + } + fromNodes.add( sqmJoin ); + } + } + return false; + } + public SqmSelectClause getSelectClause() { return selectClause; } @@ -118,36 +147,6 @@ public class SqmQuerySpec implements SqmCteConsumer, SqmNode, SqmFromClauseCo this.havingClausePredicate = havingClausePredicate; } - public SqmOrderByClause getOrderByClause() { - return orderByClause; - } - - public void setOrderByClause(SqmOrderByClause orderByClause) { - this.orderByClause = orderByClause; - } - - public SqmExpression getLimitExpression() { - return limitExpression; - } - - public void setLimitExpression(SqmExpression limitExpression) { - if ( limitExpression != null ) { - limitExpression.applyInferableType( StandardBasicTypes.INTEGER ); - } - this.limitExpression = limitExpression; - } - - public SqmExpression getOffsetExpression() { - return offsetExpression; - } - - public void setOffsetExpression(SqmExpression offsetExpression) { - if ( offsetExpression != null ) { - offsetExpression.applyInferableType( StandardBasicTypes.INTEGER ); - } - this.offsetExpression = offsetExpression; - } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // JPA @@ -183,7 +182,7 @@ public class SqmQuerySpec implements SqmCteConsumer, SqmNode, SqmFromClauseCo @Override @SuppressWarnings("unchecked") - public Set getRoots() { + public Set> getRoots() { assert getFromClause() != null; return new HashSet<>( getFromClause().getRoots() ); } @@ -219,7 +218,7 @@ public class SqmQuerySpec implements SqmCteConsumer, SqmNode, SqmFromClauseCo if ( getWhereClause() == null ) { setWhereClause( new SqmWhereClause( nodeBuilder() ) ); } - getWhereClause().setPredicate( nodeBuilder.wrap( restriction ) ); + getWhereClause().setPredicate( nodeBuilder().wrap( restriction ) ); return this; } @@ -268,46 +267,19 @@ public class SqmQuerySpec implements SqmCteConsumer, SqmNode, SqmFromClauseCo @Override public SqmQuerySpec setGroupRestriction(Expression restriction) { - havingClausePredicate = nodeBuilder.wrap( restriction ); + havingClausePredicate = nodeBuilder().wrap( restriction ); return this; } @Override public SqmQuerySpec setGroupRestriction(Predicate... restrictions) { - havingClausePredicate = nodeBuilder.wrap( restrictions ); + havingClausePredicate = nodeBuilder().wrap( restrictions ); return this; } - @Override - public List getSortSpecifications() { - if ( getOrderByClause() == null ) { - return Collections.emptyList(); - } - - return getOrderByClause().getSortSpecifications(); - } - @Override public SqmQuerySpec setSortSpecifications(List sortSpecifications) { - if ( getOrderByClause() == null ) { - setOrderByClause( new SqmOrderByClause() ); - } - - //noinspection unchecked - getOrderByClause().setSortSpecifications( (List) sortSpecifications ); - - return this; - } - - @Override - @SuppressWarnings("unchecked") - public SqmExpression getLimit() { - return getLimitExpression(); - } - - @Override - public SqmQuerySpec setLimit(JpaExpression limit) { - setLimitExpression( (SqmExpression) limit ); + super.setSortSpecifications( sortSpecifications ); return this; } @@ -322,4 +294,22 @@ public class SqmQuerySpec implements SqmCteConsumer, SqmNode, SqmFromClauseCo setOffsetExpression( (SqmExpression) offset ); return this; } + + @Override + @SuppressWarnings("unchecked") + public SqmExpression getFetch() { + return getFetchExpression(); + } + + @Override + public SqmQuerySpec setFetch(JpaExpression fetch) { + setFetchExpression( (SqmExpression) fetch ); + return this; + } + + @Override + public SqmQuerySpec setFetch(JpaExpression fetch, FetchClauseType fetchClauseType) { + setFetchExpression( (SqmExpression) fetch, fetchClauseType ); + return this; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectQuery.java index 217caa9160..c68ee25b6c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectQuery.java @@ -20,6 +20,8 @@ public interface SqmSelectQuery extends SqmQuery, JpaSelectCriteria, Sq @Override SqmQuerySpec getQuerySpec(); + SqmQueryPart getQueryPart(); + @Override SqmSelectQuery distinct(boolean distinct); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java index f4eca11801..80c53bce1a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java @@ -53,6 +53,11 @@ public class SqmSelectStatement extends AbstractSqmSelectQuery implements this.querySource = querySource; } + public SqmSelectStatement(Class resultJavaType, SqmQuerySource querySource, NodeBuilder nodeBuilder) { + super( resultJavaType, nodeBuilder ); + this.querySource = querySource; + } + /** * @implNote This form is used from Hibernate's JPA criteria handling. */ @@ -253,7 +258,7 @@ public class SqmSelectStatement extends AbstractSqmSelectQuery implements @SuppressWarnings("unchecked") public SqmSelectStatement select(Selection selection) { getQuerySpec().setSelection( (JpaSelection) selection ); - setResultType( selection.getJavaType() ); + setResultType( (Class) selection.getJavaType() ); return this; } @@ -262,7 +267,7 @@ public class SqmSelectStatement extends AbstractSqmSelectQuery implements for ( Selection selection : selections ) { getQuerySpec().getSelectClause().add( (SqmExpression) selection, selection.getAlias() ); } - setResultType( Object[].class ); + setResultType( (Class) Object[].class ); return this; } @@ -271,7 +276,7 @@ public class SqmSelectStatement extends AbstractSqmSelectQuery implements for ( Selection selection : selectionList ) { getQuerySpec().getSelectClause().add( (SqmExpression) selection, selection.getAlias() ); } - setResultType( Object[].class ); + setResultType( (Class) Object[].class ); return this; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java index 79f9f47e97..f8f7b0b78f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.select; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Set; import javax.persistence.criteria.CollectionJoin; @@ -16,12 +17,12 @@ import javax.persistence.criteria.Expression; import javax.persistence.criteria.Join; import javax.persistence.criteria.ListJoin; import javax.persistence.criteria.MapJoin; +import javax.persistence.criteria.PluralJoin; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.persistence.criteria.Selection; import javax.persistence.criteria.SetJoin; -import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.criteria.JpaSubQuery; import org.hibernate.query.sqm.NodeBuilder; @@ -29,11 +30,24 @@ import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressable; import org.hibernate.query.sqm.tree.SqmQuery; import org.hibernate.query.sqm.tree.domain.SqmBagJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedBagJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedCrossJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedEntityJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedListJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedMapJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedRoot; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedSetJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedSingularJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelation; import org.hibernate.query.sqm.tree.domain.SqmListJoin; import org.hibernate.query.sqm.tree.domain.SqmMapJoin; import org.hibernate.query.sqm.tree.domain.SqmSetJoin; import org.hibernate.query.sqm.tree.domain.SqmSingularJoin; 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.SqmJoin; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.predicate.SqmInPredicate; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; @@ -51,9 +65,10 @@ public class SqmSubQuery extends AbstractSqmSelectQuery implements SqmSele public SqmSubQuery( SqmQuery parent, - SqmQuerySpec querySpec, + SqmQueryPart queryPart, + Class resultType, NodeBuilder builder) { - super( querySpec, builder ); + super( queryPart, resultType, builder ); this.parent = parent; } @@ -129,11 +144,6 @@ public class SqmSubQuery extends AbstractSqmSelectQuery implements SqmSele return (SqmSubQuery) super.distinct( distinct ); } - @Override - public SqmRoot correlate(Root parentRoot) { - throw new NotYetImplementedFor6Exception(); - } - @Override public SqmSubQuery where(Expression restriction) { return (SqmSubQuery) super.where( restriction ); @@ -165,33 +175,118 @@ public class SqmSubQuery extends AbstractSqmSelectQuery implements SqmSele } @Override - public SqmSingularJoin correlate(Join parentJoin) { - throw new NotYetImplementedFor6Exception(); + public SqmRoot correlate(Root parentRoot) { + final SqmCorrelatedRoot correlated = ( (SqmRoot) parentRoot ).createCorrelation(); + if ( getQuerySpec().getFromClause() != null ) { + getQuerySpec().getFromClause().addRoot( correlated ); + } + return correlated; + } + + @Override + public SqmAttributeJoin correlate(Join join) { + if ( join instanceof PluralJoin ) { + final PluralJoin pluralJoin = (PluralJoin) join; + switch ( pluralJoin.getModel().getCollectionType() ) { + case COLLECTION: + return correlate( (CollectionJoin) join ); + case LIST: + return correlate( (ListJoin) join ); + case SET: + return correlate( (SetJoin) join ); + case MAP: + return correlate( (MapJoin) join ); + } + } + final SqmCorrelatedSingularJoin correlated = ( (SqmSingularJoin) join ).createCorrelation(); + if ( getQuerySpec().getFromClause() != null ) { + getQuerySpec().getFromClause().addRoot( correlated.getCorrelatedRoot() ); + } + return correlated; } @Override public SqmBagJoin correlate(CollectionJoin parentCollection) { - throw new NotYetImplementedFor6Exception(); + final SqmCorrelatedBagJoin correlated = ( (SqmBagJoin) parentCollection ).createCorrelation(); + if ( getQuerySpec().getFromClause() != null ) { + getQuerySpec().getFromClause().addRoot( correlated.getCorrelatedRoot() ); + } + return correlated; } @Override public SqmSetJoin correlate(SetJoin parentSet) { - throw new NotYetImplementedFor6Exception(); + final SqmCorrelatedSetJoin correlated = ( (SqmSetJoin) parentSet ).createCorrelation(); + if ( getQuerySpec().getFromClause() != null ) { + getQuerySpec().getFromClause().addRoot( correlated.getCorrelatedRoot() ); + } + return correlated; } @Override public SqmListJoin correlate(ListJoin parentList) { - throw new NotYetImplementedFor6Exception(); + final SqmCorrelatedListJoin correlated = ( (SqmListJoin) parentList ).createCorrelation(); + if ( getQuerySpec().getFromClause() != null ) { + getQuerySpec().getFromClause().addRoot( correlated.getCorrelatedRoot() ); + } + return correlated; } @Override public SqmMapJoin correlate(MapJoin parentMap) { - throw new NotYetImplementedFor6Exception(); + final SqmCorrelatedMapJoin correlated = ( (SqmMapJoin) parentMap ).createCorrelation(); + if ( getQuerySpec().getFromClause() != null ) { + getQuerySpec().getFromClause().addRoot( correlated.getCorrelatedRoot() ); + } + return correlated; + } + + @Override + public SqmCrossJoin correlate(SqmCrossJoin parentCrossJoin) { + final SqmCorrelatedCrossJoin correlated = parentCrossJoin.createCorrelation(); + if ( getQuerySpec().getFromClause() != null ) { + getQuerySpec().getFromClause().addRoot( correlated.getCorrelatedRoot() ); + } + return correlated; + } + + @Override + public SqmEntityJoin correlate(SqmEntityJoin parentEntityJoin) { + final SqmCorrelatedEntityJoin correlated = parentEntityJoin.createCorrelation(); + if ( getQuerySpec().getFromClause() != null ) { + getQuerySpec().getFromClause().addRoot( correlated.getCorrelatedRoot() ); + } + return correlated; } @Override public Set> getCorrelatedJoins() { - throw new NotYetImplementedFor6Exception(); + final Set> correlatedJoins = new HashSet<>(); + for ( SqmRoot root : getQuerySpec().getFromClause().getRoots() ) { + if ( root instanceof SqmCorrelation ) { + for ( SqmJoin sqmJoin : root.getSqmJoins() ) { + if ( sqmJoin instanceof SqmCorrelation && sqmJoin instanceof Join ) { + correlatedJoins.add( (Join) sqmJoin ); + } + } + } + } + return correlatedJoins; + } + + @Override + public Set> getCorrelatedSqmJoins() { + final Set> correlatedJoins = new HashSet<>(); + for ( SqmRoot root : getQuerySpec().getFromClause().getRoots() ) { + if ( root instanceof SqmCorrelation ) { + for ( SqmJoin sqmJoin : root.getSqmJoins() ) { + if ( sqmJoin instanceof SqmCorrelation ) { + correlatedJoins.add( sqmJoin ); + } + } + } + } + return correlatedJoins; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java index 26b037d1d6..cdd382eb8c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java @@ -23,7 +23,6 @@ import org.hibernate.query.sqm.SqmQuerySource; import org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder; import org.hibernate.query.sqm.tree.AbstractSqmDmlStatement; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; -import org.hibernate.query.sqm.tree.cte.SqmCteConsumer; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -35,7 +34,7 @@ import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; */ public class SqmUpdateStatement extends AbstractSqmDmlStatement - implements SqmDeleteOrUpdateStatement, SqmCteConsumer, JpaCriteriaUpdate { + implements SqmDeleteOrUpdateStatement, JpaCriteriaUpdate { private SqmSetClause setClause; private SqmWhereClause whereClause; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/Clause.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/Clause.java index 60010a1b83..a95533bde8 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/Clause.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/Clause.java @@ -48,8 +48,10 @@ public enum Clause { GROUP, HAVING, ORDER, - LIMIT, OFFSET, + FETCH, + OVER, + PARTITION, CALL, /** diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstDeleteTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstDeleteTranslator.java deleted file mode 100644 index 264e8675de..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstDeleteTranslator.java +++ /dev/null @@ -1,22 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.sql.ast; - -import org.hibernate.sql.ast.spi.SqlAstToJdbcOperationConverter; -import org.hibernate.sql.ast.tree.cte.CteStatement; -import org.hibernate.sql.ast.tree.delete.DeleteStatement; -import org.hibernate.sql.exec.spi.JdbcDelete; - -/** - * @author Steve Ebersole - */ -public interface SqlAstDeleteTranslator extends SqlAstWalker, SqlAstToJdbcOperationConverter { - JdbcDelete translate(DeleteStatement sqlAst); - - @Override - JdbcDelete translate(CteStatement cteStatement); -} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstInsertTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstInsertTranslator.java deleted file mode 100644 index 5501773e9a..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstInsertTranslator.java +++ /dev/null @@ -1,17 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.sql.ast; - -import org.hibernate.sql.ast.tree.insert.InsertStatement; -import org.hibernate.sql.exec.spi.JdbcInsert; - -/** - * @author Steve Ebersole - */ -public interface SqlAstInsertTranslator extends SqlAstTranslator { - JdbcInsert translate(InsertStatement sqlAst); -} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstSelectTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstSelectTranslator.java deleted file mode 100644 index 90ebe97155..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstSelectTranslator.java +++ /dev/null @@ -1,27 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.sql.ast; - -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.spi.JdbcSelect; - -/** - * @author Steve Ebersole - */ -public interface SqlAstSelectTranslator extends SqlAstTranslator { - - /** - * Translate the SelectStatement into the executable JdbcSelect - */ - JdbcSelect translate(SelectStatement selectStatement); - - /** - * Translate the QuerySpec into the executable JdbcSelect - */ - JdbcSelect translate(QuerySpec querySpec); -} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslator.java index bc75de5263..4c50dbd38c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslator.java @@ -8,14 +8,15 @@ package org.hibernate.sql.ast; import java.util.Set; -import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.query.spi.QueryOptions; import org.hibernate.sql.exec.spi.JdbcOperation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators; /** * @author Steve Ebersole */ -public interface SqlAstTranslator extends SqlAstWalker, SqlTypeDescriptorIndicators { +public interface SqlAstTranslator extends SqlAstWalker, SqlTypeDescriptorIndicators { /** * Not the best spot for this. Its the table names collected while walking the SQL AST. * Its ok here because the translator is consider a one-time-use. It just needs to be called @@ -25,11 +26,6 @@ public interface SqlAstTranslator extends SqlAstWalker, SqlTypeDescriptorIndicat */ Set getAffectedTableNames(); - /** - * Generalized support for translating a CTE statement. The underlying - * {@link CteStatement#getCteConsumer()} could be a SELECT, UPDATE, DELETE, etc. - * - * Implementors may throw an exception if the CTE-consumer is of the incorrect type - */ - JdbcOperation translate(CteStatement cteStatement); + T translate(JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions); + } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslatorFactory.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslatorFactory.java index fe5a88f76c..5d36887ef6 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslatorFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslatorFactory.java @@ -7,6 +7,14 @@ package org.hibernate.sql.ast; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; +import org.hibernate.sql.ast.tree.insert.InsertStatement; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.spi.JdbcDelete; +import org.hibernate.sql.exec.spi.JdbcInsert; +import org.hibernate.sql.exec.spi.JdbcSelect; +import org.hibernate.sql.exec.spi.JdbcUpdate; /** * Factory for obtaining single-use SQL AST translators @@ -17,19 +25,20 @@ public interface SqlAstTranslatorFactory { /** * Builds a single-use select translator */ - SqlAstSelectTranslator buildSelectTranslator(SessionFactoryImplementor sessionFactory); + SqlAstTranslator buildSelectTranslator(SessionFactoryImplementor sessionFactory, SelectStatement statement); /** * Builds a single-use delete translator */ - SqlAstDeleteTranslator buildDeleteTranslator(SessionFactoryImplementor sessionFactory); + SqlAstTranslator buildDeleteTranslator(SessionFactoryImplementor sessionFactory, DeleteStatement statement); /** * Builds a single-use insert-select translator */ - SqlAstInsertTranslator buildInsertTranslator(SessionFactoryImplementor sessionFactory); + SqlAstTranslator buildInsertTranslator(SessionFactoryImplementor sessionFactory, InsertStatement statement); - SqlAstUpdateTranslator buildUpdateTranslator(SessionFactoryImplementor sessionFactory); - - // todo (6.0) : CTE + /** + * Builds a single-use update translator + */ + SqlAstTranslator buildUpdateTranslator(SessionFactoryImplementor sessionFactory, UpdateStatement statement); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstUpdateTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstUpdateTranslator.java deleted file mode 100644 index 94633097f1..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstUpdateTranslator.java +++ /dev/null @@ -1,21 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.sql.ast; - -import org.hibernate.sql.ast.tree.cte.CteStatement; -import org.hibernate.sql.ast.tree.update.UpdateStatement; -import org.hibernate.sql.exec.spi.JdbcUpdate; - -/** - * @author Steve Ebersole - */ -public interface SqlAstUpdateTranslator extends SqlAstTranslator { - JdbcUpdate translate(UpdateStatement sqlAst); - - @Override - JdbcUpdate translate(CteStatement cteStatement); -} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstWalker.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstWalker.java index 90ea266b2f..25bc0c851b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstWalker.java @@ -9,6 +9,7 @@ package org.hibernate.sql.ast; import org.hibernate.Incubating; import org.hibernate.query.sqm.tree.expression.Conversion; import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.expression.Any; import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; @@ -38,6 +39,7 @@ 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.from.TableReferenceJoin; +import org.hibernate.sql.ast.tree.insert.InsertStatement; import org.hibernate.sql.ast.tree.predicate.BetweenPredicate; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; import org.hibernate.sql.ast.tree.predicate.ExistsPredicate; @@ -50,10 +52,14 @@ import org.hibernate.sql.ast.tree.predicate.LikePredicate; import org.hibernate.sql.ast.tree.predicate.NegatedPredicate; import org.hibernate.sql.ast.tree.predicate.NullnessPredicate; import org.hibernate.sql.ast.tree.predicate.SelfRenderingPredicate; +import org.hibernate.sql.ast.tree.select.QueryGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.ast.tree.select.SortSpecification; import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.sql.ast.tree.update.UpdateStatement; /** * @author Steve Ebersole @@ -61,13 +67,24 @@ import org.hibernate.sql.ast.tree.update.Assignment; */ @Incubating public interface SqlAstWalker { + + void visitSelectStatement(SelectStatement statement); + + void visitDeleteStatement(DeleteStatement statement); + + void visitUpdateStatement(UpdateStatement statement); + + void visitInsertStatement(InsertStatement statement); + void visitAssignment(Assignment assignment); + void visitQueryGroup(QueryGroup queryGroup); + void visitQuerySpec(QuerySpec querySpec); void visitSortSpecification(SortSpecification sortSpecification); - void visitLimitOffsetClause(QuerySpec querySpec); + void visitOffsetFetchClause(QueryPart querySpec); void visitSelectClause(SelectClause selectClause); 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 1d4536dbaa..60935e0aff 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 @@ -6,31 +6,78 @@ */ package org.hibernate.sql.ast.spi; -import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Collections; import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; import java.util.Set; +import org.hibernate.FetchClauseType; +import org.hibernate.LockOptions; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.FilterJdbcParameter; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.persister.entity.AbstractEntityPersister; +import org.hibernate.query.Limit; +import org.hibernate.query.spi.QueryOptions; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlTreeCreationException; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.expression.Literal; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.insert.InsertStatement; import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; +import org.hibernate.sql.exec.internal.JdbcParameterImpl; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcDelete; +import org.hibernate.sql.exec.spi.JdbcInsert; +import org.hibernate.sql.exec.spi.JdbcOperation; +import org.hibernate.sql.exec.spi.JdbcParameterBinder; +import org.hibernate.sql.exec.spi.JdbcParameterBinding; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelect; +import org.hibernate.sql.exec.spi.JdbcUpdate; +import org.hibernate.sql.results.jdbc.internal.JdbcValuesMappingProducerStandard; +import org.hibernate.type.IntegerType; + +import static org.hibernate.sql.ast.SqlTreePrinter.logSqlAst; +import static org.hibernate.sql.results.graph.DomainResultGraphPrinter.logDomainResultGraph; /** * @author Steve Ebersole */ -public abstract class AbstractSqlAstTranslator +public abstract class AbstractSqlAstTranslator extends AbstractSqlAstWalker - implements SqlAstTranslator { + implements SqlAstTranslator { + private final Statement statement; private final Set affectedTableNames = new HashSet<>(); - protected AbstractSqlAstTranslator(SessionFactoryImplementor sessionFactory) { + private Map appliedParameterBindings = Collections.emptyMap(); + private JdbcParameterBindings jdbcParameterBindings; + private LockOptions lockOptions; + private Limit limit; + private JdbcParameter offsetParameter; + private JdbcParameter limitParameter; + + protected AbstractSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { super( sessionFactory ); + this.statement = statement; } @Override @@ -43,6 +90,497 @@ public abstract class AbstractSqlAstTranslator return affectedTableNames; } + protected void addAppliedParameterBinding(JdbcParameter parameter, JdbcParameterBinding binding) { + if ( appliedParameterBindings.isEmpty() ) { + appliedParameterBindings = new IdentityHashMap<>(); + } + if ( binding == null ) { + appliedParameterBindings.put( parameter, null ); + } + else { + final JdbcMapping bindType = binding.getBindType(); + final Object value = bindType.getJavaTypeDescriptor() + .getMutabilityPlan() + .deepCopy( binding.getBindValue() ); + appliedParameterBindings.put( parameter, new JdbcParameterBindingImpl( bindType, value ) ); + } + } + + protected Map getAppliedParameterBindings() { + return appliedParameterBindings; + } + + protected JdbcParameterBindings getJdbcParameterBindings() { + return jdbcParameterBindings; + } + + protected LockOptions getLockOptions() { + return lockOptions; + } + + protected Limit getLimit() { + return limit; + } + + protected boolean hasLimit() { + return limit != null && !limit.isEmpty(); + } + + protected boolean hasOffset(QueryPart queryPart) { + if ( queryPart.isRoot() && hasLimit() ) { + return limit.getFirstRowJpa() != 0; + } + else { + return queryPart.getOffsetClauseExpression() != null; + } + } + + protected boolean useOffsetFetchClause(QueryPart queryPart) { + return !queryPart.isRoot() || limit == null || limit.isEmpty(); + } + + protected boolean isRowsOnlyFetchClauseType(QueryPart queryPart) { + if ( queryPart.isRoot() && hasLimit() || queryPart.getFetchClauseType() == null ) { + return true; + } + else { + return queryPart.getFetchClauseType() == FetchClauseType.ROWS_ONLY; + } + } + + @Override + protected FetchClauseType getFetchClauseTypeForRowNumbering(QueryPart queryPartForRowNumbering) { + if ( queryPartForRowNumbering.isRoot() && hasLimit() ) { + return FetchClauseType.ROWS_ONLY; + } + else { + return super.getFetchClauseTypeForRowNumbering( queryPartForRowNumbering ); + } + } + + @Override + protected void assertRowsOnlyFetchClauseType(QueryPart queryPart) { + if ( !queryPart.isRoot() || !hasLimit() ) { + super.assertRowsOnlyFetchClauseType( queryPart ); + } + } + + protected JdbcParameter getOffsetParameter() { + return offsetParameter; + } + + protected void setOffsetParameter(JdbcParameter offsetParameter) { + this.offsetParameter = offsetParameter; + } + + protected JdbcParameter getLimitParameter() { + return limitParameter; + } + + protected void setLimitParameter(JdbcParameter limitParameter) { + this.limitParameter = limitParameter; + } + + protected R interpretExpression(Expression expression, JdbcParameterBindings jdbcParameterBindings) { + if ( expression instanceof Literal ) { + return (R) ( (Literal) expression ).getLiteralValue(); + } + else if ( expression instanceof JdbcParameter ) { + if ( jdbcParameterBindings == null ) { + throw new IllegalArgumentException( "Can't interpret expression because no parameter bindings are available!" ); + } + return (R) getParameterBindValue( (JdbcParameter) expression ); + } + throw new UnsupportedOperationException( "Can't interpret expression: " + expression ); + } + + protected void renderExpressionAsLiteral(Expression expression, JdbcParameterBindings jdbcParameterBindings) { + if ( expression instanceof Literal ) { + expression.accept( this ); + } + else if ( expression instanceof JdbcParameter ) { + if ( jdbcParameterBindings == null ) { + throw new IllegalArgumentException( "Can't interpret expression because no parameter bindings are available!" ); + } + final JdbcParameter parameter = (JdbcParameter) expression; + renderAsLiteral( parameter, getParameterBindValue( parameter ) ); + } + throw new UnsupportedOperationException( "Can't render expression as literal: " + expression ); + } + + protected Object getParameterBindValue(JdbcParameter parameter) { + final JdbcParameterBinding binding; + if ( parameter == getOffsetParameter() ) { + binding = new JdbcParameterBindingImpl( IntegerType.INSTANCE, getLimit().getFirstRow() ); + } + else if ( parameter == getLimitParameter() ) { + binding = new JdbcParameterBindingImpl( IntegerType.INSTANCE, getLimit().getMaxRows() ); + } + else { + binding = jdbcParameterBindings.getBinding( parameter ); + } + addAppliedParameterBinding( parameter, binding ); + return binding.getBindValue(); + } + + @Override + protected void cleanup() { + super.cleanup(); + this.jdbcParameterBindings = null; + this.lockOptions = null; + this.limit = null; + setOffsetParameter( null ); + setLimitParameter( null ); + } + + @Override + public T translate(JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) { + try { + this.jdbcParameterBindings = jdbcParameterBindings; + this.lockOptions = queryOptions.getLockOptions().makeCopy(); + this.limit = queryOptions.getLimit() == null ? null : queryOptions.getLimit().makeCopy(); + final JdbcOperation jdbcOperation; + if ( statement instanceof DeleteStatement ) { + jdbcOperation = translateDelete( (DeleteStatement) statement ); + } + else if ( statement instanceof UpdateStatement ) { + jdbcOperation = translateUpdate( (UpdateStatement) statement ); + } + else if ( statement instanceof InsertStatement ) { + jdbcOperation = translateInsert( (InsertStatement) statement ); + } + else if ( statement instanceof SelectStatement ) { + jdbcOperation = translateSelect( (SelectStatement) statement ); + } + else { + throw new IllegalArgumentException( "Unexpected statement!" ); + } + if ( jdbcParameterBindings != null && CollectionHelper.isNotEmpty( getFilterJdbcParameters() ) ) { + for ( FilterJdbcParameter filterJdbcParameter : getFilterJdbcParameters() ) { + jdbcParameterBindings.addBinding( + filterJdbcParameter.getParameter(), + filterJdbcParameter.getBinding() + ); + } + } + return (T) jdbcOperation; + } + finally { + cleanup(); + } + } + + protected JdbcDelete translateDelete(DeleteStatement sqlAst) { + visitDeleteStatement( sqlAst ); + + return new JdbcDelete() { + @Override + public String getSql() { + return AbstractSqlAstTranslator.this.getSql(); + } + + @Override + public List getParameterBinders() { + return AbstractSqlAstTranslator.this.getParameterBinders(); + } + + @Override + public Set getAffectedTableNames() { + return AbstractSqlAstTranslator.this.getAffectedTableNames(); + } + + @Override + public Set getFilterJdbcParameters() { + return AbstractSqlAstTranslator.this.getFilterJdbcParameters(); + } + }; + } + + protected JdbcUpdate translateUpdate(UpdateStatement sqlAst) { + visitUpdateStatement( sqlAst ); + + return new JdbcUpdate() { + @Override + public String getSql() { + return AbstractSqlAstTranslator.this.getSql(); + } + + @Override + public List getParameterBinders() { + return AbstractSqlAstTranslator.this.getParameterBinders(); + } + + @Override + public Set getFilterJdbcParameters() { + return AbstractSqlAstTranslator.this.getFilterJdbcParameters(); + } + + @Override + public Set getAffectedTableNames() { + return AbstractSqlAstTranslator.this.getAffectedTableNames(); + } + }; + } + + protected JdbcInsert translateInsert(InsertStatement sqlAst) { + visitInsertStatement( sqlAst ); + + return new JdbcInsert() { + @Override + public String getSql() { + return AbstractSqlAstTranslator.this.getSql(); + } + + @Override + public List getParameterBinders() { + return AbstractSqlAstTranslator.this.getParameterBinders(); + } + + @Override + public Set getAffectedTableNames() { + return AbstractSqlAstTranslator.this.getAffectedTableNames(); + } + + @Override + public Set getFilterJdbcParameters() { + return AbstractSqlAstTranslator.this.getFilterJdbcParameters(); + } + }; + } + + protected JdbcSelect translateSelect(SelectStatement sqlAstSelect) { + logDomainResultGraph( sqlAstSelect.getDomainResultDescriptors() ); + logSqlAst( sqlAstSelect ); + + visitSelectStatement( sqlAstSelect ); + + final int rowsToSkip; + return new JdbcSelect( + getSql(), + getParameterBinders(), + new JdbcValuesMappingProducerStandard( + sqlAstSelect.getQuerySpec().getSelectClause().getSqlSelections(), + sqlAstSelect.getDomainResultDescriptors() + ), + getAffectedTableNames(), + getFilterJdbcParameters(), + rowsToSkip = getRowsToSkip( sqlAstSelect, getJdbcParameterBindings() ), + getMaxRows( sqlAstSelect, getJdbcParameterBindings(), rowsToSkip ), + getAppliedParameterBindings(), + getLockOptions(), + getOffsetParameter(), + getLimitParameter() + ); + } + + protected int getRowsToSkip(SelectStatement sqlAstSelect, JdbcParameterBindings jdbcParameterBindings) { + if ( hasLimit() ) { + if ( offsetParameter != null && needsRowsToSkip() ) { + return interpretExpression( offsetParameter, jdbcParameterBindings ); + } + } + else { + final Expression offsetClauseExpression = sqlAstSelect.getQueryPart().getOffsetClauseExpression(); + if ( offsetClauseExpression != null && needsRowsToSkip() ) { + return interpretExpression( offsetClauseExpression, jdbcParameterBindings ); + } + } + return 0; + } + + protected int getMaxRows(SelectStatement sqlAstSelect, JdbcParameterBindings jdbcParameterBindings, int rowsToSkip) { + if ( hasLimit() ) { + if ( limitParameter != null && needsMaxRows() ) { + final Number fetchCount = interpretExpression( limitParameter, jdbcParameterBindings ); + return rowsToSkip + fetchCount.intValue(); + } + } + else { + final Expression fetchClauseExpression = sqlAstSelect.getQueryPart().getFetchClauseExpression(); + if ( fetchClauseExpression != null && needsMaxRows() ) { + final Number fetchCount = interpretExpression( fetchClauseExpression, jdbcParameterBindings ); + return rowsToSkip + fetchCount.intValue(); + } + } + return Integer.MAX_VALUE; + } + + protected boolean needsRowsToSkip() { + return false; + } + + protected boolean needsMaxRows() { + return false; + } + + protected void prepareLimitOffsetParameters() { + final Limit limit = getLimit(); + if ( limit.getFirstRow() != null ) { + setOffsetParameter( + new JdbcParameterImpl( IntegerType.INSTANCE ) { + @Override + public void bindParameterValue( + PreparedStatement statement, + int startPosition, + JdbcParameterBindings jdbcParamBindings, + ExecutionContext executionContext) throws SQLException { + IntegerType.INSTANCE.getJdbcValueBinder().bind( + statement, + executionContext.getQueryOptions().getLimit().getFirstRow(), + startPosition, + executionContext.getSession() + ); + } + } + ); + } + if ( limit.getMaxRows() != null ) { + setLimitParameter( + new JdbcParameterImpl( IntegerType.INSTANCE ) { + @Override + public void bindParameterValue( + PreparedStatement statement, + int startPosition, + JdbcParameterBindings jdbcParamBindings, + ExecutionContext executionContext) throws SQLException { + IntegerType.INSTANCE.getJdbcValueBinder().bind( + statement, + executionContext.getQueryOptions().getLimit().getMaxRows(), + startPosition, + executionContext.getSession() + ); + } + } + ); + } + } + + @Override + protected void emulateFetchOffsetWithWindowFunctions(QueryPart queryPart, boolean emulateFetchClause) { + if ( queryPart.isRoot() && hasLimit() ) { + prepareLimitOffsetParameters(); + emulateFetchOffsetWithWindowFunctions( + queryPart, + getOffsetParameter(), + getLimitParameter(), + FetchClauseType.ROWS_ONLY, + emulateFetchClause + ); + } + else { + super.emulateFetchOffsetWithWindowFunctions( queryPart, emulateFetchClause ); + } + } + + @Override + protected void renderOffsetFetchClause(QueryPart queryPart, boolean renderOffsetRowsKeyword) { + if ( queryPart.isRoot() && hasLimit() ) { + prepareLimitOffsetParameters(); + renderOffsetFetchClause( + getOffsetParameter(), + getLimitParameter(), + FetchClauseType.ROWS_ONLY, + renderOffsetRowsKeyword + ); + } + else { + super.renderOffsetFetchClause( queryPart, renderOffsetRowsKeyword ); + } + } + + @Override + protected void renderTopClause(QuerySpec querySpec, boolean addOffset) { + if ( querySpec.isRoot() && hasLimit() ) { + prepareLimitOffsetParameters(); + renderTopClause( + getOffsetParameter(), + getLimitParameter(), + FetchClauseType.ROWS_ONLY, + addOffset + ); + } + else { + super.renderTopClause( querySpec, addOffset ); + } + } + + @Override + protected void renderTopStartAtClause(QuerySpec querySpec) { + if ( querySpec.isRoot() && hasLimit() ) { + prepareLimitOffsetParameters(); + renderTopStartAtClause( getOffsetParameter(), getLimitParameter(), FetchClauseType.ROWS_ONLY ); + } + else { + super.renderTopStartAtClause( querySpec ); + } + } + + @Override + protected void renderRowsToClause(QuerySpec querySpec) { + if ( querySpec.isRoot() && hasLimit() ) { + prepareLimitOffsetParameters(); + renderRowsToClause( getOffsetParameter(), getLimitParameter() ); + } + else { + super.renderRowsToClause( querySpec ); + } + } + + @Override + protected void renderFirstSkipClause(QuerySpec querySpec) { + if ( querySpec.isRoot() && hasLimit() ) { + prepareLimitOffsetParameters(); + renderFirstSkipClause( getOffsetParameter(), getLimitParameter() ); + } + else { + super.renderFirstSkipClause( querySpec ); + } + } + + @Override + protected void renderSkipFirstClause(QuerySpec querySpec) { + if ( querySpec.isRoot() && hasLimit() ) { + prepareLimitOffsetParameters(); + renderSkipFirstClause( getOffsetParameter(), getLimitParameter() ); + } + else { + super.renderSkipFirstClause( querySpec ); + } + } + + @Override + protected void renderFirstClause(QuerySpec querySpec) { + if ( querySpec.isRoot() && hasLimit() ) { + prepareLimitOffsetParameters(); + renderFirstClause( getOffsetParameter(), getLimitParameter() ); + } + else { + super.renderFirstClause( querySpec ); + } + } + + @Override + protected void renderCombinedLimitClause(QueryPart queryPart) { + if ( queryPart.isRoot() && hasLimit() ) { + prepareLimitOffsetParameters(); + renderCombinedLimitClause( getOffsetParameter(), getLimitParameter() ); + } + else { + super.renderCombinedLimitClause( queryPart ); + } + } + + @Override + protected void renderLimitOffsetClause(QueryPart queryPart) { + if ( queryPart.isRoot() && hasLimit() ) { + prepareLimitOffsetParameters(); + renderLimitOffsetClause( getOffsetParameter(), getLimitParameter() ); + } + else { + super.renderLimitOffsetClause( queryPart ); + } + } + @Override protected void renderTableGroup(TableGroup tableGroup) { super.renderTableGroup( tableGroup ); @@ -67,7 +605,6 @@ public abstract class AbstractSqlAstTranslator } } - @Override protected void renderTableReference(TableReference tableReference) { super.renderTableReference( tableReference ); @@ -81,4 +618,5 @@ public abstract class AbstractSqlAstTranslator protected void registerAffectedTable(String tableExpression) { affectedTableNames.add( tableExpression ); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java index 38f920f90c..4bbbe6263b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java @@ -6,7 +6,10 @@ */ package org.hibernate.sql.ast.spi; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -14,6 +17,8 @@ import java.util.Locale; import java.util.Set; import java.util.TimeZone; +import org.hibernate.CteSearchClauseKind; +import org.hibernate.FetchClauseType; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.NullPrecedence; import org.hibernate.SortOrder; @@ -43,6 +48,12 @@ import org.hibernate.query.sqm.tree.expression.Conversion; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.ast.tree.cte.CteColumn; +import org.hibernate.sql.ast.tree.cte.CteContainer; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.cte.SearchClauseSpecification; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.expression.Any; import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; @@ -76,6 +87,8 @@ import org.hibernate.sql.ast.tree.from.TableGroupJoin; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.from.TableReferenceJoin; import org.hibernate.sql.ast.tree.from.VirtualTableGroup; +import org.hibernate.sql.ast.tree.insert.InsertStatement; +import org.hibernate.sql.ast.tree.insert.Values; import org.hibernate.sql.ast.tree.predicate.BetweenPredicate; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; import org.hibernate.sql.ast.tree.predicate.ExistsPredicate; @@ -89,11 +102,20 @@ import org.hibernate.sql.ast.tree.predicate.NegatedPredicate; import org.hibernate.sql.ast.tree.predicate.NullnessPredicate; import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.ast.tree.predicate.SelfRenderingPredicate; +import org.hibernate.sql.ast.tree.select.QueryGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.ast.tree.select.SortSpecification; +import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.ExecutionException; import org.hibernate.sql.exec.internal.JdbcParametersImpl; +import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcParameterBinder; +import org.hibernate.sql.exec.spi.JdbcParameterBinding; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.type.IntegerType; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.sql.JdbcLiteralFormatter; @@ -123,9 +145,13 @@ public abstract class AbstractSqlAstWalker private final Set filterJdbcParameters = new HashSet<>(); private final Stack clauseStack = new StandardStack<>(); - protected final Stack querySpecStack = new StandardStack<>(); + private final Stack queryPartStack = new StandardStack<>(); private final Dialect dialect; + private String dmlTargetTableAlias; + private QueryPart queryPartForRowNumbering; + private int queryPartForRowNumberingAliasCounter; + private int queryGroupAliasCounter; private transient AbstractSqmSelfRenderingFunctionDescriptor castFunction; private transient LazySessionWrapperOptions lazySessionWrapperOptions; @@ -257,35 +283,408 @@ public abstract class AbstractSqlAstWalker || clauseStack.getCurrent() == Clause.HAVING; } + protected boolean inOverClause() { + return clauseStack.findCurrentFirst( + clause -> { + if ( clause == Clause.OVER ) { + return true; + } + return null; + } + ) != null; + } + protected Stack getClauseStack() { return clauseStack; } + protected Stack getQueryPartStack() { + return queryPartStack; + } + + @Override + public void visitSelectStatement(SelectStatement statement) { + String oldDmlTargetTableAlias = dmlTargetTableAlias; + dmlTargetTableAlias = null; + try { + visitCteContainer( statement ); + statement.getQueryPart().accept( this ); + } + finally { + dmlTargetTableAlias = oldDmlTargetTableAlias; + } + } + + @Override + public void visitDeleteStatement(DeleteStatement statement) { + String oldDmlTargetTableAlias = dmlTargetTableAlias; + dmlTargetTableAlias = null; + try { + visitCteContainer( statement ); + dmlTargetTableAlias = statement.getTargetTable().getIdentificationVariable(); + visitDeleteStatementOnly( statement ); + } + finally { + dmlTargetTableAlias = oldDmlTargetTableAlias; + } + } + + @Override + public void visitUpdateStatement(UpdateStatement statement) { + String oldDmlTargetTableAlias = dmlTargetTableAlias; + dmlTargetTableAlias = null; + try { + visitCteContainer( statement ); + dmlTargetTableAlias = statement.getTargetTable().getIdentificationVariable(); + visitUpdateStatementOnly( statement ); + } + finally { + dmlTargetTableAlias = oldDmlTargetTableAlias; + } + } + + @Override + public void visitInsertStatement(InsertStatement statement) { + String oldDmlTargetTableAlias = dmlTargetTableAlias; + dmlTargetTableAlias = null; + try { + visitCteContainer( statement ); + visitInsertStatementOnly( statement ); + } + finally { + dmlTargetTableAlias = oldDmlTargetTableAlias; + } + } + + protected void visitDeleteStatementOnly(DeleteStatement statement) { + // todo (6.0) : to support joins we need dialect support + appendSql( "delete from " ); + final Stack clauseStack = getClauseStack(); + try { + clauseStack.push( Clause.DELETE ); + renderTableReference( statement.getTargetTable() ); + } + finally { + clauseStack.pop(); + } + + if ( statement.getRestriction() != null ) { + try { + clauseStack.push( Clause.WHERE ); + appendSql( " where " ); + statement.getRestriction().accept( this ); + } + finally { + clauseStack.pop(); + } + } + visitReturningColumns( statement ); + } + + protected void visitUpdateStatementOnly(UpdateStatement statement) { + // todo (6.0) : to support joins we need dialect support + appendSql( "update " ); + final Stack clauseStack = getClauseStack(); + try { + clauseStack.push( Clause.UPDATE ); + renderTableReference( statement.getTargetTable() ); + } + finally { + clauseStack.pop(); + } + + appendSql( " set " ); + boolean firstPass = true; + try { + clauseStack.push( Clause.SET ); + for ( Assignment assignment : statement.getAssignments() ) { + if ( firstPass ) { + firstPass = false; + } + else { + appendSql( ", " ); + } + + final List columnReferences = assignment.getAssignable().getColumnReferences(); + if ( columnReferences.size() == 1 ) { + columnReferences.get( 0 ).accept( this ); + } + else { + appendSql( " (" ); + for ( ColumnReference columnReference : columnReferences ) { + columnReference.accept( this ); + } + appendSql( ") " ); + } + appendSql( " = " ); + assignment.getAssignedValue().accept( this ); + } + } + finally { + clauseStack.pop(); + } + + if ( statement.getRestriction() != null ) { + appendSql( " where " ); + try { + clauseStack.push( Clause.WHERE ); + statement.getRestriction().accept( this ); + } + finally { + clauseStack.pop(); + } + } + visitReturningColumns( statement ); + } + + protected void visitInsertStatementOnly(InsertStatement statement) { + appendSql( "insert into " ); + appendSql( statement.getTargetTable().getTableExpression() ); + + appendSql( " (" ); + boolean firstPass = true; + + final List targetColumnReferences = statement.getTargetColumnReferences(); + if ( targetColumnReferences == null ) { + renderImplicitTargetColumnSpec(); + } + else { + for (ColumnReference targetColumnReference : targetColumnReferences) { + if (firstPass) { + firstPass = false; + } + else { + appendSql( ", " ); + } + + appendSql( targetColumnReference.getColumnExpression() ); + } + } + + appendSql( ") " ); + + if ( statement.getSourceSelectStatement() != null ) { + statement.getSourceSelectStatement().accept( this ); + } + else { + appendSql("values"); + boolean firstTuple = true; + final Stack clauseStack = getClauseStack(); + try { + clauseStack.push( Clause.VALUES ); + for ( Values values : statement.getValuesList() ) { + if ( firstTuple ) { + firstTuple = false; + } + else { + appendSql( ", " ); + } + appendSql( " (" ); + boolean firstExpr = true; + for ( Expression expression : values.getExpressions() ) { + if ( firstExpr ) { + firstExpr = false; + } + else { + appendSql( ", " ); + } + expression.accept( this ); + } + appendSql( ")" ); + } + } + finally { + clauseStack.pop(); + } + } + visitReturningColumns( statement ); + } + + private void renderImplicitTargetColumnSpec() { + } + + protected void visitReturningColumns(MutationStatement mutationStatement) { + final List returningColumns = mutationStatement.getReturningColumns(); + final int size = returningColumns.size(); + if ( size == 0 ) { + return; + } + + appendSql( " returning " ); + String separator = ""; + for ( int i = 0; i < size; i++ ) { + appendSql( separator ); + appendSql( returningColumns.get( i ).getColumnExpression() ); + separator = ", "; + } + } + + public void visitCteContainer(CteContainer cteContainer) { + final Collection cteStatements = cteContainer.getCteStatements(); + if ( cteStatements.isEmpty() ) { + return; + } + appendSql( "with " ); + + if ( cteContainer.isWithRecursive() ) { + appendSql( "recursive " ); + } + + String mainSeparator = ""; + for ( CteStatement cte : cteStatements ) { + appendSql( mainSeparator ); + appendSql( cte.getCteTable().getTableExpression() ); + + appendSql( " (" ); + + String separator = ""; + + for ( CteColumn cteColumn : cte.getCteTable().getCteColumns() ) { + appendSql( separator ); + appendSql( cteColumn.getColumnExpression() ); + separator = ", "; + } + + appendSql( ") as (" ); + + cte.getCteDefinition().accept( this ); + + appendSql( ')' ); + + renderSearchClause( cte ); + renderCycleClause( cte ); + + mainSeparator = ", "; + } + appendSql( ' ' ); + } + + protected void renderSearchClause(CteStatement cte) { + String separator; + if ( cte.getSearchClauseKind() != null ) { + appendSql( " search " ); + if ( cte.getSearchClauseKind() == CteSearchClauseKind.DEPTH_FIRST ) { + appendSql( " depth " ); + } + else { + appendSql( " breadth " ); + } + appendSql( " first by " ); + separator = ""; + for ( SearchClauseSpecification searchBySpecification : cte.getSearchBySpecifications() ) { + appendSql( separator ); + appendSql( searchBySpecification.getCteColumn().getColumnExpression() ); + if ( searchBySpecification.getSortOrder() != null ) { + if ( searchBySpecification.getSortOrder() == SortOrder.ASCENDING ) { + appendSql( " asc" ); + } + else { + appendSql( " desc" ); + } + if ( searchBySpecification.getNullPrecedence() != null ) { + if ( searchBySpecification.getNullPrecedence() == NullPrecedence.FIRST ) { + appendSql( " nulls first" ); + } + else { + appendSql( " nulls last" ); + } + } + } + separator = ", "; + } + } + } + + protected void renderCycleClause(CteStatement cte) { + String separator; + if ( cte.getCycleMarkColumn() != null ) { + appendSql( " cycle " ); + separator = ""; + for ( CteColumn cycleColumn : cte.getCycleColumns() ) { + appendSql( separator ); + appendSql( cycleColumn.getColumnExpression() ); + separator = ", "; + } + appendSql( " set " ); + appendSql( cte.getCycleMarkColumn().getColumnExpression() ); + appendSql( " to '" ); + appendSql( cte.getCycleValue() ); + appendSql( "' default '" ); + appendSql( cte.getNoCycleValue() ); + appendSql( "'" ); + } + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // QuerySpec @Override - public void visitQuerySpec(QuerySpec querySpec) { + public void visitQueryGroup(QueryGroup queryGroup) { + final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; try { - querySpecStack.push( querySpec ); - if ( !querySpec.isRoot() ) { - appendSql( " (" ); + String queryGroupAlias = null; + if ( queryPartForRowNumbering != queryPartStack.getCurrent() ) { + this.queryPartForRowNumbering = null; + } + // If we do row counting for this query group, the wrapper select is added by the caller + if ( queryPartForRowNumbering != queryGroup && !queryGroup.isRoot() ) { + queryGroupAlias = "grp_" + queryGroupAliasCounter + "_"; + queryGroupAliasCounter++; + appendSql( "select " ); + appendSql( queryGroupAlias ); + appendSql( ".* from (" ); + } + queryPartStack.push( queryGroup ); + final List queryParts = queryGroup.getQueryParts(); + final String setOperatorString = " " + queryGroup.getSetOperator().sqlString() + " "; + String separator = ""; + for ( int i = 0; i < queryParts.size(); i++ ) { + appendSql( separator ); + queryParts.get( i ).accept( this ); + separator = setOperatorString; + } + + visitOrderBy( queryGroup.getSortSpecifications() ); + visitOffsetFetchClause( queryGroup ); + if ( queryGroupAlias != null ) { + appendSql( ") " ); + appendSql( queryGroupAlias ); + } + } + finally { + queryPartStack.pop(); + this.queryPartForRowNumbering = queryPartForRowNumbering; + } + } + + @Override + public void visitQuerySpec(QuerySpec querySpec) { + final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; + try { + if ( queryPartForRowNumbering != queryPartStack.getCurrent() ) { + this.queryPartForRowNumbering = null; + } + final boolean needsParenthesis = !querySpec.isRoot() && !( queryPartStack.getCurrent() instanceof QueryGroup ); + queryPartStack.push( querySpec ); + if ( needsParenthesis ) { + appendSql( "(" ); } visitSelectClause( querySpec.getSelectClause() ); visitFromClause( querySpec.getFromClause() ); visitWhereClause( querySpec ); visitGroupByClause( querySpec, dialect.supportsSelectAliasInGroupByClause() ); visitHavingClause( querySpec ); - visitOrderBy( querySpec ); - visitLimitOffsetClause( querySpec ); + visitOrderBy( querySpec.getSortSpecifications() ); + visitOffsetFetchClause( querySpec ); - if ( !querySpec.isRoot() ) { + if ( needsParenthesis ) { appendSql( ")" ); } } finally { - querySpecStack.push( querySpec ); + queryPartStack.pop(); + this.queryPartForRowNumbering = queryPartForRowNumbering; } } @@ -305,14 +704,17 @@ public abstract class AbstractSqlAstWalker } protected Expression resolveAliasedExpression(Expression expression) { + return resolveAliasedExpression( + queryPartStack.getCurrent().getFirstQuerySpec().getSelectClause().getSqlSelections(), + expression + ); + } + + protected Expression resolveAliasedExpression(List sqlSelections, Expression expression) { if ( expression instanceof Literal ) { Object literalValue = ( (Literal) expression ).getLiteralValue(); if ( literalValue instanceof Integer ) { - return querySpecStack.getCurrent() - .getSelectClause() - .getSqlSelections() - .get( (Integer) literalValue ) - .getExpression(); + return sqlSelections.get( (Integer) literalValue ).getExpression(); } } else if ( expression instanceof SqlSelectionExpression ) { @@ -322,43 +724,12 @@ public abstract class AbstractSqlAstWalker } protected final void visitGroupByClause(QuerySpec querySpec, boolean supportsSelectAliases) { - List groupByClauseExpressions = querySpec.getGroupByClauseExpressions(); - if ( !groupByClauseExpressions.isEmpty() ) { - clauseStack.push( Clause.GROUP ); - String separator = " group by "; + final List partitionExpressions = querySpec.getGroupByClauseExpressions(); + if ( !partitionExpressions.isEmpty() ) { try { - if ( supportsSelectAliases ) { - for ( Expression groupByClauseExpression : groupByClauseExpressions ) { - if ( groupByClauseExpression instanceof SqlTuple ) { - for ( Expression expression : ( (SqlTuple) groupByClauseExpression ).getExpressions() ) { - appendSql( separator ); - renderGroupByItem( expression ); - separator = COMA_SEPARATOR; - } - } - else { - appendSql( separator ); - renderGroupByItem( groupByClauseExpression ); - } - separator = COMA_SEPARATOR; - } - } - else { - for ( Expression groupByClauseExpression : groupByClauseExpressions ) { - if ( groupByClauseExpression instanceof SqlTuple ) { - for ( Expression expression : ( (SqlTuple) groupByClauseExpression ).getExpressions() ) { - appendSql( separator ); - renderGroupByItem( resolveAliasedExpression( expression ) ); - separator = COMA_SEPARATOR; - } - } - else { - appendSql( separator ); - renderGroupByItem( resolveAliasedExpression( groupByClauseExpression ) ); - } - separator = COMA_SEPARATOR; - } - } + clauseStack.push( Clause.GROUP ); + appendSql( " group by " ); + visitPartitionExpressions( partitionExpressions, supportsSelectAliases ); } finally { clauseStack.pop(); @@ -366,7 +737,56 @@ public abstract class AbstractSqlAstWalker } } - protected void renderGroupByItem(Expression expression) { + protected final void visitPartitionByClause(List partitionExpressions) { + if ( !partitionExpressions.isEmpty() ) { + try { + clauseStack.push( Clause.PARTITION ); + appendSql( "partition by " ); + visitPartitionExpressions( partitionExpressions, false ); + } + finally { + clauseStack.pop(); + } + } + } + + protected final void visitPartitionExpressions(List partitionExpressions, boolean supportsSelectAliases) { + String separator = ""; + if ( supportsSelectAliases ) { + for ( Expression partitionExpression : partitionExpressions ) { + if ( partitionExpression instanceof SqlTuple ) { + for ( Expression expression : ( (SqlTuple) partitionExpression ).getExpressions() ) { + appendSql( separator ); + renderPartitionItem( expression ); + separator = COMA_SEPARATOR; + } + } + else { + appendSql( separator ); + renderPartitionItem( partitionExpression ); + } + separator = COMA_SEPARATOR; + } + } + else { + for ( Expression partitionExpression : partitionExpressions ) { + if ( partitionExpression instanceof SqlTuple ) { + for ( Expression expression : ( (SqlTuple) partitionExpression ).getExpressions() ) { + appendSql( separator ); + renderPartitionItem( resolveAliasedExpression( expression ) ); + separator = COMA_SEPARATOR; + } + } + else { + appendSql( separator ); + renderPartitionItem( resolveAliasedExpression( partitionExpression ) ); + } + separator = COMA_SEPARATOR; + } + } + } + + protected void renderPartitionItem(Expression expression) { // We render an empty group instead of literals as some DBs don't support grouping by literals // Note that integer literals, which refer to select item positions, are handled in #visitGroupByClause if ( expression instanceof Literal ) { @@ -434,10 +854,20 @@ public abstract class AbstractSqlAstWalker } } - protected final void visitOrderBy(QuerySpec querySpec) { - final List sortSpecifications = querySpec.getSortSpecifications(); + protected void visitOrderBy(List sortSpecifications) { + // If we have a query part for row numbering, there is no need to render the order by clause + // as that is part of the row numbering window function already, by which we then order by in the outer query + if ( queryPartForRowNumbering == null ) { + renderOrderBy( true, sortSpecifications ); + } + } + + protected void renderOrderBy(boolean addWhitespace, List sortSpecifications) { if ( sortSpecifications != null && !sortSpecifications.isEmpty() ) { - appendSql( " order by " ); + if ( addWhitespace ) { + appendSql( ' ' ); + } + appendSql( "order by " ); clauseStack.push( Clause.ORDER ); try { @@ -659,23 +1089,18 @@ public abstract class AbstractSqlAstWalker } public void visitSortSpecification(Expression sortExpression, SortOrder sortOrder, NullPrecedence nullPrecedence) { - final boolean hasNullPrecedence = nullPrecedence != null && nullPrecedence != NullPrecedence.NONE; - if ( hasNullPrecedence && !dialect.supportsNullPrecedence() ) { - // TODO: generate "virtual" select items and use them here positionally - appendSql( "case when (" ); - resolveAliasedExpression( sortExpression ).accept( this ); - appendSql( ") is null then " ); - if ( nullPrecedence == NullPrecedence.FIRST ) { - appendSql( "0 else 1" ); - } - else { - appendSql( "1 else 0" ); - } - appendSql( " end" ); - appendSql( COMA_SEPARATOR ); + final boolean renderNullPrecedence = nullPrecedence != null && + !nullPrecedence.isDefaultOrdering( sortOrder, dialect.getNullOrdering() ); + if ( renderNullPrecedence && !dialect.supportsNullPrecedence() ) { + emulateSortSpecificationNullPrecedence( sortExpression, nullPrecedence ); } - sortExpression.accept( this ); + if ( inOverClause() ) { + resolveAliasedExpression( sortExpression ).accept( this ); + } + else { + sortExpression.accept( this ); + } if ( sortOrder == SortOrder.ASCENDING ) { appendSql( " asc" ); @@ -684,52 +1109,634 @@ public abstract class AbstractSqlAstWalker appendSql( " desc" ); } - if ( hasNullPrecedence && dialect.supportsNullPrecedence() ) { + if ( renderNullPrecedence && dialect.supportsNullPrecedence() ) { appendSql( " nulls " ); appendSql( nullPrecedence.name().toLowerCase( Locale.ROOT ) ); } } + protected void emulateSortSpecificationNullPrecedence(Expression sortExpression, NullPrecedence nullPrecedence) { + // TODO: generate "virtual" select items and use them here positionally + appendSql( "case when (" ); + resolveAliasedExpression( sortExpression ).accept( this ); + appendSql( ") is null then " ); + if ( nullPrecedence == NullPrecedence.FIRST ) { + appendSql( "0 else 1" ); + } + else { + appendSql( "1 else 0" ); + } + appendSql( " end" ); + appendSql( COMA_SEPARATOR ); + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // LIMIT/OFFSET clause + // LIMIT/OFFSET/FETCH clause @Override - public void visitLimitOffsetClause(QuerySpec querySpec) { - if ( querySpec.getOffsetClauseExpression() != null ) { - renderOffset( querySpec.getOffsetClauseExpression() ); + public void visitOffsetFetchClause(QueryPart queryPart) { + if ( !isRowNumberingCurrentQueryPart() ) { + renderOffsetFetchClause( queryPart, true ); + } + } + + protected void renderOffsetFetchClause(QueryPart queryPart, boolean renderOffsetRowsKeyword) { + renderOffsetFetchClause( + queryPart.getOffsetClauseExpression(), + queryPart.getFetchClauseExpression(), + queryPart.getFetchClauseType(), + renderOffsetRowsKeyword + ); + } + + protected void renderOffsetFetchClause( + Expression offsetExpression, + Expression fetchExpression, + FetchClauseType fetchClauseType, + boolean renderOffsetRowsKeyword) { + if ( offsetExpression != null ) { + renderOffset( offsetExpression, renderOffsetRowsKeyword ); } - if ( querySpec.getLimitClauseExpression() != null ) { - renderLimit( querySpec.getLimitClauseExpression() ); + if ( fetchExpression != null ) { + renderFetch( fetchExpression, null, fetchClauseType ); } } @SuppressWarnings("WeakerAccess") - protected void renderOffset(Expression offsetExpression) { + protected void renderOffset(Expression offsetExpression, boolean renderOffsetRowsKeyword) { appendSql( " offset " ); clauseStack.push( Clause.OFFSET ); try { - offsetExpression.accept( this ); + renderOffsetExpression( offsetExpression ); } finally { clauseStack.pop(); } - appendSql( " rows" ); + if ( renderOffsetRowsKeyword ) { + appendSql( " rows" ); + } } @SuppressWarnings("WeakerAccess") - protected void renderLimit(Expression limitExpression) { + protected void renderFetch( + Expression fetchExpression, + Expression offsetExpressionToAdd, + FetchClauseType fetchClauseType) { appendSql( " fetch first " ); - clauseStack.push( Clause.LIMIT ); + clauseStack.push( Clause.FETCH ); try { - limitExpression.accept( this ); + if ( offsetExpressionToAdd == null ) { + renderFetchExpression( fetchExpression ); + } + else { + renderFetchPlusOffsetExpression( fetchExpression, offsetExpressionToAdd, 0 ); + } } finally { clauseStack.pop(); } - appendSql( " rows only" ); + switch ( fetchClauseType ) { + case ROWS_ONLY: + appendSql( " rows only" ); + break; + case ROWS_WITH_TIES: + appendSql( " rows with ties" ); + break; + case PERCENT_ONLY: + appendSql( " percent rows only" ); + break; + case PERCENT_WITH_TIES: + appendSql( " percent rows with ties" ); + break; + } } + protected void renderOffsetExpression(Expression offsetExpression) { + offsetExpression.accept( this ); + } + + protected void renderFetchExpression(Expression fetchExpression) { + fetchExpression.accept( this ); + } + + protected void renderTopClause(QuerySpec querySpec, boolean addOffset) { + renderTopClause( + querySpec.getOffsetClauseExpression(), + querySpec.getFetchClauseExpression(), + querySpec.getFetchClauseType(), + addOffset + ); + } + + protected void renderTopClause( + Expression offsetExpression, + Expression fetchExpression, + FetchClauseType fetchClauseType, + boolean addOffset) { + if ( fetchExpression != null ) { + appendSql( "top (" ); + final Stack clauseStack = getClauseStack(); + clauseStack.push( Clause.FETCH ); + try { + if ( addOffset && offsetExpression != null ) { + renderFetchPlusOffsetExpression( fetchExpression, offsetExpression, 0 ); + } + else { + renderFetchExpression( fetchExpression ); + } + } + finally { + clauseStack.pop(); + } + appendSql( ") " ); + switch ( fetchClauseType ) { + case ROWS_WITH_TIES: + appendSql( "with ties " ); + break; + case PERCENT_ONLY: + appendSql( "percent " ); + break; + case PERCENT_WITH_TIES: + appendSql( "percent with ties " ); + break; + } + } + } + + protected void renderTopStartAtClause(QuerySpec querySpec) { + renderTopStartAtClause( + querySpec.getOffsetClauseExpression(), + querySpec.getFetchClauseExpression(), + querySpec.getFetchClauseType() + ); + } + + protected void renderTopStartAtClause( + Expression offsetExpression, + Expression fetchExpression, + FetchClauseType fetchClauseType) { + if ( fetchExpression != null ) { + appendSql( "top " ); + final Stack clauseStack = getClauseStack(); + clauseStack.push( Clause.FETCH ); + try { + renderFetchExpression( fetchExpression ); + } + finally { + clauseStack.pop(); + } + if ( offsetExpression != null ) { + clauseStack.push( Clause.OFFSET ); + try { + appendSql( " start at " ); + renderOffsetExpression( offsetExpression ); + } + finally { + clauseStack.pop(); + } + } + appendSql( ' ' ); + switch ( fetchClauseType ) { + case ROWS_WITH_TIES: + appendSql( "with ties " ); + break; + case PERCENT_ONLY: + appendSql( "percent " ); + break; + case PERCENT_WITH_TIES: + appendSql( "percent with ties " ); + break; + } + } + } + + protected void renderRowsToClause(QuerySpec querySpec) { + assertRowsOnlyFetchClauseType( querySpec ); + renderRowsToClause( querySpec.getOffsetClauseExpression(), querySpec.getFetchClauseExpression() ); + } + + protected void renderRowsToClause(Expression offsetClauseExpression, Expression fetchClauseExpression) { + if ( fetchClauseExpression != null ) { + appendSql( "rows " ); + final Stack clauseStack = getClauseStack(); + clauseStack.push( Clause.FETCH ); + try { + renderFetchExpression( fetchClauseExpression ); + } + finally { + clauseStack.pop(); + } + if ( offsetClauseExpression != null ) { + clauseStack.push( Clause.OFFSET ); + try { + appendSql( " to " ); + // According to RowsLimitHandler this is 1 based so we need to add 1 to the offset + renderFetchPlusOffsetExpression( fetchClauseExpression, offsetClauseExpression, 1 ); + } + finally { + clauseStack.pop(); + } + } + appendSql( ' ' ); + } + } + + protected void renderFetchPlusOffsetExpression( + Expression fetchClauseExpression, + Expression offsetClauseExpression, + int offset) { + renderFetchExpression( fetchClauseExpression ); + appendSql( '+' ); + renderOffsetExpression( offsetClauseExpression ); + if ( offset != 0 ) { + appendSql( '+' ); + appendSql( Integer.toString( offset ) ); + } + } + + protected void renderFetchPlusOffsetExpressionAsSingleParameter( + Expression fetchClauseExpression, + Expression offsetClauseExpression, + int offset) { + if ( fetchClauseExpression instanceof Literal ) { + final Number fetchCount = (Number) ( (Literal) fetchClauseExpression ).getLiteralValue(); + if ( offsetClauseExpression instanceof Literal ) { + final Number offsetCount = (Number) ( (Literal) offsetClauseExpression ).getLiteralValue(); + appendSql( Integer.toString( fetchCount.intValue() + offsetCount.intValue() + offset ) ); + } + else { + appendSql( PARAM_MARKER ); + final JdbcParameter offsetParameter = (JdbcParameter) offsetClauseExpression; + final int offsetValue = offset + fetchCount.intValue(); + jdbcParameters.addParameter( offsetParameter ); + parameterBinders.add( + (statement, startPosition, jdbcParameterBindings, executionContext) -> { + final JdbcParameterBinding binding = jdbcParameterBindings.getBinding( offsetParameter ); + if ( binding == null ) { + throw new ExecutionException( "JDBC parameter value not bound - " + offsetParameter ); + } + final Number bindValue = (Number) binding.getBindValue(); + offsetParameter.getExpressionType().getJdbcMappings().get( 0 ).getJdbcValueBinder().bind( + statement, + bindValue.intValue() + offsetValue, + startPosition, + executionContext.getSession() + ); + } + ); + } + } + else { + appendSql( PARAM_MARKER ); + final JdbcParameter offsetParameter = (JdbcParameter) offsetClauseExpression; + final JdbcParameter fetchParameter = (JdbcParameter) fetchClauseExpression; + final OffsetReceivingParameterBinder fetchBinder = new OffsetReceivingParameterBinder( + fetchParameter, + offset + ); + jdbcParameters.addParameter( fetchParameter ); + parameterBinders.add( fetchBinder ); + jdbcParameters.addParameter( offsetParameter ); + parameterBinders.add( + (statement, startPosition, jdbcParameterBindings, executionContext) -> { + final JdbcParameterBinding binding = jdbcParameterBindings.getBinding( offsetParameter ); + if ( binding == null ) { + throw new ExecutionException( "JDBC parameter value not bound - " + offsetParameter ); + } + fetchBinder.dynamicOffset = (Number) binding.getBindValue(); + } + ); + } + } + + private static class OffsetReceivingParameterBinder implements JdbcParameterBinder { + + private final JdbcParameter fetchParameter; + private final int staticOffset; + private Number dynamicOffset; + + public OffsetReceivingParameterBinder(JdbcParameter fetchParameter, int staticOffset) { + this.fetchParameter = fetchParameter; + this.staticOffset = staticOffset; + } + + @Override + public void bindParameterValue( + PreparedStatement statement, + int startPosition, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) throws SQLException { + final JdbcParameterBinding binding = jdbcParameterBindings.getBinding( fetchParameter ); + if ( binding == null ) { + throw new ExecutionException( "JDBC parameter value not bound - " + fetchParameter ); + } + final Number bindValue = (Number) binding.getBindValue(); + final int offsetValue = dynamicOffset.intValue() + staticOffset; + dynamicOffset = null; + fetchParameter.getExpressionType().getJdbcMappings().get( 0 ).getJdbcValueBinder().bind( + statement, + bindValue.intValue() + offsetValue, + startPosition, + executionContext.getSession() + ); + } + } + + protected void renderFirstSkipClause(QuerySpec querySpec) { + assertRowsOnlyFetchClauseType( querySpec ); + renderFirstSkipClause( querySpec.getOffsetClauseExpression(), querySpec.getFetchClauseExpression() ); + } + + protected void renderFirstSkipClause(Expression offsetExpression, Expression fetchExpression) { + final Stack clauseStack = getClauseStack(); + if ( fetchExpression != null ) { + appendSql( "first " ); + clauseStack.push( Clause.FETCH ); + try { + renderFetchExpression( fetchExpression ); + } + finally { + clauseStack.pop(); + } + appendSql( ' ' ); + } + if ( offsetExpression != null ) { + appendSql( "skip " ); + clauseStack.push( Clause.OFFSET ); + try { + renderOffsetExpression( offsetExpression ); + } + finally { + clauseStack.pop(); + } + appendSql( ' ' ); + } + } + + protected void renderSkipFirstClause(QuerySpec querySpec) { + assertRowsOnlyFetchClauseType( querySpec ); + renderSkipFirstClause( querySpec.getOffsetClauseExpression(), querySpec.getFetchClauseExpression() ); + } + + protected void renderSkipFirstClause(Expression offsetExpression, Expression fetchExpression) { + final Stack clauseStack = getClauseStack(); + if ( offsetExpression != null ) { + appendSql( "skip " ); + clauseStack.push( Clause.OFFSET ); + try { + renderOffsetExpression( offsetExpression ); + } + finally { + clauseStack.pop(); + } + appendSql( ' ' ); + } + if ( fetchExpression != null ) { + appendSql( "first " ); + clauseStack.push( Clause.FETCH ); + try { + renderFetchExpression( fetchExpression ); + } + finally { + clauseStack.pop(); + } + appendSql( ' ' ); + } + } + + protected void renderFirstClause(QuerySpec querySpec) { + assertRowsOnlyFetchClauseType( querySpec ); + renderFirstClause( querySpec.getOffsetClauseExpression(), querySpec.getFetchClauseExpression() ); + } + + protected void renderFirstClause(Expression offsetExpression, Expression fetchExpression) { + final Stack clauseStack = getClauseStack(); + if ( fetchExpression != null ) { + appendSql( "first " ); + clauseStack.push( Clause.FETCH ); + try { + renderFetchPlusOffsetExpression( fetchExpression, offsetExpression, 0 ); + } + finally { + clauseStack.pop(); + } + appendSql( ' ' ); + } + } + + protected void renderCombinedLimitClause(QueryPart queryPart) { + assertRowsOnlyFetchClauseType( queryPart ); + renderCombinedLimitClause( queryPart.getOffsetClauseExpression(), queryPart.getFetchClauseExpression() ); + } + + protected void renderCombinedLimitClause(Expression offsetExpression, Expression fetchExpression) { + if ( offsetExpression != null ) { + final Stack clauseStack = getClauseStack(); + appendSql( " limit " ); + clauseStack.push( Clause.OFFSET ); + try { + renderOffsetExpression( offsetExpression ); + } + finally { + clauseStack.pop(); + } + appendSql( COMA_SEPARATOR ); + if ( fetchExpression != null ) { + clauseStack.push( Clause.FETCH ); + try { + renderFetchExpression( fetchExpression ); + } + finally { + clauseStack.pop(); + } + } + else { + appendSql( Integer.toString( Integer.MAX_VALUE ) ); + } + } + else if ( fetchExpression != null ) { + final Stack clauseStack = getClauseStack(); + appendSql( " limit " ); + clauseStack.push( Clause.FETCH ); + try { + renderFetchExpression( fetchExpression ); + } + finally { + clauseStack.pop(); + } + } + } + + protected void renderLimitOffsetClause(QueryPart queryPart) { + assertRowsOnlyFetchClauseType( queryPart ); + renderLimitOffsetClause( queryPart.getOffsetClauseExpression(), queryPart.getFetchClauseExpression() ); + } + + protected void renderLimitOffsetClause(Expression offsetExpression, Expression fetchExpression) { + if ( fetchExpression != null ) { + appendSql( " limit " ); + clauseStack.push( Clause.FETCH ); + try { + renderFetchExpression( fetchExpression ); + } + finally { + clauseStack.pop(); + } + } + else if ( offsetExpression != null ) { + appendSql( " limit " ); + appendSql( Integer.toString( Integer.MAX_VALUE ) ); + } + if ( offsetExpression != null ) { + final Stack clauseStack = getClauseStack(); + appendSql( " offset " ); + clauseStack.push( Clause.OFFSET ); + try { + renderOffsetExpression( offsetExpression ); + } + finally { + clauseStack.pop(); + } + } + } + + protected void assertRowsOnlyFetchClauseType(QueryPart queryPart) { + final FetchClauseType fetchClauseType = queryPart.getFetchClauseType(); + if ( fetchClauseType != null && fetchClauseType != FetchClauseType.ROWS_ONLY ) { + throw new IllegalArgumentException( "Can't emulate fetch clause type: " + fetchClauseType ); + } + } + + protected QueryPart getQueryPartForRowNumbering() { + return queryPartForRowNumbering; + } + + protected boolean isRowNumberingCurrentQueryPart() { + return queryPartForRowNumbering != null; + } + + protected void emulateFetchOffsetWithWindowFunctions(QueryPart queryPart, boolean emulateFetchClause) { + emulateFetchOffsetWithWindowFunctions( + queryPart, + queryPart.getOffsetClauseExpression(), + queryPart.getFetchClauseExpression(), + queryPart.getFetchClauseType(), + emulateFetchClause + ); + } + + protected void emulateFetchOffsetWithWindowFunctions( + QueryPart queryPart, + Expression offsetExpression, + Expression fetchExpression, + FetchClauseType fetchClauseType, + boolean emulateFetchClause) { + final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; + try { + this.queryPartForRowNumbering = queryPart; + final String alias = "r_" + queryPartForRowNumberingAliasCounter + "_"; + queryPartForRowNumberingAliasCounter++; + appendSql( "select " ); + if ( getClauseStack().isEmpty() ) { + appendSql( "*" ); + } + else { + final int size = queryPart.getFirstQuerySpec().getSelectClause().getSqlSelections().size(); + String separator = ""; + for ( int i = 0; i < size; i++ ) { + appendSql( separator ); + appendSql( alias ); + appendSql( ".c" ); + appendSql( Integer.toString( i ) ); + separator = COMA_SEPARATOR; + } + } + appendSql( " from (" ); + queryPart.accept( this ); + appendSql( " ) "); + appendSql( alias ); + appendSql( " where " ); + final Stack clauseStack = getClauseStack(); + clauseStack.push( Clause.WHERE ); + try { + if ( emulateFetchClause ) { + switch ( fetchClauseType ) { + case PERCENT_ONLY: + appendSql( alias ); + appendSql( ".rn <= " ); + if ( offsetExpression != null ) { + offsetExpression.accept( this ); + appendSql( " + " ); + } + appendSql( "ceil("); + appendSql( alias ); + appendSql( ".cnt * " ); + fetchExpression.accept( this ); + appendSql( " / 100 )" ); + break; + case ROWS_ONLY: + appendSql( alias ); + appendSql( ".rn <= " ); + fetchExpression.accept( this ); + break; + case PERCENT_WITH_TIES: + appendSql( alias ); + appendSql( ".rnk <= " ); + if ( offsetExpression != null ) { + offsetExpression.accept( this ); + appendSql( " + " ); + } + appendSql( "ceil("); + appendSql( alias ); + appendSql( ".cnt * " ); + fetchExpression.accept( this ); + appendSql( " / 100 )" ); + break; + case ROWS_WITH_TIES: + appendSql( alias ); + appendSql( ".rnk <= " ); + fetchExpression.accept( this ); + break; + } + } + // todo: not sure if databases handle order by row number or the original ordering better.. + if ( offsetExpression == null ) { + switch ( fetchClauseType ) { + case PERCENT_ONLY: + case ROWS_ONLY: + appendSql( " order by " ); + appendSql( alias ); + appendSql( ".rn" ); + break; + case PERCENT_WITH_TIES: + case ROWS_WITH_TIES: + appendSql( " order by " ); + appendSql( alias ); + appendSql( ".rnk" ); + break; + } + } + else { + if ( emulateFetchClause ) { + appendSql( " and " ); + } + appendSql( alias ); + appendSql( ".rn > " ); + offsetExpression.accept( this ); + appendSql( " order by " ); + appendSql( alias ); + appendSql( ".rn" ); + } + } + finally { + clauseStack.pop(); + } + } + finally { + this.queryPartForRowNumbering = queryPartForRowNumbering; + } + } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // SELECT clause @@ -743,19 +1750,139 @@ public abstract class AbstractSqlAstWalker if ( selectClause.isDistinct() ) { appendSql( "distinct " ); } + visitSqlSelections( selectClause ); + } + finally { + clauseStack.pop(); + } + } + protected void visitSqlSelections(SelectClause selectClause) { + final List sqlSelections = selectClause.getSqlSelections(); + final int size = sqlSelections.size(); + if ( queryPartForRowNumbering == null ) { String separator = NO_SEPARATOR; - for ( SqlSelection sqlSelection : selectClause.getSqlSelections() ) { + for ( int i = 0; i < size; i++ ) { + final SqlSelection sqlSelection = sqlSelections.get( i ); appendSql( separator ); sqlSelection.accept( this ); separator = COMA_SEPARATOR; } } + else { + for ( int i = 0; i < size; i++ ) { + final SqlSelection sqlSelection = sqlSelections.get( i ); + sqlSelection.accept( this ); + appendSql( " c" ); + appendSql( Integer.toString( i ) ); + appendSql( COMA_SEPARATOR ); + } + + switch ( getFetchClauseTypeForRowNumbering( queryPartForRowNumbering ) ) { + case PERCENT_ONLY: + appendSql( "count(*) over () cnt," ); + case ROWS_ONLY: + renderRowNumber( selectClause, queryPartForRowNumbering ); + appendSql( " rn " ); + break; + case PERCENT_WITH_TIES: + appendSql( "count(*) over () cnt," ); + case ROWS_WITH_TIES: + if ( queryPartForRowNumbering.getOffsetClauseExpression() != null ) { + renderRowNumber( selectClause, queryPartForRowNumbering ); + appendSql( " rn, " ); + } + if ( selectClause.isDistinct() ) { + appendSql( "dense_rank()" ); + } + else { + appendSql( "rank()" ); + } + visitOverClause( + Collections.emptyList(), + getSortSpecificationsRowNumbering( selectClause, queryPartForRowNumbering ) + ); + appendSql( " rnk" ); + break; + } + } + } + + protected FetchClauseType getFetchClauseTypeForRowNumbering(QueryPart queryPartForRowNumbering) { + return queryPartForRowNumbering.getFetchClauseType(); + } + + protected void visitOverClause( + List partitionExpressions, + List sortSpecifications) { + try { + clauseStack.push( Clause.OVER ); + appendSql( " over (" ); + visitPartitionByClause( partitionExpressions ); + renderOrderBy( !partitionExpressions.isEmpty(), sortSpecifications ); + appendSql( ')' ); + } finally { clauseStack.pop(); } } + protected void renderRowNumber(SelectClause selectClause, QueryPart queryPart) { + if ( selectClause.isDistinct() ) { + appendSql( "dense_rank()" ); + } + else { + appendSql( "row_number()" ); + } + visitOverClause( Collections.emptyList(), getSortSpecificationsRowNumbering( selectClause, queryPart ) ); + } + + protected List getSortSpecificationsRowNumbering( + SelectClause selectClause, + QueryPart queryPart) { + final List sortSpecifications = queryPart.getSortSpecifications(); + if ( selectClause.isDistinct() ) { + // When select distinct is used, we need to add all select items to the order by clause + final List sqlSelections = new ArrayList<>( selectClause.getSqlSelections() ); + final int specificationsSize = sortSpecifications.size(); + for ( int i = sqlSelections.size() - 1; i != 0; i-- ) { + final Expression selectionExpression = sqlSelections.get( i ).getExpression(); + for ( int j = 0; j < specificationsSize; j++ ) { + final Expression expression = resolveAliasedExpression( + sqlSelections, + sortSpecifications.get( j ).getSortExpression() + ); + if ( expression.equals( selectionExpression ) ) { + sqlSelections.remove( i ); + break; + } + } + } + final int sqlSelectionsSize = sqlSelections.size(); + if ( sqlSelectionsSize == 0 ) { + return sortSpecifications; + } + else { + final List sortSpecificationsRowNumbering = new ArrayList<>( sqlSelectionsSize + specificationsSize ); + sortSpecificationsRowNumbering.addAll( sortSpecifications ); + for ( int i = 0; i < sqlSelectionsSize; i++ ) { + sortSpecifications.add( + new SortSpecification( + new SqlSelectionExpression( sqlSelections.get( i ) ), + null, + SortOrder.ASCENDING, + NullPrecedence.NONE + ) + ); + } + return sortSpecificationsRowNumbering; + } + } + else { + return sortSpecifications; + } + } + @Override public void visitSqlSelection(SqlSelection sqlSelection) { // do nothing... this is handled #visitSelectClause @@ -955,7 +2082,17 @@ public abstract class AbstractSqlAstWalker @Override public void visitColumnReference(ColumnReference columnReference) { - appendSql( columnReference.getExpressionText() ); + 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 + appendSql( columnReference.getColumnExpression() ); + } + else { + appendSql( columnReference.getExpressionText() ); + } } @Override @@ -1042,7 +2179,8 @@ public abstract class AbstractSqlAstWalker @Override public void visitCollate(Collate collate) { collate.getExpression().accept( this ); - dialect.appendCollate( this, collate.getCollation() ); + appendSql( " collate " ); + appendSql( collate.getCollation() ); } @Override @@ -1437,11 +2575,12 @@ public abstract class AbstractSqlAstWalker if ( literalFormatter == null ) { parameterBinders.add( literal ); + final LiteralAsParameter jdbcParameter = new LiteralAsParameter<>( literal ); if ( clauseStack.getCurrent() == Clause.SELECT && dialect.requiresCastingOfParametersInSelectClause() ) { - castFunction().render( this, Collections.singletonList( new LiteralAsParameter<>( literal ) ), this ); + castFunction().render( this, Collections.singletonList( jdbcParameter ), this ); } else { - parameterBinders.add( literal ); + appendSql( PARAM_MARKER ); } } else { @@ -1456,6 +2595,29 @@ public abstract class AbstractSqlAstWalker } } + protected void renderAsLiteral(JdbcParameter jdbcParameter, Object literalValue) { + if ( literalValue == null ) { + appendSql( SqlAppender.NULL_KEYWORD ); + } + else { + assert jdbcParameter.getExpressionType().getJdbcTypeCount() == 1; + final JdbcMapping jdbcMapping = jdbcParameter.getExpressionType().getJdbcMappings().get( 0 ); + final JdbcLiteralFormatter literalFormatter = jdbcMapping.getSqlTypeDescriptor().getJdbcLiteralFormatter( jdbcMapping.getJavaTypeDescriptor() ); + if ( literalFormatter == null ) { + throw new IllegalArgumentException( "Can't render parameter as literal, no literal formatter found" ); + } + else { + appendSql( + literalFormatter.toJdbcLiteral( + literalValue, + dialect, + getWrapperOptions() + ) + ); + } + } + } + @Override public void visitUnaryOperationExpression(UnaryOperation unaryOperationExpression) { if ( unaryOperationExpression.getOperator() == UnaryArithmeticOperator.UNARY_PLUS ) { @@ -1632,7 +2794,7 @@ public abstract class AbstractSqlAstWalker appendSql( " not" ); } appendSql( " in " ); - visitQuerySpec( inSubQueryPredicate.getSubQuery() ); + inSubQueryPredicate.getSubQuery().accept( this ); } else if ( !dialect.supportsRowValueConstructorSyntaxInInSubquery() ) { emulateTupleSubQueryPredicate( @@ -1649,7 +2811,7 @@ public abstract class AbstractSqlAstWalker appendSql( " not" ); } appendSql( " in " ); - visitQuerySpec( inSubQueryPredicate.getSubQuery() ); + inSubQueryPredicate.getSubQuery().accept( this ); } } else { @@ -1658,24 +2820,28 @@ public abstract class AbstractSqlAstWalker appendSql( " not" ); } appendSql( " in " ); - visitQuerySpec( inSubQueryPredicate.getSubQuery() ); + inSubQueryPredicate.getSubQuery().accept( this ); } } protected void emulateTupleSubQueryPredicate( Predicate predicate, boolean negated, - QuerySpec subQuery, + QueryPart queryPart, SqlTuple lhsTuple, ComparisonOperator tupleComparisonOperator) { - if ( subQuery.getLimitClauseExpression() == null && subQuery.getOffsetClauseExpression() == null ) { + final QuerySpec subQuery; + if ( queryPart instanceof QuerySpec && queryPart.getFetchClauseExpression() == null && queryPart.getOffsetClauseExpression() == null ) { + subQuery = (QuerySpec) queryPart; // We can only emulate the tuple sub query predicate as exists predicate when there are no limit/offsets if ( negated ) { appendSql( "not " ); } + final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; try { - querySpecStack.push( subQuery ); + this.queryPartForRowNumbering = null; + queryPartStack.push( subQuery ); appendSql( "exists (select 1" ); visitFromClause( subQuery.getFromClause() ); @@ -1729,12 +2895,13 @@ public abstract class AbstractSqlAstWalker appendSql( ")" ); } finally { - querySpecStack.pop(); + queryPartStack.pop(); + this.queryPartForRowNumbering = queryPartForRowNumbering; } } else { // TODO: We could use nested queries and use row numbers to emulate this - throw new IllegalArgumentException( "Can't emulate in predicate with tuples and limit/offset: " + predicate ); + throw new IllegalArgumentException( "Can't emulate in predicate with tuples and limit/offset or set operations: " + predicate ); } } @@ -1744,18 +2911,22 @@ public abstract class AbstractSqlAstWalker */ protected void emulateQuantifiedTupleSubQueryPredicate( Predicate predicate, - QuerySpec subQuery, + QueryPart queryPart, SqlTuple lhsTuple, ComparisonOperator tupleComparisonOperator) { - if ( subQuery.getLimitClauseExpression() == null && subQuery.getOffsetClauseExpression() == null ) { + final QuerySpec subQuery; + if ( queryPart instanceof QuerySpec && queryPart.getFetchClauseExpression() == null && queryPart.getOffsetClauseExpression() == null ) { + subQuery = (QuerySpec) queryPart; // We can only emulate the tuple sub query predicate as exists predicate when there are no limit/offsets lhsTuple.accept( this ); appendSql( " " ); appendSql( tupleComparisonOperator.sqlText() ); appendSql( " " ); + final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; try { - querySpecStack.push( subQuery ); + this.queryPartForRowNumbering = null; + queryPartStack.push( subQuery ); appendSql( "(" ); visitSelectClause( subQuery.getSelectClause() ); visitFromClause( subQuery.getFromClause() ); @@ -1780,16 +2951,17 @@ public abstract class AbstractSqlAstWalker appendSql( Integer.toString( i + 1 ) ); appendSql( order ); } - renderLimit( ONE_LITERAL ); + renderFetch( ONE_LITERAL, null, FetchClauseType.ROWS_ONLY ); appendSql( ")" ); } finally { - querySpecStack.pop(); + queryPartStack.pop(); + this.queryPartForRowNumbering = queryPartForRowNumbering; } } else { // TODO: We could use nested queries and use row numbers to emulate this - throw new IllegalArgumentException( "Can't emulate in predicate with tuples and limit/offset: " + predicate ); + throw new IllegalArgumentException( "Can't emulate in predicate with tuples and limit/offset or set operations: " + predicate ); } } @@ -1889,11 +3061,11 @@ public abstract class AbstractSqlAstWalker if ( ( lhsTuple = getTuple( comparisonPredicate.getLeftHandExpression() ) ) != null ) { final Expression rhsExpression = comparisonPredicate.getRightHandExpression(); final boolean all; - final QuerySpec subquery; + final QueryPart subquery; // Handle emulation of quantified comparison - if ( rhsExpression instanceof QuerySpec ) { - subquery = (QuerySpec) rhsExpression; + if ( rhsExpression instanceof QueryPart ) { + subquery = (QueryPart) rhsExpression; all = true; } else if ( rhsExpression instanceof Every ) { @@ -1976,8 +3148,8 @@ public abstract class AbstractSqlAstWalker else if ( ( rhsTuple = getTuple( comparisonPredicate.getRightHandExpression() ) ) != null ) { final Expression lhsExpression = comparisonPredicate.getLeftHandExpression(); - if ( lhsExpression instanceof QuerySpec ) { - final QuerySpec subquery = (QuerySpec) lhsExpression; + if ( lhsExpression instanceof QueryGroup ) { + final QueryGroup subquery = (QueryGroup) lhsExpression; if ( rhsTuple.getExpressions().size() == 1 ) { // Special case for tuples with arity 1 as any DBMS supports scalar IN predicates diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SimpleFromClauseAccessImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SimpleFromClauseAccessImpl.java index 85a8710675..204dbe9689 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SimpleFromClauseAccessImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SimpleFromClauseAccessImpl.java @@ -19,14 +19,25 @@ import org.hibernate.sql.ast.tree.from.TableGroup; * @author Steve Ebersole */ public class SimpleFromClauseAccessImpl implements FromClauseAccess { + + protected final FromClauseAccess parent; protected final Map tableGroupMap = new HashMap<>(); public SimpleFromClauseAccessImpl() { + this( null ); + } + + public SimpleFromClauseAccessImpl(FromClauseAccess parent) { + this.parent = parent; } @Override public TableGroup findTableGroup(NavigablePath navigablePath) { - return tableGroupMap.get( navigablePath.getIdentifierForTableGroup() ); + final TableGroup tableGroup = tableGroupMap.get( navigablePath.getIdentifierForTableGroup() ); + if ( tableGroup == null && parent != null ) { + return parent.findTableGroup( navigablePath ); + } + return tableGroup; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstQuerySpecProcessingState.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstQueryPartProcessingState.java similarity index 67% rename from hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstQuerySpecProcessingState.java rename to hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstQueryPartProcessingState.java index d82e126b0b..5f90ca5253 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstQuerySpecProcessingState.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstQueryPartProcessingState.java @@ -6,16 +6,16 @@ */ package org.hibernate.sql.ast.spi; -import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.QueryPart; /** * SqlAstProcessingState specialization for * @author Steve Ebersole */ -public interface SqlAstQuerySpecProcessingState extends SqlAstProcessingState { +public interface SqlAstQueryPartProcessingState extends SqlAstProcessingState { /** - * Get the QuerySpec being processed as part of this state. It is + * Get the QueryPart being processed as part of this state. It is * considered in-flight as it is probably still being built. */ - QuerySpec getInflightQuerySpec(); + QueryPart getInflightQueryPart(); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstToJdbcOperationConverter.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstToJdbcOperationConverter.java deleted file mode 100644 index 4a178ac8a4..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstToJdbcOperationConverter.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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.sql.ast.spi; - -import java.util.Set; - -import org.hibernate.sql.ast.SqlAstWalker; -import org.hibernate.sql.ast.tree.cte.CteStatement; -import org.hibernate.sql.exec.spi.JdbcOperation; -import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators; - -/** - * Base contract for {@link SqlAstWalker} implementations that convert - * SQL AST into a {@link JdbcOperation} - * - * @author Steve Ebersole - */ -public interface SqlAstToJdbcOperationConverter extends SqlAstWalker, SqlTypeDescriptorIndicators { - Set getAffectedTableNames(); - - JdbcOperation translate(CteStatement cteStatement); -} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstDeleteTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstDeleteTranslator.java deleted file mode 100644 index 98fdf903c7..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstDeleteTranslator.java +++ /dev/null @@ -1,154 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.sql.ast.spi; - -import java.util.List; -import java.util.Set; - -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.FilterJdbcParameter; -import org.hibernate.internal.util.collections.Stack; -import org.hibernate.sql.ast.Clause; -import org.hibernate.sql.ast.tree.cte.CteColumn; -import org.hibernate.sql.ast.SqlAstDeleteTranslator; -import org.hibernate.sql.ast.tree.cte.CteStatement; -import org.hibernate.sql.ast.tree.delete.DeleteStatement; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.exec.spi.JdbcDelete; -import org.hibernate.sql.exec.spi.JdbcParameterBinder; - -/** - * @author Steve Ebersole - */ -public class StandardSqlAstDeleteTranslator extends AbstractSqlAstTranslator implements SqlAstDeleteTranslator { - public StandardSqlAstDeleteTranslator(SessionFactoryImplementor sessionFactory) { - super( sessionFactory ); - } - - private String deletingTableAlias; - - @Override - public JdbcDelete translate(DeleteStatement sqlAst) { - try { - deletingTableAlias = sqlAst.getTargetTable().getIdentificationVariable(); - // todo (6.0) : to support joins we need dialect support - appendSql( "delete from " ); - final Stack clauseStack = getClauseStack(); - try { - clauseStack.push( Clause.DELETE ); - renderTableReference( sqlAst.getTargetTable() ); - } - finally { - clauseStack.pop(); - } - - if ( sqlAst.getRestriction() != null ) { - try { - clauseStack.push( Clause.WHERE ); - appendSql( " where " ); - sqlAst.getRestriction().accept( this ); - } - finally { - clauseStack.pop(); - } - } - - return new JdbcDelete() { - @Override - public String getSql() { - return StandardSqlAstDeleteTranslator.this.getSql(); - } - - @Override - public List getParameterBinders() { - return StandardSqlAstDeleteTranslator.this.getParameterBinders(); - } - - @Override - public Set getAffectedTableNames() { - return StandardSqlAstDeleteTranslator.this.getAffectedTableNames(); - } - - @Override - public Set getFilterJdbcParameters() { - return StandardSqlAstDeleteTranslator.this.getFilterJdbcParameters(); - } - }; - } - finally { - cleanup(); - } - } - - @Override - public void visitColumnReference(ColumnReference columnReference) { - if ( deletingTableAlias != null && deletingTableAlias.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 - appendSql( columnReference.getColumnExpression() ); - } - else { - super.visitColumnReference( columnReference ); - } - } - - @Override - public JdbcDelete translate(CteStatement sqlAst) { - assert sqlAst.getCteConsumer() instanceof DeleteStatement; - try { - appendSql( "with " ); - appendSql( sqlAst.getCteLabel() ); - - appendSql( " (" ); - - String separator = ""; - - for ( CteColumn cteColumn : sqlAst.getCteTable().getCteColumns() ) { - appendSql( separator ); - appendSql( cteColumn.getColumnExpression() ); - separator = ", "; - } - - appendSql( ") as (" ); - - visitQuerySpec( sqlAst.getCteDefinition() ); - - appendSql( ") " ); - - translate( (DeleteStatement) sqlAst.getCteConsumer() ); - - return new JdbcDelete() { - @Override - public String getSql() { - return StandardSqlAstDeleteTranslator.this.getSql(); - } - - @Override - public List getParameterBinders() { - return StandardSqlAstDeleteTranslator.this.getParameterBinders(); - } - - @Override - public Set getAffectedTableNames() { - return StandardSqlAstDeleteTranslator.this.getAffectedTableNames(); - } - - @Override - public Set getFilterJdbcParameters() { - return StandardSqlAstDeleteTranslator.this.getFilterJdbcParameters(); - } - }; - } - finally { - cleanup(); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstInsertTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstInsertTranslator.java deleted file mode 100644 index bd4cadd678..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstInsertTranslator.java +++ /dev/null @@ -1,180 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.sql.ast.spi; - -import java.util.List; -import java.util.Set; - -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.FilterJdbcParameter; -import org.hibernate.internal.util.collections.Stack; -import org.hibernate.sql.ast.Clause; -import org.hibernate.sql.ast.SqlAstInsertTranslator; -import org.hibernate.sql.ast.tree.cte.CteColumn; -import org.hibernate.sql.ast.tree.cte.CteStatement; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.insert.InsertStatement; -import org.hibernate.sql.ast.tree.insert.Values; -import org.hibernate.sql.exec.spi.JdbcInsert; -import org.hibernate.sql.exec.spi.JdbcParameterBinder; - -/** - * @author Steve Ebersole - */ -public class StandardSqlAstInsertTranslator - extends AbstractSqlAstTranslator - implements SqlAstInsertTranslator { - public StandardSqlAstInsertTranslator(SessionFactoryImplementor sessionFactory) { - super( sessionFactory ); - } - - @Override - public JdbcInsert translate(InsertStatement sqlAst) { - try { - appendSql( "insert into " ); - appendSql( sqlAst.getTargetTable().getTableExpression() ); - - appendSql( " (" ); - boolean firstPass = true; - - final List targetColumnReferences = sqlAst.getTargetColumnReferences(); - if ( targetColumnReferences == null ) { - renderImplicitTargetColumnSpec(); - } - else { - for (ColumnReference targetColumnReference : targetColumnReferences) { - if (firstPass) { - firstPass = false; - } - else { - appendSql( ", " ); - } - - appendSql( targetColumnReference.getColumnExpression() ); - } - } - - appendSql( ") " ); - - if ( sqlAst.getSourceSelectStatement()!=null ) { - visitQuerySpec( sqlAst.getSourceSelectStatement() ); - } - else { - appendSql("values"); - boolean firstTuple = true; - final Stack clauseStack = getClauseStack(); - try { - clauseStack.push( Clause.VALUES ); - for ( Values values : sqlAst.getValuesList() ) { - if ( firstTuple ) { - firstTuple = false; - } - else { - appendSql( ", " ); - } - appendSql( " (" ); - boolean firstExpr = true; - for ( Expression expression : values.getExpressions() ) { - if ( firstExpr ) { - firstExpr = false; - } - else { - appendSql( ", " ); - } - expression.accept( this ); - } - appendSql( ")" ); - } - } - finally { - clauseStack.pop(); - } - } - - return new JdbcInsert() { - @Override - public String getSql() { - return StandardSqlAstInsertTranslator.this.getSql(); - } - - @Override - public List getParameterBinders() { - return StandardSqlAstInsertTranslator.this.getParameterBinders(); - } - - @Override - public Set getAffectedTableNames() { - return StandardSqlAstInsertTranslator.this.getAffectedTableNames(); - } - - @Override - public Set getFilterJdbcParameters() { - return StandardSqlAstInsertTranslator.this.getFilterJdbcParameters(); - } - }; - } - finally { - cleanup(); - } - } - - private void renderImplicitTargetColumnSpec() { - } - - @Override - public JdbcInsert translate(CteStatement sqlAst) { - assert sqlAst.getCteConsumer() instanceof InsertStatement; - try { - appendSql( "with " ); - appendSql( sqlAst.getCteLabel() ); - - appendSql( " (" ); - - String separator = ""; - - for ( CteColumn cteColumn : sqlAst.getCteTable().getCteColumns() ) { - appendSql( separator ); - appendSql( cteColumn.getColumnExpression() ); - separator = ", "; - } - - appendSql( ") as (" ); - - visitQuerySpec( sqlAst.getCteDefinition() ); - - appendSql( ") " ); - - translate( (InsertStatement) sqlAst.getCteConsumer() ); - - return new JdbcInsert() { - @Override - public String getSql() { - return StandardSqlAstInsertTranslator.this.getSql(); - } - - @Override - public List getParameterBinders() { - return StandardSqlAstInsertTranslator.this.getParameterBinders(); - } - - @Override - public Set getAffectedTableNames() { - return StandardSqlAstInsertTranslator.this.getAffectedTableNames(); - } - - @Override - public Set getFilterJdbcParameters() { - return StandardSqlAstInsertTranslator.this.getFilterJdbcParameters(); - } - }; - } - finally { - cleanup(); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstSelectTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstSelectTranslator.java deleted file mode 100644 index 2b6b5cae54..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstSelectTranslator.java +++ /dev/null @@ -1,112 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.sql.ast.spi; - -import java.util.Collections; - -import org.hibernate.NotYetImplementedFor6Exception; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.sql.ast.SqlAstSelectTranslator; -import org.hibernate.sql.ast.tree.cte.CteColumn; -import org.hibernate.sql.ast.tree.cte.CteStatement; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.spi.JdbcSelect; -import org.hibernate.sql.results.jdbc.internal.JdbcValuesMappingProducerStandard; - -import static org.hibernate.sql.ast.SqlTreePrinter.logSqlAst; -import static org.hibernate.sql.results.graph.DomainResultGraphPrinter.logDomainResultGraph; - -/** - * The final phase of query translation. Here we take the SQL-AST an - * "interpretation". For a select query, that means an instance of - * {@link JdbcSelect}. - * - * @author Steve Ebersole - */ -public class StandardSqlAstSelectTranslator - extends AbstractSqlAstTranslator - implements SqlAstSelectTranslator { - - public StandardSqlAstSelectTranslator(SessionFactoryImplementor sessionFactory) { - super( sessionFactory ); - } - - @Override - public JdbcSelect translate(CteStatement sqlAst) { - assert sqlAst.getCteConsumer() instanceof QuerySpec; - - appendSql( "with " ); - appendSql( sqlAst.getCteLabel() ); - - appendSql( " (" ); - - String separator = ""; - - for ( CteColumn cteColumn : sqlAst.getCteTable().getCteColumns() ) { - appendSql( separator ); - appendSql( cteColumn.getColumnExpression() ); - separator = ", "; - } - - appendSql( ") as (" ); - - visitQuerySpec( sqlAst.getCteDefinition() ); - - appendSql( ") " ); - - translate( (QuerySpec) sqlAst.getCteConsumer() ); - - throw new NotYetImplementedFor6Exception( getClass() ); - } - - @Override - public JdbcSelect translate(QuerySpec querySpec) { - try { - visitQuerySpec( querySpec ); - - return new JdbcSelect( - getSql(), - getParameterBinders(), - new JdbcValuesMappingProducerStandard( - querySpec.getSelectClause().getSqlSelections(), - Collections.emptyList() - ), - getAffectedTableNames(), - getFilterJdbcParameters() - ); - } - finally { - cleanup(); - } - } - - @Override - public JdbcSelect translate(SelectStatement sqlAstSelect) { - try { - logDomainResultGraph( sqlAstSelect.getDomainResultDescriptors() ); - logSqlAst( sqlAstSelect ); - - visitQuerySpec( sqlAstSelect.getQuerySpec() ); - - return new JdbcSelect( - getSql(), - getParameterBinders(), - new JdbcValuesMappingProducerStandard( - sqlAstSelect.getQuerySpec().getSelectClause().getSqlSelections(), - sqlAstSelect.getDomainResultDescriptors() - ), - getAffectedTableNames(), - getFilterJdbcParameters() - ); - } - finally { - cleanup(); - } - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstTranslator.java new file mode 100644 index 0000000000..70bb30243e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstTranslator.java @@ -0,0 +1,26 @@ +/* + * 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.sql.ast.spi; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; +import org.hibernate.sql.exec.spi.JdbcSelect; + +/** + * The final phase of query translation. Here we take the SQL-AST an + * "interpretation". For a select query, that means an instance of + * {@link JdbcSelect}. + * + * @author Christian Beikov + */ +public class StandardSqlAstTranslator extends AbstractSqlAstTranslator { + + public StandardSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstTranslatorFactory.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstTranslatorFactory.java index d59212b3e2..f97d271835 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstTranslatorFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstTranslatorFactory.java @@ -7,33 +7,45 @@ package org.hibernate.sql.ast.spi; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.sql.ast.SqlAstDeleteTranslator; -import org.hibernate.sql.ast.SqlAstInsertTranslator; -import org.hibernate.sql.ast.SqlAstSelectTranslator; +import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.ast.SqlAstUpdateTranslator; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; +import org.hibernate.sql.ast.tree.insert.InsertStatement; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.spi.JdbcDelete; +import org.hibernate.sql.exec.spi.JdbcInsert; +import org.hibernate.sql.exec.spi.JdbcOperation; +import org.hibernate.sql.exec.spi.JdbcSelect; +import org.hibernate.sql.exec.spi.JdbcUpdate; /** * @author Steve Ebersole */ public class StandardSqlAstTranslatorFactory implements SqlAstTranslatorFactory { + @Override - public SqlAstSelectTranslator buildSelectTranslator(SessionFactoryImplementor sessionFactory) { - return new StandardSqlAstSelectTranslator( sessionFactory); + public SqlAstTranslator buildSelectTranslator(SessionFactoryImplementor sessionFactory, SelectStatement statement) { + return buildTranslator( sessionFactory, statement ); } @Override - public SqlAstDeleteTranslator buildDeleteTranslator(SessionFactoryImplementor sessionFactory) { - return new StandardSqlAstDeleteTranslator( sessionFactory ); + public SqlAstTranslator buildDeleteTranslator(SessionFactoryImplementor sessionFactory, DeleteStatement statement) { + return buildTranslator( sessionFactory, statement ); } @Override - public SqlAstInsertTranslator buildInsertTranslator(SessionFactoryImplementor sessionFactory) { - return new StandardSqlAstInsertTranslator( sessionFactory ); + public SqlAstTranslator buildInsertTranslator(SessionFactoryImplementor sessionFactory, InsertStatement statement) { + return buildTranslator( sessionFactory, statement ); } @Override - public SqlAstUpdateTranslator buildUpdateTranslator(SessionFactoryImplementor sessionFactory) { - return new StandardSqlAstUpdateTranslator( sessionFactory ); + public SqlAstTranslator buildUpdateTranslator(SessionFactoryImplementor sessionFactory, UpdateStatement statement) { + return buildTranslator( sessionFactory, statement ); + } + + protected SqlAstTranslator buildTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + return new StandardSqlAstTranslator<>( sessionFactory, statement ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstUpdateTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstUpdateTranslator.java deleted file mode 100644 index d53f31634f..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/StandardSqlAstUpdateTranslator.java +++ /dev/null @@ -1,147 +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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.sql.ast.spi; - -import java.util.List; -import java.util.Set; - -import org.hibernate.NotYetImplementedFor6Exception; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.FilterJdbcParameter; -import org.hibernate.internal.util.collections.Stack; -import org.hibernate.sql.ast.Clause; -import org.hibernate.sql.ast.SqlAstUpdateTranslator; -import org.hibernate.sql.ast.tree.cte.CteStatement; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.ast.tree.update.UpdateStatement; -import org.hibernate.sql.exec.spi.JdbcParameterBinder; -import org.hibernate.sql.exec.spi.JdbcUpdate; - -/** - * @author Steve Ebersole - */ -public class StandardSqlAstUpdateTranslator - extends AbstractSqlAstTranslator - implements SqlAstUpdateTranslator { -// private final Dialect dialect; - - public StandardSqlAstUpdateTranslator(SessionFactoryImplementor sessionFactory) { - super( sessionFactory ); - - // 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 -// dialect = getSessionFactory().getJdbcServices().getJdbcEnvironment().getDialect(); - } - - private String updatingTableAlias; - - @Override - public JdbcUpdate translate(UpdateStatement sqlAst) { - try { - updatingTableAlias = sqlAst.getTargetTable().getIdentificationVariable(); - // todo (6.0) : to support joins we need dialect support - appendSql( "update " ); - final Stack clauseStack = getClauseStack(); - try { - clauseStack.push( Clause.UPDATE ); - renderTableReference( sqlAst.getTargetTable() ); - } - finally { - clauseStack.pop(); - } - - appendSql( " set " ); - boolean firstPass = true; - try { - clauseStack.push( Clause.SET ); - for ( Assignment assignment : sqlAst.getAssignments() ) { - if ( firstPass ) { - firstPass = false; - } - else { - appendSql( ", " ); - } - - final List columnReferences = assignment.getAssignable().getColumnReferences(); - if ( columnReferences.size() == 1 ) { - columnReferences.get( 0 ).accept( this ); - } - else { - appendSql( " (" ); - for (ColumnReference columnReference : columnReferences) { - columnReference.accept( this ); - } - appendSql( ") " ); - } - appendSql( " = " ); - assignment.getAssignedValue().accept( this ); - } - } - finally { - clauseStack.pop(); - } - - if ( sqlAst.getRestriction() != null ) { - appendSql( " where " ); - try { - clauseStack.push( Clause.WHERE ); - sqlAst.getRestriction().accept( this ); - } - finally { - clauseStack.pop(); - } - } - - return new JdbcUpdate() { - @Override - public String getSql() { - return StandardSqlAstUpdateTranslator.this.getSql(); - } - - @Override - public List getParameterBinders() { - return StandardSqlAstUpdateTranslator.this.getParameterBinders(); - } - - @Override - public Set getFilterJdbcParameters() { - return StandardSqlAstUpdateTranslator.this.getFilterJdbcParameters(); - } - - @Override - public Set getAffectedTableNames() { - return StandardSqlAstUpdateTranslator.this.getAffectedTableNames(); - } - }; - } - finally { - cleanup(); - } - } - - @Override - public void visitColumnReference(ColumnReference columnReference) { - if ( updatingTableAlias != null && updatingTableAlias.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 - appendSql( columnReference.getColumnExpression() ); - } - else { - super.visitColumnReference( columnReference ); - } - } - - @Override - public JdbcUpdate translate(CteStatement cteStatement) { - throw new NotYetImplementedFor6Exception( getClass() ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/AbstractMutationStatement.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/AbstractMutationStatement.java new file mode 100644 index 0000000000..0ddace5ca8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/AbstractMutationStatement.java @@ -0,0 +1,47 @@ +/* + * 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.sql.ast.tree; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.from.TableReference; + +/** + * @author Christian Beikov + */ +public abstract class AbstractMutationStatement extends AbstractStatement implements MutationStatement { + + private final TableReference targetTable; + private final List returningColumns; + + public AbstractMutationStatement(TableReference targetTable) { + super( new LinkedHashMap<>() ); + this.targetTable = targetTable; + this.returningColumns = Collections.emptyList(); + } + + public AbstractMutationStatement(Map cteStatements, TableReference targetTable, List returningColumns) { + super( cteStatements ); + this.targetTable = targetTable; + this.returningColumns = returningColumns; + } + + @Override + public TableReference getTargetTable() { + return targetTable; + } + + @Override + public List getReturningColumns() { + return returningColumns; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/AbstractStatement.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/AbstractStatement.java new file mode 100644 index 0000000000..beb1bb859e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/AbstractStatement.java @@ -0,0 +1,53 @@ +/* + * 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.sql.ast.tree; + +import java.util.Collection; +import java.util.Map; + +import org.hibernate.sql.ast.tree.cte.CteContainer; +import org.hibernate.sql.ast.tree.cte.CteStatement; + +/** + * @author Christian Beikov + */ +public abstract class AbstractStatement implements Statement, CteContainer { + + private final Map cteStatements; + private boolean withRecursive; + + public AbstractStatement(Map cteStatements) { + this.cteStatements = cteStatements; + } + + @Override + public boolean isWithRecursive() { + return withRecursive; + } + + @Override + public void setWithRecursive(boolean withRecursive) { + this.withRecursive = withRecursive; + } + + @Override + public Collection getCteStatements() { + return cteStatements.values(); + } + + @Override + public CteStatement getCteStatement(String cteLabel) { + return cteStatements.get( cteLabel ); + } + + @Override + public void addCteStatement(CteStatement cteStatement) { + if ( cteStatements.putIfAbsent( cteStatement.getCteTable().getTableExpression(), cteStatement ) != null ) { + throw new IllegalArgumentException( "A CTE with the label " + cteStatement.getCteTable().getTableExpression() + " already exists!" ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/MutationStatement.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/MutationStatement.java index be06895787..379f853227 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/MutationStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/MutationStatement.java @@ -6,10 +6,17 @@ */ package org.hibernate.sql.ast.tree; +import java.util.List; + +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.from.TableReference; + /** * Specialization of Statement for mutation (DML) statements * * @author Steve Ebersole */ public interface MutationStatement extends Statement { + TableReference getTargetTable(); + List getReturningColumns(); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/Statement.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/Statement.java index 3be6ccc08c..4541cfe1f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/Statement.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/Statement.java @@ -6,10 +6,13 @@ */ package org.hibernate.sql.ast.tree; +import org.hibernate.sql.ast.SqlAstWalker; + /** * Base contract for any statement * * @author Steve Ebersole */ public interface Statement { + void accept(SqlAstWalker walker); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteConsumer.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteContainer.java similarity index 57% rename from hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteConsumer.java rename to hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteContainer.java index 4a6fc1dfa1..e56bdd914c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteConsumer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteContainer.java @@ -6,11 +6,24 @@ */ package org.hibernate.sql.ast.tree.cte; +import java.util.Collection; + /** * The consumer part of a CTE statement - the select or insert or delete or update that uses * the CTE * * @author Steve Ebersole + * @author Christian Beikov */ -public interface CteConsumer { +public interface CteContainer { + + boolean isWithRecursive(); + + void setWithRecursive(boolean recursive); + + Collection getCteStatements(); + + CteStatement getCteStatement(String cteLabel); + + void addCteStatement(CteStatement cteStatement); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteStatement.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteStatement.java index 677b82abde..7bcbff423c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteStatement.java @@ -6,40 +6,86 @@ */ package org.hibernate.sql.ast.tree.cte; +import java.util.List; + +import org.hibernate.CteSearchClauseKind; import org.hibernate.sql.ast.tree.Statement; -import org.hibernate.sql.ast.tree.select.QuerySpec; /** * A statement using a CTE * * @author Steve Ebersole + * @author Christian Beikov */ -public class CteStatement implements Statement { - private final String cteLabel; +public class CteStatement { private final CteTable cteTable; - private final QuerySpec cteDefinition; - private final CteConsumer cteConsumer; + private final Statement cteDefinition; + private final CteSearchClauseKind searchClauseKind; + private final List searchBySpecifications; + private final List cycleColumns; + private final CteColumn cycleMarkColumn; + private final char cycleValue; + private final char noCycleValue; - public CteStatement(QuerySpec cteDefinition, String cteLabel, CteTable cteTable, CteConsumer cteConsumer) { + public CteStatement(CteTable cteTable, Statement cteDefinition) { this.cteDefinition = cteDefinition; - this.cteLabel = cteLabel; this.cteTable = cteTable; - this.cteConsumer = cteConsumer; + this.searchClauseKind = null; + this.searchBySpecifications = null; + this.cycleColumns = null; + this.cycleMarkColumn = null; + this.cycleValue = '\0'; + this.noCycleValue = '\0'; } - public String getCteLabel() { - return cteLabel; + public CteStatement( + CteTable cteTable, + Statement cteDefinition, + CteSearchClauseKind searchClauseKind, + List searchBySpecifications, + List cycleColumns, + CteColumn cycleMarkColumn, + char cycleValue, + char noCycleValue) { + this.cteTable = cteTable; + this.cteDefinition = cteDefinition; + this.searchClauseKind = searchClauseKind; + this.searchBySpecifications = searchBySpecifications; + this.cycleColumns = cycleColumns; + this.cycleMarkColumn = cycleMarkColumn; + this.cycleValue = cycleValue; + this.noCycleValue = noCycleValue; } public CteTable getCteTable() { return cteTable; } - public QuerySpec getCteDefinition() { + public Statement getCteDefinition() { return cteDefinition; } - public CteConsumer getCteConsumer() { - return cteConsumer; + public CteSearchClauseKind getSearchClauseKind() { + return searchClauseKind; + } + + public List getSearchBySpecifications() { + return searchBySpecifications; + } + + public List getCycleColumns() { + return cycleColumns; + } + + public CteColumn getCycleMarkColumn() { + return cycleMarkColumn; + } + + public char getCycleValue() { + return cycleValue; + } + + public char getNoCycleValue() { + return noCycleValue; } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java index 4654d10164..2e4a101632 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java @@ -12,18 +12,15 @@ import java.util.List; import org.hibernate.LockMode; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.util.StringHelper; import org.hibernate.metamodel.mapping.Bindable; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.query.NavigablePath; -import org.hibernate.query.sqm.mutation.internal.cte.CteStrategy; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.from.StandardTableGroup; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterImpl; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcParameterBindings; @@ -36,28 +33,30 @@ import org.hibernate.sql.results.internal.SqlSelectionImpl; */ public class CteTable { private final SessionFactoryImplementor sessionFactory; - + private final String cteName; private final List cteColumns; - public CteTable(EntityMappingType entityDescriptor) { - this.sessionFactory = entityDescriptor.getEntityPersister().getFactory(); - + public CteTable(String cteName, EntityMappingType entityDescriptor) { final int numberOfColumns = entityDescriptor.getIdentifierMapping().getJdbcTypeCount(); - cteColumns = new ArrayList<>( numberOfColumns ); + final List columns = new ArrayList<>( numberOfColumns ); entityDescriptor.getIdentifierMapping().forEachSelection( - (columnIndex, selection) -> cteColumns.add( + (columnIndex, selection) -> columns.add( new CteColumn("cte_" + selection.getSelectionExpression(), selection.getJdbcMapping() ) ) ); + this.cteName = cteName; + this.cteColumns = columns; + this.sessionFactory = entityDescriptor.getEntityPersister().getFactory(); } - public CteTable(List cteColumns, SessionFactoryImplementor sessionFactory) { + public CteTable(String cteName, List cteColumns, SessionFactoryImplementor sessionFactory) { + this.cteName = cteName; this.cteColumns = cteColumns; this.sessionFactory = sessionFactory; } public String getTableExpression() { - return CteStrategy.TABLE_NAME; + return cteName; } public List getCteColumns() { @@ -134,7 +133,7 @@ public class CteTable { return new TableReference( tableValueCtorExpressionBuffer.toString(), - CteStrategy.TABLE_NAME, + cteName, false, sessionFactory ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTableGroup.java index 354560602f..ad049fb6b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTableGroup.java @@ -16,7 +16,6 @@ import org.hibernate.LockMode; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.query.NavigablePath; -import org.hibernate.query.sqm.mutation.internal.cte.CteStrategy; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroupJoin; import org.hibernate.sql.ast.tree.from.TableReference; @@ -34,7 +33,7 @@ public class CteTableGroup implements TableGroup { @SuppressWarnings("WeakerAccess") public CteTableGroup(TableReference cteTableReference) { - this.navigablePath = new NavigablePath( CteStrategy.TABLE_NAME ); + this.navigablePath = new NavigablePath( cteTableReference.getTableExpression() ); this.cteTableReference = cteTableReference; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/SearchClauseSpecification.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/SearchClauseSpecification.java new file mode 100644 index 0000000000..666dd8a5b6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/SearchClauseSpecification.java @@ -0,0 +1,37 @@ +/* + * 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.sql.ast.tree.cte; + +import org.hibernate.NullPrecedence; +import org.hibernate.SortOrder; + +/** + * @author Christian Beikov + */ +public class SearchClauseSpecification { + private final CteColumn cteColumn; + private final SortOrder sortOrder; + private final NullPrecedence nullPrecedence; + + public SearchClauseSpecification(CteColumn cteColumn, SortOrder sortOrder, NullPrecedence nullPrecedence) { + this.cteColumn = cteColumn; + this.sortOrder = sortOrder; + this.nullPrecedence = nullPrecedence; + } + + public CteColumn getCteColumn() { + return cteColumn; + } + + public SortOrder getSortOrder() { + return sortOrder; + } + + public NullPrecedence getNullPrecedence() { + return nullPrecedence; + } +} 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 b8f0a335b8..e63349381e 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 @@ -6,9 +6,15 @@ */ package org.hibernate.sql.ast.tree.delete; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.hibernate.sql.ast.SqlAstWalker; import org.hibernate.sql.ast.spi.SqlAstHelper; -import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.ast.tree.cte.CteConsumer; +import org.hibernate.sql.ast.tree.AbstractMutationStatement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.predicate.Junction; import org.hibernate.sql.ast.tree.predicate.Predicate; @@ -16,17 +22,32 @@ import org.hibernate.sql.ast.tree.predicate.Predicate; /** * @author Steve Ebersole */ -public class DeleteStatement implements MutationStatement, CteConsumer { - private final TableReference targetTable; +public class DeleteStatement extends AbstractMutationStatement { + private final Predicate restriction; public DeleteStatement(TableReference targetTable, Predicate restriction) { - this.targetTable = targetTable; + super( targetTable ); this.restriction = restriction; } - public TableReference getTargetTable() { - return targetTable; + public DeleteStatement( + TableReference targetTable, + Predicate restriction, + List returningColumns) { + super( new LinkedHashMap<>(), targetTable, returningColumns ); + this.restriction = restriction; + } + + public DeleteStatement( + boolean withRecursive, + Map cteStatements, + TableReference targetTable, + Predicate restriction, + List returningColumns) { + super( cteStatements, targetTable, returningColumns ); + this.restriction = restriction; + setWithRecursive( withRecursive ); } public Predicate getRestriction() { @@ -58,4 +79,9 @@ public class DeleteStatement implements MutationStatement, CteConsumer { ); } } + + @Override + public void accept(SqlAstWalker walker) { + walker.visitDeleteStatement( this ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Any.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Any.java index 4806218bd4..aed3728aa8 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Any.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Any.java @@ -8,6 +8,8 @@ package org.hibernate.sql.ast.tree.expression; import org.hibernate.metamodel.mapping.MappingModelExpressable; import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.tree.select.QueryGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QuerySpec; /** @@ -15,15 +17,15 @@ import org.hibernate.sql.ast.tree.select.QuerySpec; */ public class Any implements Expression { - private QuerySpec subquery; + private QueryPart subquery; private MappingModelExpressable type; - public Any(QuerySpec subquery, MappingModelExpressable type) { + public Any(QueryPart subquery, MappingModelExpressable type) { this.subquery = subquery; this.type = type; } - public QuerySpec getSubquery() { + public QueryPart getSubquery() { return subquery; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java index f3d1905fc4..ecb7b98dbf 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java @@ -108,6 +108,22 @@ public class ColumnReference implements Expression, Assignable { ); } + public ColumnReference( + TableReference tableReference, + String mapping, + JdbcMapping jdbcMapping, + SessionFactoryImplementor sessionFactory) { + this( + tableReference.getIdentificationVariable(), + mapping, + false, + null, + null, + jdbcMapping, + sessionFactory + ); + } + public ColumnReference( TableReference tableReference, String columnExpression, diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Every.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Every.java index f14865b053..1489116bac 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Every.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Every.java @@ -8,6 +8,8 @@ package org.hibernate.sql.ast.tree.expression; import org.hibernate.metamodel.mapping.MappingModelExpressable; import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.tree.select.QueryGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QuerySpec; /** @@ -15,15 +17,15 @@ import org.hibernate.sql.ast.tree.select.QuerySpec; */ public class Every implements Expression { - private QuerySpec subquery; + private QueryPart subquery; private MappingModelExpressable type; - public Every(QuerySpec subquery, MappingModelExpressable type) { + public Every(QueryPart subquery, MappingModelExpressable type) { this.subquery = subquery; this.type = type; } - public QuerySpec getSubquery() { + public QueryPart getSubquery() { return subquery; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/CorrelatedTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/CorrelatedTableGroup.java new file mode 100644 index 0000000000..4619b2dc7c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/CorrelatedTableGroup.java @@ -0,0 +1,96 @@ +/* + * 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.sql.ast.tree.from; + +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +import org.hibernate.LockMode; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.SqlAstJoinType; +import org.hibernate.sql.ast.spi.SqlAliasBase; +import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.ast.tree.select.QuerySpec; + +/** + * A virtual {@link TableReference} for correlated roots. + * Table group joins are pushed into the from clause as roots and join predicates to the where clause. + * + * @author Christian Beikov + */ +public class CorrelatedTableGroup extends AbstractTableGroup { + + private final TableGroup correlatedTableGroup; + private final QuerySpec querySpec; + private final Consumer joinPredicateConsumer; + + public CorrelatedTableGroup( + TableGroup correlatedTableGroup, + SqlAliasBase sqlAliasBase, + QuerySpec querySpec, + Consumer joinPredicateConsumer, + SessionFactoryImplementor sessionFactory) { + super( + correlatedTableGroup.getNavigablePath(), + (TableGroupProducer) correlatedTableGroup.getExpressionType(), + LockMode.NONE, + sqlAliasBase, + sessionFactory + ); + this.correlatedTableGroup = correlatedTableGroup; + this.querySpec = querySpec; + this.joinPredicateConsumer = joinPredicateConsumer; + } + + @Override + public void addTableGroupJoin(TableGroupJoin join) { + if ( getTableGroupJoins().contains( join ) ) { + return; + } + assert join.getJoinType() == SqlAstJoinType.INNER; + querySpec.getFromClause().addRoot( join.getJoinedGroup() ); + joinPredicateConsumer.accept( join.getPredicate() ); + super.addTableGroupJoin( join ); + } + + @Override + protected TableReference getTableReferenceInternal(String tableExpression) { + final TableReference primaryTableReference = correlatedTableGroup.getPrimaryTableReference(); + if ( tableExpression.equals( primaryTableReference.getTableExpression() ) ) { + return primaryTableReference; + } + for ( TableGroupJoin tableGroupJoin : getTableGroupJoins() ) { + final TableReference groupTableReference = tableGroupJoin.getJoinedGroup().getPrimaryTableReference(); + if ( groupTableReference.getTableReference( tableExpression ) != null ) { + return groupTableReference; + } + } + for ( TableReferenceJoin tableReferenceJoin : correlatedTableGroup.getTableReferenceJoins() ) { + if ( tableExpression.equals( tableReferenceJoin.getJoinedTableReference().getTableExpression() ) ) { + return tableReferenceJoin.getJoinedTableReference(); + } + } + return null; + } + + @Override + public void applyAffectedTableNames(Consumer nameCollector) { + nameCollector.accept( getPrimaryTableReference().getTableExpression() ); + } + + @Override + public TableReference getPrimaryTableReference() { + return correlatedTableGroup.getPrimaryTableReference(); + } + + @Override + public List getTableReferenceJoins() { + return Collections.emptyList(); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroup.java index 79b831078f..4a262bd13c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroup.java @@ -12,6 +12,7 @@ import java.util.function.Consumer; import org.hibernate.LockMode; import org.hibernate.metamodel.mapping.ModelPartContainer; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.NavigablePath; import org.hibernate.query.sqm.sql.internal.DomainResultProducer; import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation; @@ -68,6 +69,15 @@ public interface TableGroup extends SqlAstNode, ColumnReferenceQualifier, SqmPat ); } + @Override + default void applySqlSelections(DomainResultCreationState creationState) { + getModelPart().applySqlSelections( + getNavigablePath(), + creationState.getSqlAstCreationState().getFromClauseAccess().findTableGroup( getNavigablePath() ), + creationState + ); + } + default ColumnReference locateColumnReferenceByName(String name) { throw new UnsupportedOperationException( "Cannot call #locateColumnReferenceByName on this type of TableGroup" diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/insert/InsertStatement.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/insert/InsertStatement.java index 83b0601300..26f3f0a33e 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/insert/InsertStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/insert/InsertStatement.java @@ -9,36 +9,32 @@ package org.hibernate.sql.ast.tree.insert; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; +import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.tree.AbstractMutationStatement; import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.ast.tree.cte.CteConsumer; +import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.select.QuerySpec; - -import org.jboss.logging.Logger; +import org.hibernate.sql.ast.tree.select.QueryPart; /** * @author Steve Ebersole */ -public class InsertStatement implements MutationStatement, CteConsumer { - private static final Logger log = Logger.getLogger( InsertStatement.class ); +public class InsertStatement extends AbstractMutationStatement { - private TableReference targetTable; private List targetColumnReferences; - private QuerySpec sourceSelectStatement; + private QueryPart sourceSelectStatement; private List valuesList = new ArrayList<>(); - public TableReference getTargetTable() { - return targetTable; + public InsertStatement(TableReference targetTable) { + super( targetTable ); } - public void setTargetTable(TableReference targetTable) { - log.tracef( "Setting INSERT target table [%s]", targetTable ); - if ( this.targetTable != null ) { - log.debugf( "INSERT target table has been set multiple times" ); - } - this.targetTable = targetTable; + public InsertStatement(boolean withRecursive, Map cteStatements, TableReference targetTable, List returningColumns) { + super( cteStatements, targetTable, returningColumns ); + setWithRecursive( withRecursive ); } public List getTargetColumnReferences() { @@ -61,11 +57,11 @@ public class InsertStatement implements MutationStatement, CteConsumer { this.targetColumnReferences.addAll( references ); } - public QuerySpec getSourceSelectStatement() { + public QueryPart getSourceSelectStatement() { return sourceSelectStatement; } - public void setSourceSelectStatement(QuerySpec sourceSelectStatement) { + public void setSourceSelectStatement(QueryPart sourceSelectStatement) { this.sourceSelectStatement = sourceSelectStatement; } @@ -76,4 +72,9 @@ public class InsertStatement implements MutationStatement, CteConsumer { public void setValuesList(List valuesList) { this.valuesList = valuesList; } + + @Override + public void accept(SqlAstWalker walker) { + walker.visitInsertStatement( this ); + } } 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 74378a5c0b..072478cf9c 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 @@ -8,19 +8,20 @@ package org.hibernate.sql.ast.tree.predicate; import org.hibernate.sql.ast.SqlAstWalker; import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.select.QueryPart; /** * @author Gavin King */ public class ExistsPredicate implements Predicate { - private Expression expression; + private QueryPart expression; - public ExistsPredicate(Expression expression) { + public ExistsPredicate(QueryPart expression) { this.expression = expression; } - public Expression getExpression() { + public QueryPart getExpression() { return expression; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/predicate/InSubQueryPredicate.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/predicate/InSubQueryPredicate.java index f055552cda..7f0e49dfc6 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/predicate/InSubQueryPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/predicate/InSubQueryPredicate.java @@ -8,17 +8,17 @@ package org.hibernate.sql.ast.tree.predicate; import org.hibernate.sql.ast.SqlAstWalker; import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.QueryPart; /** * @author Steve Ebersole */ public class InSubQueryPredicate implements Predicate { private final Expression testExpression; - private final QuerySpec subQuery; + private final QueryPart subQuery; private final boolean negated; - public InSubQueryPredicate(Expression testExpression, QuerySpec subQuery, boolean negated) { + public InSubQueryPredicate(Expression testExpression, QueryPart subQuery, boolean negated) { this.testExpression = testExpression; this.subQuery = subQuery; this.negated = negated; @@ -28,7 +28,7 @@ public class InSubQueryPredicate implements Predicate { return testExpression; } - public QuerySpec getSubQuery() { + public QueryPart getSubQuery() { return subQuery; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryGroup.java new file mode 100644 index 0000000000..eaea37dedf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryGroup.java @@ -0,0 +1,70 @@ +/* + * 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.sql.ast.tree.select; + +import java.util.List; + +import org.hibernate.SetOperator; +import org.hibernate.metamodel.mapping.MappingModelExpressable; +import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.DomainResultCreationState; + +/** + * @author Christian Beikov + */ +public class QueryGroup extends QueryPart { + private final SetOperator setOperator; + private final List queryParts; + + public QueryGroup(boolean isRoot, SetOperator setOperator, List queryParts) { + super( isRoot ); + this.setOperator = setOperator; + this.queryParts = queryParts; + } + + @Override + public QuerySpec getFirstQuerySpec() { + return queryParts.get( 0 ).getFirstQuerySpec(); + } + + @Override + public QuerySpec getLastQuerySpec() { + return queryParts.get( queryParts.size() - 1 ).getLastQuerySpec(); + } + + public SetOperator getSetOperator() { + return setOperator; + } + + public List getQueryParts() { + return queryParts; + } + + @Override + public void accept(SqlAstWalker sqlTreeWalker) { + sqlTreeWalker.visitQueryGroup( this ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Expression + + @Override + public MappingModelExpressable getExpressionType() { + return queryParts.get( 0 ).getExpressionType(); + } + + @Override + public void applySqlSelections(DomainResultCreationState creationState) { + queryParts.get( 0 ).applySqlSelections( creationState ); + } + + @Override + public DomainResult createDomainResult(String resultVariable, DomainResultCreationState creationState) { + return queryParts.get( 0 ).createDomainResult( resultVariable, creationState ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryPart.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryPart.java new file mode 100644 index 0000000000..7867d70a2d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QueryPart.java @@ -0,0 +1,100 @@ +/* + * 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.sql.ast.tree.select; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.hibernate.FetchClauseType; +import org.hibernate.query.sqm.sql.internal.DomainResultProducer; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; + +/** + * @author Christian Beikov + */ +public abstract class QueryPart implements SqlAstNode, Expression, DomainResultProducer { + + private final boolean isRoot; + private List sortSpecifications; + private Expression offsetClauseExpression; + private Expression fetchClauseExpression; + private FetchClauseType fetchClauseType = FetchClauseType.ROWS_ONLY; + + public QueryPart(boolean isRoot) { + this.isRoot = isRoot; + } + + public abstract QuerySpec getFirstQuerySpec(); + + public abstract QuerySpec getLastQuerySpec(); + + /** + * Does this QueryPart map to the statement's root query (as + * opposed to one of its sub-queries)? + */ + public boolean isRoot() { + return isRoot; + } + + public boolean hasSortSpecifications() { + return sortSpecifications != null && !sortSpecifications.isEmpty(); + } + + public List getSortSpecifications() { + return sortSpecifications; + } + + void visitSortSpecifications(Consumer consumer) { + if ( sortSpecifications != null ) { + sortSpecifications.forEach( consumer ); + } + } + + public void addSortSpecification(SortSpecification specification) { + if ( sortSpecifications == null ) { + sortSpecifications = new ArrayList<>(); + } + sortSpecifications.add( specification ); + } + + public boolean hasOffsetOrFetchClause() { + return offsetClauseExpression != null || fetchClauseExpression != null; + } + + public Expression getOffsetClauseExpression() { + return offsetClauseExpression; + } + + public void setOffsetClauseExpression(Expression offsetClauseExpression) { + this.offsetClauseExpression = offsetClauseExpression; + } + + public Expression getFetchClauseExpression() { + return fetchClauseExpression; + } + + public void setFetchClauseExpression(Expression fetchClauseExpression, FetchClauseType fetchClauseType) { + if ( fetchClauseExpression == null ) { + this.fetchClauseExpression = null; + this.fetchClauseType = null; + } + else { + if ( fetchClauseType == null ) { + throw new IllegalArgumentException( "Fetch clause may not be null!" ); + } + this.fetchClauseExpression = fetchClauseExpression; + this.fetchClauseType = fetchClauseType; + } + } + + public FetchClauseType getFetchClauseType() { + return fetchClauseType; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QuerySpec.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QuerySpec.java index 04f87ed15b..9a943dec80 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QuerySpec.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/QuerySpec.java @@ -6,10 +6,8 @@ */ package org.hibernate.sql.ast.tree.select; -import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.function.Consumer; import org.hibernate.metamodel.mapping.MappingModelExpressable; import org.hibernate.query.sqm.sql.internal.DomainResultProducer; @@ -18,7 +16,6 @@ import org.hibernate.sql.ast.spi.SqlAstTreeHelper; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.SqlAstNode; -import org.hibernate.sql.ast.tree.cte.CteConsumer; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.from.FromClause; import org.hibernate.sql.ast.tree.predicate.Predicate; @@ -32,8 +29,7 @@ import org.hibernate.type.spi.TypeConfiguration; /** * @author Steve Ebersole */ -public class QuerySpec implements SqlAstNode, PredicateContainer, Expression, CteConsumer, DomainResultProducer { - private final boolean isRoot; +public class QuerySpec extends QueryPart implements SqlAstNode, PredicateContainer, Expression, DomainResultProducer { private final FromClause fromClause; private final SelectClause selectClause = new SelectClause(); @@ -43,26 +39,24 @@ public class QuerySpec implements SqlAstNode, PredicateContainer, Expression, Ct private List groupByClauseExpressions = Collections.emptyList(); private Predicate havingClauseRestrictions; - private List sortSpecifications; - private Expression limitClauseExpression; - private Expression offsetClauseExpression; - public QuerySpec(boolean isRoot) { - this.isRoot = isRoot; + super( isRoot ); this.fromClause = new FromClause(); } public QuerySpec(boolean isRoot, int expectedNumberOfRoots) { - this.isRoot = isRoot; + super( isRoot ); this.fromClause = new FromClause( expectedNumberOfRoots ); } - /** - * Does this QuerySpec map to the statement's root query (as - * opposed to one of its sub-queries)? - */ - public boolean isRoot() { - return isRoot; + @Override + public QuerySpec getFirstQuerySpec() { + return this; + } + + @Override + public QuerySpec getLastQuerySpec() { + return this; } public FromClause getFromClause() { @@ -98,39 +92,6 @@ public class QuerySpec implements SqlAstNode, PredicateContainer, Expression, Ct this.havingClauseRestrictions = havingClauseRestrictions; } - public List getSortSpecifications() { - return sortSpecifications; - } - - void visitSortSpecifications(Consumer consumer) { - if ( sortSpecifications != null ) { - sortSpecifications.forEach( consumer ); - } - } - - public void addSortSpecification(SortSpecification specification) { - if ( sortSpecifications == null ) { - sortSpecifications = new ArrayList<>(); - } - sortSpecifications.add( specification ); - } - - public Expression getLimitClauseExpression() { - return limitClauseExpression; - } - - public void setLimitClauseExpression(Expression limitClauseExpression) { - this.limitClauseExpression = limitClauseExpression; - } - - public Expression getOffsetClauseExpression() { - return offsetClauseExpression; - } - - public void setOffsetClauseExpression(Expression offsetClauseExpression) { - this.offsetClauseExpression = offsetClauseExpression; - } - @Override public void accept(SqlAstWalker sqlTreeWalker) { sqlTreeWalker.visitQuerySpec( this ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/SelectStatement.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/SelectStatement.java index 1039c85b86..19f9516b6e 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/SelectStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/select/SelectStatement.java @@ -7,30 +7,55 @@ package org.hibernate.sql.ast.tree.select; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; -import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.tree.AbstractStatement; +import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.results.graph.DomainResult; /** * @author Steve Ebersole */ -public class SelectStatement implements Statement { - private final QuerySpec querySpec; +public class SelectStatement extends AbstractStatement { + private final QueryPart queryPart; private final List domainResults; + public SelectStatement(QueryPart queryPart) { + this( queryPart, Collections.emptyList() ); + } + + public SelectStatement(QueryPart queryPart, List domainResults) { + this( false, new LinkedHashMap<>(), queryPart, domainResults ); + } + public SelectStatement( - QuerySpec querySpec, + boolean withRecursive, + Map cteStatements, + QueryPart queryPart, List domainResults) { - this.querySpec = querySpec; - this.domainResults = Collections.unmodifiableList( domainResults ); + super( cteStatements ); + this.queryPart = queryPart; + this.domainResults = domainResults; + setWithRecursive( withRecursive ); } public QuerySpec getQuerySpec() { - return querySpec; + return queryPart.getFirstQuerySpec(); + } + + public QueryPart getQueryPart() { + return queryPart; } public List getDomainResultDescriptors() { return domainResults; } + + @Override + public void accept(SqlAstWalker walker) { + walker.visitSelectStatement( this ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/update/UpdateStatement.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/update/UpdateStatement.java index 9dc9fe6f7c..f8955f8ea8 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/update/UpdateStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/update/UpdateStatement.java @@ -7,19 +7,22 @@ package org.hibernate.sql.ast.tree.update; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import org.hibernate.sql.ast.SqlAstWalker; import org.hibernate.sql.ast.spi.SqlAstTreeHelper; -import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.ast.tree.cte.CteConsumer; +import org.hibernate.sql.ast.tree.AbstractMutationStatement; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.predicate.Predicate; /** * @author Steve Ebersole */ -public class UpdateStatement implements MutationStatement, CteConsumer { - private final TableReference targetTable; +public class UpdateStatement extends AbstractMutationStatement { private final List assignments; private final Predicate restriction; @@ -27,13 +30,32 @@ public class UpdateStatement implements MutationStatement, CteConsumer { TableReference targetTable, List assignments, Predicate restriction) { - this.targetTable = targetTable; + super( targetTable ); this.assignments = assignments; this.restriction = restriction; } - public TableReference getTargetTable() { - return targetTable; + public UpdateStatement( + TableReference targetTable, + List assignments, + Predicate restriction, + List returningColumns) { + super( new LinkedHashMap<>(), targetTable, returningColumns ); + this.assignments = assignments; + this.restriction = restriction; + } + + public UpdateStatement( + boolean withRecursive, + Map cteStatements, + TableReference targetTable, + List assignments, + Predicate restriction, + List returningColumns) { + super( cteStatements, targetTable, returningColumns ); + this.assignments = assignments; + this.restriction = restriction; + setWithRecursive( withRecursive ); } public List getAssignments() { @@ -44,7 +66,6 @@ public class UpdateStatement implements MutationStatement, CteConsumer { return restriction; } - public static class UpdateStatementBuilder { private final TableReference targetTableRef; private List assignments; @@ -88,4 +109,9 @@ public class UpdateStatement implements MutationStatement, CteConsumer { return new UpdateStatement( targetTableRef, assignments, restriction ); } } + + @Override + public void accept(SqlAstWalker walker) { + walker.visitUpdateStatement( this ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/DelegatingExecutionContext.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/DelegatingExecutionContext.java new file mode 100644 index 0000000000..f3bc29fbe8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/DelegatingExecutionContext.java @@ -0,0 +1,82 @@ +/* + * 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.sql.exec.internal; + +import org.hibernate.engine.spi.CollectionKey; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryParameterBindings; +import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor; +import org.hibernate.sql.exec.spi.Callback; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; + +/** + * @author Christian Beikov + */ +public class DelegatingExecutionContext implements ExecutionContext { + + private final ExecutionContext executionContext; + + public DelegatingExecutionContext(ExecutionContext executionContext) { + this.executionContext = executionContext; + } + + @Override + public SharedSessionContractImplementor getSession() { + return executionContext.getSession(); + } + + @Override + public QueryOptions getQueryOptions() { + return executionContext.getQueryOptions(); + } + + @Override + public LoadQueryInfluencers getLoadQueryInfluencers() { + return executionContext.getLoadQueryInfluencers(); + } + + @Override + public QueryParameterBindings getQueryParameterBindings() { + return executionContext.getQueryParameterBindings(); + } + + @Override + public Callback getCallback() { + return executionContext.getCallback(); + } + + @Override + public CollectionKey getCollectionKey() { + return executionContext.getCollectionKey(); + } + + @Override + public Object getEntityInstance() { + return executionContext.getEntityInstance(); + } + + @Override + public Object getEntityId() { + return executionContext.getEntityId(); + } + + @Override + public void registerLoadingEntityEntry( + EntityKey entityKey, + LoadingEntityEntry entry) { + executionContext.registerLoadingEntityEntry( entityKey, entry ); + } + + @Override + public void afterStatement(LogicalConnectionImplementor logicalConnection) { + executionContext.afterStatement( logicalConnection ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcOperation.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcOperation.java index 39ffab8ec1..917db07f1f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcOperation.java @@ -11,6 +11,7 @@ import java.util.Set; import org.hibernate.internal.FilterJdbcParameter; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.query.spi.QueryOptions; /** * Unifying contract for any SQL statement we want to execute via JDBC. @@ -33,6 +34,20 @@ public interface JdbcOperation { Set getFilterJdbcParameters(); + /** + * Signals that the SQL depends on the parameter bindings e.g. due to the need for inlining + * of parameter values or multiValued parameters. + */ + default boolean dependsOnParameterBindings() { + return false; + } + + default boolean isCompatibleWith( + JdbcParameterBindings jdbcParameterBindings, + QueryOptions queryOptions) { + return true; + } + default void bindFilterJdbcParameters(JdbcParameterBindings jdbcParameterBindings) { if ( CollectionHelper.isNotEmpty( getFilterJdbcParameters() ) ) { for ( FilterJdbcParameter filterJdbcParameter : getFilterJdbcParameters() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcSelect.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcSelect.java index dd2f0523e1..48c5b885b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcSelect.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcSelect.java @@ -6,12 +6,18 @@ */ package org.hibernate.sql.exec.spi; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; +import org.hibernate.LockOptions; import org.hibernate.internal.FilterJdbcParameter; -import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.query.Limit; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; /** * Executable JDBC command @@ -24,6 +30,12 @@ public class JdbcSelect implements JdbcOperation { private final JdbcValuesMappingProducer jdbcValuesMappingProducer; private final Set affectedTableNames; private final Set filterJdbcParameters; + private final int rowsToSkip; + private final int maxRows; + private final Map appliedParameters; + private final LockOptions appliedLockOptions; + private final JdbcParameter offsetParameter; + private final JdbcParameter limitParameter; public JdbcSelect( String sql, @@ -31,11 +43,44 @@ public class JdbcSelect implements JdbcOperation { JdbcValuesMappingProducer jdbcValuesMappingProducer, Set affectedTableNames, Set filterJdbcParameters) { + this( + sql, + parameterBinders, + jdbcValuesMappingProducer, + affectedTableNames, + filterJdbcParameters, + 0, + Integer.MAX_VALUE, + Collections.emptyMap(), + null, + null, + null + ); + } + + public JdbcSelect( + String sql, + List parameterBinders, + JdbcValuesMappingProducer jdbcValuesMappingProducer, + Set affectedTableNames, + Set filterJdbcParameters, + int rowsToSkip, + int maxRows, + Map appliedParameters, + LockOptions appliedLockOptions, + JdbcParameter offsetParameter, + JdbcParameter limitParameter) { this.sql = sql; this.parameterBinders = parameterBinders; this.jdbcValuesMappingProducer = jdbcValuesMappingProducer; this.affectedTableNames = affectedTableNames; this.filterJdbcParameters = filterJdbcParameters; + this.rowsToSkip = rowsToSkip; + this.maxRows = maxRows; + this.appliedParameters = appliedParameters; + this.appliedLockOptions = appliedLockOptions; + this.offsetParameter = offsetParameter; + this.limitParameter = limitParameter; } @Override @@ -62,4 +107,86 @@ public class JdbcSelect implements JdbcOperation { return jdbcValuesMappingProducer; } + public int getRowsToSkip() { + return rowsToSkip; + } + + public int getMaxRows() { + return maxRows; + } + + public boolean usesLimitParameters() { + return offsetParameter != null || limitParameter != null; + } + + @Override + public boolean dependsOnParameterBindings() { + return !appliedParameters.isEmpty(); + } + + @Override + public boolean isCompatibleWith(JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) { + if ( !appliedParameters.isEmpty() ) { + if ( jdbcParameterBindings == null ) { + return false; + } + for ( Map.Entry entry : appliedParameters.entrySet() ) { + final JdbcParameter parameter = entry.getKey(); + // We handle limit and offset parameters below + if ( parameter != offsetParameter && parameter != limitParameter ) { + final JdbcParameterBinding binding = jdbcParameterBindings.getBinding( entry.getKey() ); + final JdbcParameterBinding appliedBinding = entry.getValue(); + if ( binding == null || !appliedBinding.getBindType() + .getJavaTypeDescriptor() + .areEqual( binding.getBindValue(), appliedBinding.getBindValue() ) ) { + return false; + } + } + } + } + final LockOptions lockOptions = queryOptions.getLockOptions(); + if ( appliedLockOptions == null ) { + if ( lockOptions != null && !lockOptions.isEmpty() ) { + return false; + } + } + else if ( !appliedLockOptions.isCompatible( lockOptions ) ) { + return false; + } + final Limit limit = queryOptions.getLimit(); + if ( offsetParameter == null && limitParameter == null ) { + if ( limit != null && !limit.isEmpty() ) { + return false; + } + } + if ( !isCompatible( offsetParameter, limit == null ? null : limit.getFirstRow(), 0 ) ) { + return false; + } + if ( !isCompatible( limitParameter, limit == null ? null : limit.getMaxRows(), Integer.MAX_VALUE ) ) { + return false; + } + return true; + } + + private boolean isCompatible(JdbcParameter parameter, Integer requestedValue, int defaultValue) { + final int value; + if ( requestedValue == null ) { + value = defaultValue; + } + else { + value = requestedValue; + } + if ( parameter != null ) { + final JdbcParameterBinding jdbcParameterBinding = appliedParameters.get( parameter ); + if ( jdbcParameterBinding != null ) { + if ( value != (int) jdbcParameterBinding.getBindValue() ) { + return false; + } + } + } + else if ( value != defaultValue ) { + return false; + } + return true; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java index 0095e8b65e..b07315e706 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java @@ -786,7 +786,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces entityInstance = null; missing = false; resolvedEntityState = null; - + identifierInitializers.forEach( initializer -> initializer.finishUpRow( rowProcessingState ) ); clearParentResolutionListeners(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java index e5f25129ff..c51a8ddb02 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java @@ -11,9 +11,16 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.function.Function; +import org.hibernate.LockOptions; +import org.hibernate.Session; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.pagination.LimitHandler; +import org.hibernate.dialect.pagination.NoopLimitHandler; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreLogging; +import org.hibernate.query.Limit; +import org.hibernate.query.spi.QueryOptions; import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcParameterBinder; @@ -64,30 +71,59 @@ public class DeferredResultSetAccess extends AbstractResultSetAccess { private void executeQuery() { final LogicalConnectionImplementor logicalConnection = getPersistenceContext().getJdbcCoordinator().getLogicalConnection(); final JdbcServices jdbcServices = getPersistenceContext().getFactory().getServiceRegistry().getService( JdbcServices.class ); - - final String sql = jdbcSelect.getSql(); - - try { - log.tracef( "Executing query to retrieve ResultSet : %s", sql ); - // prepare the query - preparedStatement = statementCreator.apply( sql ); - - // set options - if ( executionContext.getQueryOptions() != null ) { - if ( executionContext.getQueryOptions().getFetchSize() != null ) { - preparedStatement.setFetchSize( executionContext.getQueryOptions().getFetchSize() ); - } - if ( executionContext.getQueryOptions().getTimeout() != null ) { - preparedStatement.setQueryTimeout( executionContext.getQueryOptions().getTimeout() ); - } + final QueryOptions queryOptions = executionContext.getQueryOptions(); + final String finalSql; + final Limit limit; + final LimitHandler limitHandler; + if ( queryOptions == null ) { + finalSql = jdbcSelect.getSql(); + limit = null; + limitHandler = NoopLimitHandler.NO_LIMIT; + } + else { + // Note that limit and lock aren't set for SQM as that is applied during SQL rendering + // But for native queries, we have to adapt the SQL string + final Dialect dialect = executionContext.getSession().getJdbcServices().getDialect(); + final String sql; + limit = queryOptions.getLimit(); + if ( limit == null || limit.isEmpty() || jdbcSelect.usesLimitParameters() ) { + sql = jdbcSelect.getSql(); + limitHandler = NoopLimitHandler.NO_LIMIT; + } + else { + limitHandler = dialect.getLimitHandler(); + sql = limitHandler.processSql( + jdbcSelect.getSql(), + limit + ); } - // todo : limit/offset + finalSql = dialect.addSqlHintOrComment( + applyLocks( sql, queryOptions.getLockOptions() ), + queryOptions, + executionContext.getSession().getFactory().getSessionFactoryOptions().isCommentsEnabled() + ); + } + try { + log.tracef( "Executing query to retrieve ResultSet : %s", finalSql ); + // prepare the query + preparedStatement = statementCreator.apply( finalSql ); + + // set options + if ( queryOptions != null ) { + if ( queryOptions.getFetchSize() != null ) { + preparedStatement.setFetchSize( queryOptions.getFetchSize() ); + } + if ( queryOptions.getTimeout() != null ) { + preparedStatement.setQueryTimeout( queryOptions.getTimeout() ); + } + } // bind parameters // todo : validate that all query parameters were bound? int paramBindingPosition = 1; + paramBindingPosition += limitHandler.bindLimitParametersAtStartOfQuery( limit, preparedStatement, paramBindingPosition ); for ( JdbcParameterBinder parameterBinder : jdbcSelect.getParameterBinders() ) { parameterBinder.bindParameterValue( preparedStatement, @@ -97,6 +133,18 @@ public class DeferredResultSetAccess extends AbstractResultSetAccess { ); } + paramBindingPosition += limitHandler.bindLimitParametersAtEndOfQuery( limit, preparedStatement, paramBindingPosition ); + + if ( !jdbcSelect.usesLimitParameters() && limit != null && limit.getMaxRows() != null ) { + limitHandler.setMaxRows( limit, preparedStatement ); + } + else { + final int maxRows = jdbcSelect.getMaxRows(); + if ( maxRows != Integer.MAX_VALUE ) { + preparedStatement.setMaxRows( maxRows ); + } + } + executionContext.getSession().getEventListenerManager().jdbcExecuteStatementStart(); try { resultSet = preparedStatement.executeQuery(); @@ -104,13 +152,25 @@ public class DeferredResultSetAccess extends AbstractResultSetAccess { finally { executionContext.getSession().getEventListenerManager().jdbcExecuteStatementEnd(); } + + // For dialects that don't support an offset clause + final int rowsToSkip; + if ( !jdbcSelect.usesLimitParameters() && limit != null && limit.getFirstRow() != null && !limitHandler.supportsOffset() ) { + rowsToSkip = limit.getFirstRow(); + } + else { + rowsToSkip = jdbcSelect.getRowsToSkip(); + } + if ( rowsToSkip != 0 ) { + resultSet.absolute( rowsToSkip ); + } logicalConnection.getResourceRegistry().register( resultSet, preparedStatement ); } catch (SQLException e) { throw jdbcServices.getSqlExceptionHelper().convert( e, - "JDBC exception executing SQL [" + sql + "]" + "JDBC exception executing SQL [" + finalSql + "]" ); } finally { @@ -118,6 +178,22 @@ public class DeferredResultSetAccess extends AbstractResultSetAccess { } } + private String applyLocks(String sql, LockOptions lockOptions) { + if ( lockOptions != null && !lockOptions.isEmpty() ) { + // Locks are applied during SQL rendering, but for native queries, we apply locks separately + final LockOptions originalLockOptions = lockOptions.makeCopy(); + executionContext.getCallback().registerAfterLoadAction( + (session, entity, persister) -> { + ( (Session) session ).buildLockRequest( originalLockOptions ).lock( + persister.getEntityName(), + entity + ); + } + ); + } + return sql; + } + @Override public void release() { if ( resultSet != null ) { diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/DB2390DialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DB2390DialectTestCase.java similarity index 95% rename from hibernate-core/src/test/java/org/hibernate/dialect/DB2390DialectTestCase.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DB2390DialectTestCase.java index 8ea9374133..c58bf9ae77 100644 --- a/hibernate-core/src/test/java/org/hibernate/dialect/DB2390DialectTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DB2390DialectTestCase.java @@ -4,7 +4,7 @@ * 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.dialect; +package org.hibernate.orm.test.dialect; import java.util.Arrays; import java.util.List; @@ -15,6 +15,7 @@ import javax.persistence.Entity; import javax.persistence.Id; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DB2zDialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.junit.After; import org.junit.Before; @@ -30,7 +31,7 @@ import static org.junit.Assert.assertEquals; * @author Chris Cranford */ @TestForIssue(jiraKey = "HHH-11747") -@RequiresDialect(DB2390Dialect.class) +@RequiresDialect(DB2zDialect.class) public class DB2390DialectTestCase extends BaseEntityManagerFunctionalTestCase { @Override protected Class[] getAnnotatedClasses() { diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/DB2DialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DB2DialectTestCase.java similarity index 96% rename from hibernate-core/src/test/java/org/hibernate/dialect/DB2DialectTestCase.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DB2DialectTestCase.java index 824412ac76..d021843494 100644 --- a/hibernate-core/src/test/java/org/hibernate/dialect/DB2DialectTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DB2DialectTestCase.java @@ -4,10 +4,11 @@ * 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.dialect; +package org.hibernate.orm.test.dialect; import java.sql.Types; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.spi.RowSelection; import org.junit.Test; diff --git a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/SQLServerDialectPaginationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/SQLServerDialectPaginationTest.java similarity index 98% rename from hibernate-core/src/test/java/org/hibernate/test/dialect/functional/SQLServerDialectPaginationTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/SQLServerDialectPaginationTest.java index d4c9904942..193eec2c34 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/SQLServerDialectPaginationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/SQLServerDialectPaginationTest.java @@ -4,7 +4,7 @@ * 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.test.dialect.functional; +package org.hibernate.orm.test.dialect.functional; import java.io.Serializable; import java.util.Arrays; diff --git a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/SQLServerDialectTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/SQLServerDialectTest.java similarity index 97% rename from hibernate-core/src/test/java/org/hibernate/test/dialect/functional/SQLServerDialectTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/SQLServerDialectTest.java index 0814a1220f..8c80c7e921 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/SQLServerDialectTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/SQLServerDialectTest.java @@ -4,7 +4,7 @@ * 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.test.dialect.functional; +package org.hibernate.orm.test.dialect.functional; import java.util.Arrays; import java.util.Collections; @@ -25,12 +25,17 @@ import org.hibernate.Transaction; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.dialect.SQLServer2005Dialect; +import org.hibernate.dialect.SQLServerDialect; import org.hibernate.exception.LockTimeoutException; import org.hibernate.query.Query; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.test.dialect.functional.Category; +import org.hibernate.test.dialect.functional.Contact; +import org.hibernate.test.dialect.functional.Folder; +import org.hibernate.test.dialect.functional.Product2; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; @@ -46,7 +51,7 @@ import static org.junit.Assert.assertTrue; * * @author Guenther Demetz */ -@RequiresDialect(value = { SQLServer2005Dialect.class }) +@RequiresDialect(SQLServerDialect.class) public class SQLServerDialectTest extends BaseCoreFunctionalTestCase { @Override @@ -277,6 +282,7 @@ public class SQLServerDialectTest extends BaseCoreFunctionalTestCase { Root root = criteria.from( Category.class ); Join products = root.join( "products", JoinType.INNER ); criteria.multiselect( root.get( "id" ), criteriaBuilder.countDistinct( products.get( "id" ) ) ); + criteria.groupBy( root.get( "id" ) ); criteria.orderBy( criteriaBuilder.asc( root.get( "id" ) ) ); Query query = session.createQuery( criteria ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/cache/SQLFunctionsInterSystemsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/cache/SQLFunctionsInterSystemsTest.java similarity index 99% rename from hibernate-core/src/test/java/org/hibernate/test/dialect/functional/cache/SQLFunctionsInterSystemsTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/cache/SQLFunctionsInterSystemsTest.java index 0a0314a1af..4b0d584ae6 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/cache/SQLFunctionsInterSystemsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/cache/SQLFunctionsInterSystemsTest.java @@ -4,7 +4,7 @@ * 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.test.dialect.functional.cache; +package org.hibernate.orm.test.dialect.functional.cache; import java.sql.Connection; import java.sql.SQLException; @@ -19,6 +19,7 @@ import java.util.List; import java.util.Map; import org.hibernate.LockOptions; +import org.hibernate.dialect.CacheDialect; import org.hibernate.query.Query; import org.hibernate.ScrollableResults; import org.hibernate.Session; @@ -29,6 +30,7 @@ import org.hibernate.jdbc.Work; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.test.dialect.functional.cache.TestInterSystemsFunctionsClass; import org.hibernate.test.legacy.Blobber; import org.hibernate.test.legacy.Broken; import org.hibernate.test.legacy.Fixed; @@ -46,7 +48,7 @@ import static org.junit.Assert.assertTrue; * * @author Jonathan Levinson */ -@RequiresDialect( value = Cache71Dialect.class ) +@RequiresDialect( value = CacheDialect.class ) public class SQLFunctionsInterSystemsTest extends BaseCoreFunctionalTestCase { public String[] getMappings() { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/ast/CriteriaEntityGraphTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/ast/CriteriaEntityGraphTest.java index ff6187e5c6..32555e20e3 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/ast/CriteriaEntityGraphTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/ast/CriteriaEntityGraphTest.java @@ -24,7 +24,6 @@ import javax.persistence.OneToMany; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; -import org.hibernate.engine.spi.EffectiveEntityGraph; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.graph.GraphSemantic; @@ -37,8 +36,8 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.hql.spi.HqlQueryImplementor; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.query.sqm.internal.QuerySqmImpl; -import org.hibernate.query.sqm.sql.SqmSelectTranslation; -import org.hibernate.query.sqm.sql.internal.StandardSqmSelectTranslator; +import org.hibernate.query.sqm.sql.SqmTranslation; +import org.hibernate.query.sqm.sql.internal.StandardSqmTranslator; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.sql.ast.tree.from.CompositeTableGroup; import org.hibernate.sql.ast.tree.from.FromClause; @@ -372,7 +371,8 @@ public class CriteriaEntityGraphTest implements SessionFactoryScopeAware { final SqmSelectStatement sqmStatement = (SqmSelectStatement) hqlQuery.getSqmStatement(); - final StandardSqmSelectTranslator sqmConverter = new StandardSqmSelectTranslator( + final StandardSqmTranslator sqmConverter = new StandardSqmTranslator<>( + sqmStatement, hqlQuery.getQueryOptions(), ( (QuerySqmImpl) hqlQuery ).getDomainParameterXref(), query.getParameterBindings(), @@ -380,7 +380,7 @@ public class CriteriaEntityGraphTest implements SessionFactoryScopeAware { session.getSessionFactory() ); - final SqmSelectTranslation sqmInterpretation = sqmConverter.translate( sqmStatement ); + final SqmTranslation sqmInterpretation = sqmConverter.translate(); return sqmInterpretation.getSqlAst(); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/ast/HqlEntityGraphTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/ast/HqlEntityGraphTest.java index c85d4fa996..7ec562d33a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/ast/HqlEntityGraphTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/ast/HqlEntityGraphTest.java @@ -33,8 +33,8 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.hql.spi.HqlQueryImplementor; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.query.sqm.internal.QuerySqmImpl; -import org.hibernate.query.sqm.sql.SqmSelectTranslation; -import org.hibernate.query.sqm.sql.internal.StandardSqmSelectTranslator; +import org.hibernate.query.sqm.sql.SqmTranslation; +import org.hibernate.query.sqm.sql.internal.StandardSqmTranslator; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.sql.ast.tree.from.CompositeTableGroup; import org.hibernate.sql.ast.tree.from.FromClause; @@ -367,7 +367,8 @@ public class HqlEntityGraphTest implements SessionFactoryScopeAware { final SqmSelectStatement sqmStatement = (SqmSelectStatement) hqlQuery.getSqmStatement(); - final StandardSqmSelectTranslator sqmConverter = new StandardSqmSelectTranslator( + final StandardSqmTranslator sqmConverter = new StandardSqmTranslator<>( + sqmStatement, hqlQuery.getQueryOptions(), ( (QuerySqmImpl) hqlQuery ).getDomainParameterXref(), query.getParameterBindings(), @@ -375,7 +376,7 @@ public class HqlEntityGraphTest implements SessionFactoryScopeAware { session.getSessionFactory() ); - final SqmSelectTranslation sqmInterpretation = sqmConverter.translate( sqmStatement ); + final SqmTranslation sqmInterpretation = sqmConverter.translate(); return sqmInterpretation.getSqlAst(); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/limitExpression/LimitExpressionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/query/LimitExpressionTest.java similarity index 96% rename from hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/limitExpression/LimitExpressionTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/query/LimitExpressionTest.java index 40fea60789..1cff5c2c37 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/limitExpression/LimitExpressionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/query/LimitExpressionTest.java @@ -4,7 +4,7 @@ * 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.jpa.test.criteria.limitExpression; +package org.hibernate.orm.test.jpa.criteria.query; import java.util.List; import javax.persistence.Entity; diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/LimitExpressionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/LimitExpressionTest.java similarity index 84% rename from hibernate-core/src/test/java/org/hibernate/jpa/test/query/LimitExpressionTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/LimitExpressionTest.java index 5505d2143c..53f77d7e06 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/query/LimitExpressionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/LimitExpressionTest.java @@ -4,7 +4,7 @@ * 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.jpa.test.query; +package org.hibernate.orm.test.jpa.query; import java.util.List; import javax.persistence.Entity; @@ -12,12 +12,9 @@ import javax.persistence.EntityManager; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Query; -import javax.persistence.criteria.CriteriaQuery; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.transaction.TransactionUtil; import org.junit.Before; @@ -29,10 +26,6 @@ import static junit.framework.TestCase.assertTrue; * @author Andrea Boriero */ -@RequiresDialectFeature( - value = DialectChecks.SupportLimitCheck.class, - comment = "Dialect does not support limit" -) public class LimitExpressionTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/pagination/DataMetaPoint.java b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/DataMetaPoint.java similarity index 97% rename from hibernate-core/src/test/java/org/hibernate/test/pagination/DataMetaPoint.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/pagination/DataMetaPoint.java index be7fd3dcf7..9afba4fa7d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/pagination/DataMetaPoint.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/DataMetaPoint.java @@ -4,7 +4,7 @@ * 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.test.pagination; +package org.hibernate.orm.test.pagination; /** * @author Piotr Findeisen diff --git a/hibernate-core/src/test/java/org/hibernate/test/pagination/DataPoint.java b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/DataPoint.java old mode 100755 new mode 100644 similarity index 98% rename from hibernate-core/src/test/java/org/hibernate/test/pagination/DataPoint.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/pagination/DataPoint.java index 021c1a7733..4141c6f9e6 --- a/hibernate-core/src/test/java/org/hibernate/test/pagination/DataPoint.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/DataPoint.java @@ -4,7 +4,7 @@ * 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.test.pagination; +package org.hibernate.orm.test.pagination; import java.math.BigDecimal; /** diff --git a/hibernate-core/src/test/java/org/hibernate/test/pagination/DistinctSelectTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/DistinctSelectTest.java similarity index 93% rename from hibernate-core/src/test/java/org/hibernate/test/pagination/DistinctSelectTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/pagination/DistinctSelectTest.java index 57c1bd7068..6364787d59 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/pagination/DistinctSelectTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/DistinctSelectTest.java @@ -4,7 +4,7 @@ * 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.test.pagination; +package org.hibernate.orm.test.pagination; import java.util.ArrayList; import java.util.List; @@ -30,6 +30,11 @@ import static org.junit.Assert.assertFalse; public class DistinctSelectTest extends BaseCoreFunctionalTestCase { private static final int NUM_OF_USERS = 30; + @Override + protected String getBaseForMappings() { + return "org/hibernate/orm/test/"; + } + @Override public String[] getMappings() { return new String[] { "pagination/EntryTag.hbm.xml" }; diff --git a/hibernate-core/src/test/java/org/hibernate/test/pagination/Entry.java b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/Entry.java similarity index 96% rename from hibernate-core/src/test/java/org/hibernate/test/pagination/Entry.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/pagination/Entry.java index 0f5cb694b3..083f4d1219 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/pagination/Entry.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/Entry.java @@ -4,7 +4,7 @@ * 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.test.pagination; +package org.hibernate.orm.test.pagination; import java.util.HashSet; import java.util.Set; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/FetchClausePaginationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/FetchClausePaginationTest.java new file mode 100644 index 0000000000..7fb67b4201 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/FetchClausePaginationTest.java @@ -0,0 +1,83 @@ +/* + * 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.pagination; + +import java.util.List; + +import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.hibernate.testing.orm.domain.gambit.EntityOfLists; +import org.hibernate.testing.orm.domain.gambit.EnumValue; +import org.hibernate.testing.orm.domain.gambit.SimpleComponent; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * + * @author Christian Beikov + */ +@DomainModel( standardModels = StandardDomainModel.GAMBIT ) +@ServiceRegistry +@SessionFactory +public class FetchClausePaginationTest { + @BeforeEach + public void createTestData(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final EntityOfLists entityContainingLists = new EntityOfLists( 1, "first" ); + + entityContainingLists.addBasic( "abc" ); + entityContainingLists.addBasic( "def" ); + entityContainingLists.addBasic( "ghi" ); + + entityContainingLists.addConvertedEnum( EnumValue.TWO ); + + entityContainingLists.addEnum( EnumValue.ONE ); + entityContainingLists.addEnum( EnumValue.THREE ); + + entityContainingLists.addComponent( new SimpleComponent( "first-a1", "first-another-a1" ) ); + entityContainingLists.addComponent( new SimpleComponent( "first-a2", "first-another-a2" ) ); + entityContainingLists.addComponent( new SimpleComponent( "first-a3", "first-another-a2" ) ); + + session.save( entityContainingLists ); + } + ); + } + + @AfterEach + public void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "delete from EntityOfLists" ).executeUpdate(); + session.createQuery( "delete from SimpleEntity" ).executeUpdate(); + } + ); + } + + @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsWithTies.class) + public void testFetchWithTies(SessionFactoryScope scope) { + scope.inSession( + session -> { + List list = session.createQuery( + "select comp from EntityOfLists e join e.listOfComponents comp order by comp.anotherAttribute desc fetch first 1 row with ties", + SimpleComponent.class + ).list(); + assertThat( list.size(), is( 2 ) ); + } + ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/pagination/OraclePaginationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/OraclePaginationTest.java similarity index 97% rename from hibernate-core/src/test/java/org/hibernate/test/pagination/OraclePaginationTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/pagination/OraclePaginationTest.java index fda480877b..2c53e661d2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/pagination/OraclePaginationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/OraclePaginationTest.java @@ -1,4 +1,4 @@ -package org.hibernate.test.pagination; +package org.hibernate.orm.test.pagination; import java.io.Serializable; import java.util.List; @@ -11,7 +11,7 @@ import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; -import org.hibernate.dialect.Oracle9iDialect; +import org.hibernate.dialect.OracleDialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.RequiresDialect; @@ -24,7 +24,7 @@ import static org.junit.Assert.assertEquals; /** * @author Vlad Mihalcea */ -@RequiresDialect(Oracle9iDialect.class) +@RequiresDialect(OracleDialect.class) public class OraclePaginationTest extends BaseEntityManagerFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/pagination/PaginationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/PaginationTest.java old mode 100755 new mode 100644 similarity index 90% rename from hibernate-core/src/test/java/org/hibernate/test/pagination/PaginationTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/pagination/PaginationTest.java index 0ae47ac813..712d8cfd32 --- a/hibernate-core/src/test/java/org/hibernate/test/pagination/PaginationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/PaginationTest.java @@ -4,13 +4,10 @@ * 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.test.pagination; +package org.hibernate.orm.test.pagination; import java.math.BigDecimal; -import java.util.Collections; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -20,18 +17,13 @@ import org.hibernate.query.NativeQuery; import org.hibernate.query.Query; import org.hibernate.Session; -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; -import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.junit.After; import org.junit.Before; import org.junit.Test; -import static java.lang.String.format; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; /** * @author Gavin King @@ -39,6 +31,11 @@ import static org.junit.Assert.fail; public class PaginationTest extends BaseNonConfigCoreFunctionalTestCase { public static final int NUMBER_OF_TEST_ROWS = 100; + @Override + protected String getBaseForMappings() { + return "org/hibernate/orm/test/"; + } + @Override public String[] getMappings() { return new String[] { "pagination/DataPoint.hbm.xml" }; @@ -50,10 +47,6 @@ public class PaginationTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - @RequiresDialectFeature( - value = DialectChecks.SupportLimitCheck.class, - comment = "Dialect does not support limit" - ) public void testLimit() { inTransaction( session -> { @@ -107,10 +100,6 @@ public class PaginationTest extends BaseNonConfigCoreFunctionalTestCase { } @Test - @RequiresDialectFeature( - value = DialectChecks.SupportLimitAndOffsetCheck.class, - comment = "Dialect does not support limit+offset" - ) public void testLimitOffset() { inTransaction( session -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/SubqueryPaginationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/SubqueryPaginationTest.java new file mode 100644 index 0000000000..dd97c585ec --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/SubqueryPaginationTest.java @@ -0,0 +1,96 @@ +/* + * 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.pagination; + +import java.util.List; + +import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.hibernate.testing.orm.domain.gambit.EntityOfLists; +import org.hibernate.testing.orm.domain.gambit.EnumValue; +import org.hibernate.testing.orm.domain.gambit.SimpleComponent; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * + * @author Christian Beikov + */ +@DomainModel( standardModels = StandardDomainModel.GAMBIT ) +@ServiceRegistry +@SessionFactory +public class SubqueryPaginationTest { + @BeforeEach + public void createTestData(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final EntityOfLists entityContainingLists = new EntityOfLists( 1, "first" ); + + entityContainingLists.addBasic( "abc" ); + entityContainingLists.addBasic( "def" ); + entityContainingLists.addBasic( "ghi" ); + + entityContainingLists.addConvertedEnum( EnumValue.TWO ); + + entityContainingLists.addEnum( EnumValue.ONE ); + entityContainingLists.addEnum( EnumValue.THREE ); + + entityContainingLists.addComponent( new SimpleComponent( "first-a1", "first-another-a1" ) ); + entityContainingLists.addComponent( new SimpleComponent( "first-a2", "first-another-a2" ) ); + entityContainingLists.addComponent( new SimpleComponent( "first-a3", "first-another-a2" ) ); + + session.save( entityContainingLists ); + } + ); + } + + @AfterEach + public void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "delete from EntityOfLists" ).executeUpdate(); + session.createQuery( "delete from SimpleEntity" ).executeUpdate(); + } + ); + } + + @Test + public void testLimitInSubquery(SessionFactoryScope scope) { + scope.inSession( + session -> { + List list = session.createQuery( + "from EntityOfLists e where 'abc' = (select basic from e.listOfBasics basic order by basic limit 1)", + EntityOfLists.class + ).list(); + assertThat( list.size(), is( 1 ) ); + } + ); + } + + @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsOffsetInSubquery.class) + public void testLimitAndOffsetInSubquery(SessionFactoryScope scope) { + scope.inSession( + session -> { + List list = session.createQuery( + "from EntityOfLists e where 'def' = (select basic from e.listOfBasics basic order by basic limit 1 offset 1)", + EntityOfLists.class + ).list(); + assertThat( list.size(), is( 1 ) ); + } + ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/pagination/Tag.java b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/Tag.java similarity index 93% rename from hibernate-core/src/test/java/org/hibernate/test/pagination/Tag.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/pagination/Tag.java index ededf7a55e..940f8bff29 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/pagination/Tag.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/Tag.java @@ -4,7 +4,7 @@ * 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.test.pagination; +package org.hibernate.orm.test.pagination; public class Tag { diff --git a/hibernate-core/src/test/java/org/hibernate/test/pagination/hhh9965/HHH9965Test.java b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/hhh9965/HHH9965Test.java similarity index 96% rename from hibernate-core/src/test/java/org/hibernate/test/pagination/hhh9965/HHH9965Test.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/pagination/hhh9965/HHH9965Test.java index 6f4450cd3d..fc64caaef4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/pagination/hhh9965/HHH9965Test.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/hhh9965/HHH9965Test.java @@ -4,7 +4,7 @@ * 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.test.pagination.hhh9965; +package org.hibernate.orm.test.pagination.hhh9965; import org.hibernate.Session; import org.hibernate.cfg.Configuration; diff --git a/hibernate-core/src/test/java/org/hibernate/test/pagination/hhh9965/Product.java b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/hhh9965/Product.java similarity index 92% rename from hibernate-core/src/test/java/org/hibernate/test/pagination/hhh9965/Product.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/pagination/hhh9965/Product.java index 6af4636d56..c8e90f034c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/pagination/hhh9965/Product.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/hhh9965/Product.java @@ -4,7 +4,7 @@ * 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.test.pagination.hhh9965; +package org.hibernate.orm.test.pagination.hhh9965; import javax.persistence.Entity; import javax.persistence.GeneratedValue; diff --git a/hibernate-core/src/test/java/org/hibernate/test/pagination/hhh9965/Shop.java b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/hhh9965/Shop.java similarity index 93% rename from hibernate-core/src/test/java/org/hibernate/test/pagination/hhh9965/Shop.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/pagination/hhh9965/Shop.java index f6d16eb7fa..235bf3068b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/pagination/hhh9965/Shop.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/pagination/hhh9965/Shop.java @@ -4,7 +4,7 @@ * 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.test.pagination.hhh9965; +package org.hibernate.orm.test.pagination.hhh9965; import javax.persistence.*; import java.util.List; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/AliasCollisionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/AliasCollisionTest.java index b9a906e500..49b980a588 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/AliasCollisionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/AliasCollisionTest.java @@ -97,7 +97,7 @@ public class AliasCollisionTest extends BaseSqmUnitTest { assertThat( selections, hasSize( 1 ) ); assertThat( selections.get( 0 ).getAlias(), nullValue() ); - final List roots = querySpec.getFromClause().getRoots(); + final List> roots = querySpec.getFromClause().getRoots(); assertThat( roots, hasSize( 1 ) ); assertThat( roots.get( 0 ).getJoins(), isEmpty() ); assertThat( roots.get( 0 ).getExplicitAlias(), is( "a" ) ); @@ -105,7 +105,7 @@ public class AliasCollisionTest extends BaseSqmUnitTest { assertThat( querySpec.getWhereClause().getPredicate(), instanceOf( SqmInSubQueryPredicate.class ) ); final SqmInSubQueryPredicate predicate = (SqmInSubQueryPredicate) querySpec.getWhereClause().getPredicate(); - final SqmRoot subQueryRoot = predicate.getSubQueryExpression() + final SqmRoot subQueryRoot = predicate.getSubQueryExpression() .getQuerySpec() .getFromClause() .getRoots() @@ -126,7 +126,7 @@ public class AliasCollisionTest extends BaseSqmUnitTest { assertThat( selections, hasSize( 1 ) ); assertThat( selections.get( 0 ).getAlias(), nullValue() ); - final List roots = querySpec.getFromClause().getRoots(); + final List> roots = querySpec.getFromClause().getRoots(); assertThat( roots, hasSize( 1 ) ); assertThat( roots.get( 0 ).getJoins(), isEmpty() ); assertThat( roots.get( 0 ).getExplicitAlias(), is( "a" ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/AttributePathTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/AttributePathTests.java index b303853667..ff7b3e78c3 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/AttributePathTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/AttributePathTests.java @@ -50,7 +50,7 @@ public class AttributePathTests extends BaseSqmUnitTest { final SqmSelectStatement statement = interpretSelect( "select s.mate.dob, s.mate.numberOfToes from Person s" ); assertThat( statement.getQuerySpec().getFromClause().getRoots().size(), is(1) ); - final SqmRoot sqmRoot = statement.getQuerySpec().getFromClause().getRoots().get( 0 ); + final SqmRoot sqmRoot = statement.getQuerySpec().getFromClause().getRoots().get( 0 ); assertThat( sqmRoot.getJoins().size(), is(0) ); assertThat( sqmRoot.getImplicitJoinPaths().size(), is(1) ); @@ -78,7 +78,7 @@ public class AttributePathTests extends BaseSqmUnitTest { final SqmSelectStatement statement = interpretSelect( "select s.mate from Person s where s.mate.dob = ?1" ); assertThat( statement.getQuerySpec().getFromClause().getRoots().size(), is(1) ); - final SqmRoot sqmRoot = statement.getQuerySpec().getFromClause().getRoots().get( 0 ); + final SqmRoot sqmRoot = statement.getQuerySpec().getFromClause().getRoots().get( 0 ); assertThat( sqmRoot.getJoins().size(), is(0) ); assertThat( sqmRoot.getImplicitJoinPaths().size(), is(1) ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FromClauseTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FromClauseTests.java index 5c9605ff3b..215fa323fe 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FromClauseTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FromClauseTests.java @@ -53,7 +53,7 @@ public class FromClauseTests extends BaseSqmUnitTest { assertThat( fromClause, notNullValue() ); assertThat( fromClause.getRoots(), hasSize( 1 ) ); - final SqmRoot firstRoot = fromClause.getRoots().get( 0 ); + final SqmRoot firstRoot = fromClause.getRoots().get( 0 ); assertThat( firstRoot, notNullValue() ); assertThat( firstRoot.getJoins(), isEmpty() ); assertThat( firstRoot.getExplicitAlias(), is( "p") ); @@ -72,12 +72,12 @@ public class FromClauseTests extends BaseSqmUnitTest { assertThat( fromClause.getRoots(), hasSize( 2 ) ); - final SqmRoot firstRoot = fromClause.getRoots().get( 0 ); + final SqmRoot firstRoot = fromClause.getRoots().get( 0 ); assertThat( firstRoot, notNullValue() ); assertThat( firstRoot.getJoins(), isEmpty() ); assertThat( firstRoot.getExplicitAlias(), is( "p") ); - final SqmRoot secondRoot = fromClause.getRoots().get( 0 ); + final SqmRoot secondRoot = fromClause.getRoots().get( 0 ); assertThat( secondRoot, notNullValue() ); assertThat( secondRoot.getJoins(), isEmpty() ); assertThat( secondRoot.getExplicitAlias(), is( "p") ); @@ -91,7 +91,7 @@ public class FromClauseTests extends BaseSqmUnitTest { assertThat( fromClause, notNullValue() ); assertThat( fromClause.getRoots(), hasSize( 1 ) ); - final SqmRoot sqmRoot = fromClause.getRoots().get( 0 ); + final SqmRoot sqmRoot = fromClause.getRoots().get( 0 ); assertThat( sqmRoot, notNullValue() ); assertThat( sqmRoot.getJoins(), isEmpty() ); assertThat( sqmRoot.getExplicitAlias(), nullValue() ); 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 9fff9666aa..42b22471c5 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 @@ -38,7 +38,7 @@ public class InsertUpdateTests { ); } - @Test @FailureExpected(reason = "update broken for secondary tables") + @Test public void testUpdateSecondaryTable(SessionFactoryScope scope) { scope.inTransaction( session -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/SelectClauseTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/SelectClauseTests.java index 3460e54048..8e7b6df1ec 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/SelectClauseTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/SelectClauseTests.java @@ -105,7 +105,7 @@ public class SelectClauseTests extends BaseSqmUnitTest { final SqmSelection selection = querySpec.getSelectClause().getSelections().get( 0 ); assertThat( querySpec.getFromClause().getRoots().size(), is(1) ); - final SqmRoot root = querySpec.getFromClause().getRoots().get( 0 ); + final SqmRoot root = querySpec.getFromClause().getRoots().get( 0 ); assertThat( root.getEntityName(), endsWith( "Person" ) ); assertThat( root.getJoins().size(), is(0) ); @@ -131,10 +131,10 @@ public class SelectClauseTests extends BaseSqmUnitTest { assertThat( querySpec.getFromClause().getRoots().size(), is(2) ); - final SqmRoot entityRoot = querySpec.getFromClause().getRoots().get( 0 ); + final SqmRoot entityRoot = querySpec.getFromClause().getRoots().get( 0 ); assertThat( entityRoot.getEntityName(), endsWith( "Person" ) ); - final SqmRoot entity2Root = querySpec.getFromClause().getRoots().get( 1 ); + final SqmRoot entity2Root = querySpec.getFromClause().getRoots().get( 1 ); assertThat( entity2Root.getEntityName(), endsWith( "Person" ) ); SqmBinaryArithmetic addExpression = (SqmBinaryArithmetic) selection.getSelectableNode(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryParameterTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryParameterTests.java index aef8c9613f..d6e75f4feb 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryParameterTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryParameterTests.java @@ -35,7 +35,7 @@ public class NativeQueryParameterTests { public void testBasicParameterBinding(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createNativeQuery( "select t.id, t.key, t.subject from ticket t where t.key = ?" ) + session.createNativeQuery( "select t.id, t.ticket_key, t.subject from ticket t where t.ticket_key = ?" ) .setParameter( 1, "ABC-123" ) .list(); } @@ -45,7 +45,7 @@ public class NativeQueryParameterTests { @Test public void testJpaStylePositionalParametersInNativeSql(SessionFactoryScope scope) { scope.inTransaction( - s -> s.createNativeQuery( "select t.subject from ticket t where t.key = ?1" ).setParameter( 1, "ABC-123" ).list() + s -> s.createNativeQuery( "select t.subject from ticket t where t.ticket_key = ?1" ).setParameter( 1, "ABC-123" ).list() ); } @@ -53,7 +53,7 @@ public class NativeQueryParameterTests { public void testTypedParameterBinding(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createNativeQuery( "select t.id, t.key, t.subject from ticket t where t.key = ?" ) + session.createNativeQuery( "select t.id, t.ticket_key, t.subject from ticket t where t.ticket_key = ?" ) .setParameter( 1, "ABC-123", StandardBasicTypes.STRING ) .list(); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java index 2db6da92e1..04548cbe75 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java @@ -13,6 +13,7 @@ import java.time.Instant; import java.util.List; import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.DerbyDialect; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.internal.BasicValuedSingularAttributeMapping; @@ -79,9 +80,10 @@ public class NativeQueryResultBuilderTests { public void fullyImplicitTest2(SessionFactoryScope scope) { scope.inTransaction( session -> { - // DB2 returns an Integer for count by default + // DB2 and Derby return an Integer for count by default Assumptions.assumeThat( session.getJdbcServices().getDialect() ) - .isNotInstanceOf( DB2Dialect.class ); + .isNotInstanceOf( DB2Dialect.class ) + .isNotInstanceOf( DerbyDialect.class ); final String sql = "select count(theString) from EntityOfBasics"; final NativeQueryImplementor query = session.createNativeQuery( sql ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sqm/exec/CrossJoinTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sqm/exec/CrossJoinTest.java index 03599f83dc..6bf2d26349 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sqm/exec/CrossJoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sqm/exec/CrossJoinTest.java @@ -27,7 +27,7 @@ public class CrossJoinTest extends SessionFactoryBasedFunctionalTest { inTransaction( session -> { session.createQuery( - "from SimpleEntity e1, SimpleEntity e2 where e1.id = e2.id and e1.someDate = {d '2018-01-01'}" ) + "from SimpleEntity e1, SimpleEntity e2 where e1.id = e2.id and e1.someDate = {ts '2018-01-01 00:00:00'}" ) .list(); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/set/SetOperationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/set/SetOperationTest.java new file mode 100644 index 0000000000..6e2ef5e841 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/set/SetOperationTest.java @@ -0,0 +1,91 @@ +/* + * 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.set; + +import java.util.List; + +import javax.persistence.Tuple; + +import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.hibernate.testing.orm.domain.gambit.EntityOfLists; +import org.hibernate.testing.orm.domain.gambit.EnumValue; +import org.hibernate.testing.orm.domain.gambit.SimpleComponent; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * + * @author Christian Beikov + */ +@DomainModel( standardModels = StandardDomainModel.GAMBIT ) +@ServiceRegistry +@SessionFactory +public class SetOperationTest { + @BeforeEach + public void createTestData(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.save( new EntityOfLists( 1, "first" ) ); + session.save( new EntityOfLists( 2, "second" ) ); + session.save( new EntityOfLists( 3, "third" ) ); + } + ); + } + + @AfterEach + public void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "delete from EntityOfLists" ).executeUpdate(); + session.createQuery( "delete from SimpleEntity" ).executeUpdate(); + } + ); + } + + @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsUnion.class) + public void testUnionAll(SessionFactoryScope scope) { + scope.inSession( + session -> { + List list = session.createQuery( + "select e from EntityOfLists e where e.id = 1 " + + "union all " + + "select e from EntityOfLists e where e.id = 2", + EntityOfLists.class + ).list(); + assertThat( list.size(), is( 2 ) ); + } + ); + } + + @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsUnion.class) + public void testUnionAllLimit(SessionFactoryScope scope) { + scope.inSession( + session -> { + List list = session.createQuery( + "select e.id, e from EntityOfLists e where e.id = 1 " + + "union all " + + "select e.id, e from EntityOfLists e where e.id = 2 " + + "order by 1 fetch first 1 row only", + Tuple.class + ).list(); + assertThat( list.size(), is( 1 ) ); + } + ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java index 00b118c288..54deb1bd89 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java @@ -17,12 +17,13 @@ import org.hibernate.orm.test.metamodel.mapping.SmokeTests.SimpleEntity; import org.hibernate.query.NavigablePath; import org.hibernate.query.hql.spi.HqlQueryImplementor; import org.hibernate.query.spi.QueryImplementor; +import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.sqm.internal.QuerySqmImpl; -import org.hibernate.query.sqm.sql.SqmSelectTranslation; -import org.hibernate.query.sqm.sql.internal.StandardSqmSelectTranslator; +import org.hibernate.query.sqm.sql.SqmTranslation; +import org.hibernate.query.sqm.sql.internal.StandardSqmTranslator; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.sql.ast.spi.SqlSelection; -import org.hibernate.sql.ast.spi.StandardSqlAstSelectTranslator; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslator; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.from.FromClause; @@ -79,7 +80,8 @@ public class SmokeTests { //noinspection unchecked final SqmSelectStatement sqmStatement = (SqmSelectStatement) hqlQuery.getSqmStatement(); - final StandardSqmSelectTranslator sqmConverter = new StandardSqmSelectTranslator( + final StandardSqmTranslator sqmConverter = new StandardSqmTranslator<>( + sqmStatement, hqlQuery.getQueryOptions(), ( (QuerySqmImpl) hqlQuery ).getDomainParameterXref(), query.getParameterBindings(), @@ -87,7 +89,7 @@ public class SmokeTests { scope.getSessionFactory() ); - final SqmSelectTranslation sqmInterpretation = sqmConverter.translate( sqmStatement ); + final SqmTranslation sqmInterpretation = sqmConverter.translate(); final SelectStatement sqlAst = sqmInterpretation.getSqlAst(); final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); @@ -113,8 +115,10 @@ public class SmokeTests { assertThat( sqlSelection.getValuesArrayPosition(), is( 0 ) ); assertThat( sqlSelection.getJdbcValueExtractor(), notNullValue() ); - final JdbcSelect jdbcSelectOperation = new StandardSqlAstSelectTranslator( session.getSessionFactory() ) - .translate( sqlAst ); + final JdbcSelect jdbcSelectOperation = new StandardSqlAstTranslator( + session.getSessionFactory(), + sqlAst + ).translate( null, QueryOptions.NONE ); assertThat( jdbcSelectOperation.getSql(), @@ -133,7 +137,8 @@ public class SmokeTests { //noinspection unchecked final SqmSelectStatement sqmStatement = (SqmSelectStatement) hqlQuery.getSqmStatement(); - final StandardSqmSelectTranslator sqmConverter = new StandardSqmSelectTranslator( + final StandardSqmTranslator sqmConverter = new StandardSqmTranslator<>( + sqmStatement, hqlQuery.getQueryOptions(), ( (QuerySqmImpl) hqlQuery ).getDomainParameterXref(), query.getParameterBindings(), @@ -141,7 +146,7 @@ public class SmokeTests { scope.getSessionFactory() ); - final SqmSelectTranslation sqmInterpretation = sqmConverter.translate( sqmStatement ); + final SqmTranslation sqmInterpretation = sqmConverter.translate(); final SelectStatement sqlAst = sqmInterpretation.getSqlAst(); final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); @@ -206,8 +211,10 @@ public class SmokeTests { assertThat( valueConverter, notNullValue() ); assertThat( valueConverter, instanceOf( OrdinalEnumValueConverter.class ) ); - final JdbcSelect jdbcSelectOperation = new StandardSqlAstSelectTranslator( session.getSessionFactory() ) - .translate( sqlAst ); + final JdbcSelect jdbcSelectOperation = new StandardSqlAstTranslator( + session.getSessionFactory(), + sqlAst + ).translate( null, QueryOptions.NONE ); assertThat( jdbcSelectOperation.getSql(), diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/results/AbstractResultTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/results/AbstractResultTests.java index 9108f24f52..90980ed1c1 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/results/AbstractResultTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/results/AbstractResultTests.java @@ -12,8 +12,7 @@ import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.sql.SqmSelectTranslation; -import org.hibernate.query.sqm.sql.SqmSelectTranslator; +import org.hibernate.query.sqm.sql.SqmTranslator; import org.hibernate.query.sqm.sql.SqmTranslatorFactory; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.sql.ast.tree.select.SelectStatement; @@ -32,7 +31,8 @@ public class AbstractResultTests { final SqmSelectStatement sqm = (SqmSelectStatement) queryEngine.getHqlTranslator().translate( hql ); final SqmTranslatorFactory sqmTranslatorFactory = queryEngine.getSqmTranslatorFactory(); - final SqmSelectTranslator sqmConverter = sqmTranslatorFactory.createSelectTranslator( + final SqmTranslator sqmConverter = sqmTranslatorFactory.createSelectTranslator( + sqm, QueryOptions.NONE, DomainParameterXref.from( sqm ), parameterBindings, @@ -40,6 +40,6 @@ public class AbstractResultTests { sessionFactory ); - return sqmConverter.translate( sqm ).getSqlAst(); + return sqmConverter.translate().getSqlAst(); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/SQLServerDialectCollationTest.java b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/SQLServerDialectCollationTest.java index adc7befadc..46237acc8b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/SQLServerDialectCollationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/SQLServerDialectCollationTest.java @@ -22,6 +22,7 @@ import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.dialect.SQLServer2005Dialect; +import org.hibernate.dialect.SQLServerDialect; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.testing.RequiresDialect; @@ -40,7 +41,7 @@ import static org.junit.Assert.fail; * * @author Guenther Demetz */ -@RequiresDialect(value = { SQLServer2005Dialect.class }) +@RequiresDialect(SQLServerDialect.class) public class SQLServerDialectCollationTest extends BaseCoreFunctionalTestCase { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/LimitExpressionTest.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/LimitExpressionTest.java deleted file mode 100644 index 8aa20d7b5a..0000000000 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/LimitExpressionTest.java +++ /dev/null @@ -1,68 +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.test.legacy; - -import java.util.Iterator; -import java.util.List; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; - -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.hibernate.testing.transaction.TransactionUtil; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * @author Andrea Boriero - */ -public class LimitExpressionTest extends BaseCoreFunctionalTestCase { - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] {Person.class}; - } - - @Test - public void testLimitZero() throws Exception { - TransactionUtil.doInHibernate( this::sessionFactory, s -> { - Iterator iter = s.createQuery( "from Person p" ) - .setMaxResults( 0 ) - .list().iterator(); - - int count = 0; - while ( iter.hasNext() ) { - iter.next(); - count++; - } - assertEquals( 0, count ); - final List list = s.createQuery( "select p from Person p" ) - .setMaxResults( 0 ) - .setFirstResult( 2 ) - .list(); - assertTrue( list.isEmpty() ); - } ); - } - - @Before - public void prepareTest() throws Exception { - TransactionUtil.doInHibernate( this::sessionFactory, session -> { - Person p = new Person(); - session.save( p ); - } ); - } - - @Entity(name = "Person") - public static class Person { - @Id - @GeneratedValue - private Long id; - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java index 6497d31651..4027501816 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/TimeAndTimestampTest.java @@ -15,7 +15,9 @@ import javax.persistence.Id; import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQL8Dialect; +import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.Oracle9iDialect; +import org.hibernate.dialect.OracleDialect; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; @@ -30,8 +32,8 @@ import static org.junit.Assert.assertEquals; */ @TestForIssue(jiraKey = "HHH-10465") @SkipForDialect(MariaDBDialect.class) -@SkipForDialect(MySQL8Dialect.class) -@SkipForDialect(value = Oracle9iDialect.class, comment = "Oracle date does not support milliseconds ") +@SkipForDialect(MySQLDialect.class) +@SkipForDialect(value = OracleDialect.class, comment = "Oracle date does not support milliseconds ") @SkipForDialect(value = AbstractHANADialect.class, comment = "HANA date does not support milliseconds ") public class TimeAndTimestampTest extends BaseNonConfigCoreFunctionalTestCase { diff --git a/hibernate-core/src/test/java/org/hibernate/test/pagination/DataPoint.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/orm/test/pagination/DataPoint.hbm.xml old mode 100755 new mode 100644 similarity index 96% rename from hibernate-core/src/test/java/org/hibernate/test/pagination/DataPoint.hbm.xml rename to hibernate-core/src/test/resources/org/hibernate/orm/test/pagination/DataPoint.hbm.xml index 1b1452a591..6cb82a7b76 --- a/hibernate-core/src/test/java/org/hibernate/test/pagination/DataPoint.hbm.xml +++ b/hibernate-core/src/test/resources/org/hibernate/orm/test/pagination/DataPoint.hbm.xml @@ -10,7 +10,7 @@ "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> + package="org.hibernate.orm.test.pagination"> diff --git a/hibernate-core/src/test/java/org/hibernate/test/pagination/EntryTag.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/orm/test/pagination/EntryTag.hbm.xml old mode 100755 new mode 100644 similarity index 84% rename from hibernate-core/src/test/java/org/hibernate/test/pagination/EntryTag.hbm.xml rename to hibernate-core/src/test/resources/org/hibernate/orm/test/pagination/EntryTag.hbm.xml index 0b766d1769..7686131096 --- a/hibernate-core/src/test/java/org/hibernate/test/pagination/EntryTag.hbm.xml +++ b/hibernate-core/src/test/resources/org/hibernate/orm/test/pagination/EntryTag.hbm.xml @@ -9,7 +9,7 @@ "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - + @@ -25,7 +25,7 @@ - + diff --git a/hibernate-core/src/test_legacy/org/hibernate/test/pagination/LimitWithExpreesionAndFetchJoinTest.java b/hibernate-core/src/test_legacy/org/hibernate/test/pagination/LimitWithExpreesionAndFetchJoinTest.java index c6ef20a1d8..625b84c8a3 100644 --- a/hibernate-core/src/test_legacy/org/hibernate/test/pagination/LimitWithExpreesionAndFetchJoinTest.java +++ b/hibernate-core/src/test_legacy/org/hibernate/test/pagination/LimitWithExpreesionAndFetchJoinTest.java @@ -4,7 +4,7 @@ * 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.test.pagination; +package org.hibernate.orm.test.pagination; import java.util.Collections; import java.util.regex.Matcher; diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Contact.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Contact.java index 98ffc7e2fe..60c4a81438 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Contact.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Contact.java @@ -117,6 +117,7 @@ public class Contact { this.last = last; } + @Column(name = "firstname") public String getFirst() { return first; } @@ -125,6 +126,7 @@ public class Contact { this.first = first; } + @Column(name = "lastname") public String getLast() { return last; } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Ticket.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Ticket.java index e349a09f8e..1b4f6f5ab3 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Ticket.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Ticket.java @@ -6,6 +6,7 @@ */ package org.hibernate.testing.orm.domain.helpdesk; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; @@ -17,6 +18,7 @@ public class Ticket { @Id private Integer id; + @Column(name = "ticket_key") private String key; private String subject; diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java index 33a6af2336..b6e2558f92 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java @@ -6,9 +6,24 @@ */ package org.hibernate.testing.orm.junit; +import org.hibernate.dialect.AbstractHANADialect; +import org.hibernate.dialect.CUBRIDDialect; +import org.hibernate.dialect.CockroachDialect; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.FirebirdDialect; import org.hibernate.dialect.GroupBySummarizationRenderingStrategy; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.HSQLDialect; +import org.hibernate.dialect.MaxDBDialect; +import org.hibernate.dialect.MimerSQLDialect; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.dialect.SpannerDialect; +import org.hibernate.dialect.TimesTenDialect; /** * Container class for different implementation of the {@link DialectFeatureCheck} interface. @@ -233,4 +248,47 @@ abstract public class DialectFeatureChecks { return dialect.supportsTimezoneTypes(); } } + + public static class SupportsOffsetInSubquery implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect instanceof AbstractHANADialect + || dialect instanceof CockroachDialect + || dialect instanceof CUBRIDDialect + || dialect instanceof DB2Dialect + || dialect instanceof FirebirdDialect + || dialect instanceof H2Dialect + || dialect instanceof HSQLDialect + || dialect instanceof MaxDBDialect + || dialect instanceof MimerSQLDialect + || dialect instanceof MySQLDialect + || dialect instanceof OracleDialect + || dialect instanceof PostgreSQLDialect + || dialect instanceof SpannerDialect + || dialect instanceof SQLServerDialect + || dialect instanceof TimesTenDialect + ; + } + } + + public static class SupportsWithTies implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect instanceof AbstractHANADialect + || dialect instanceof CockroachDialect + || dialect instanceof DB2Dialect + || dialect instanceof FirebirdDialect && dialect.getVersion() >= 300 + || dialect instanceof H2Dialect && dialect.getVersion() >= 104198 + || dialect instanceof MySQLDialect && dialect.getVersion() >= 802 + || dialect instanceof OracleDialect + || dialect instanceof PostgreSQLDialect + || dialect instanceof SQLServerDialect + ; + } + } + + public static class SupportsUnion implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsUnionAll(); + } + } + }