HHH-15328 Add support for CTE WITH clause

This commit is contained in:
Christian Beikov 2022-09-09 18:05:35 +02:00
parent 215d411ffa
commit 90a752a0ee
178 changed files with 7186 additions and 1428 deletions

View File

@ -89,6 +89,7 @@ Most of the complexity here arises from the interplay of set operators (`union`,
We'll describe the various clauses of a query later in this chapter, but to summarize, a query might have:
* a `with` clause, specifying <<hql-with-cte,named subqueries>> to be used in the following query,
* a `select` list, specifying a <<hql-select-clause,projection>> (the things to return from the query),
* a `from` clause and joins, <<hql-from-clause,specifying>> the entities involved in the query, and how they're <<hql-join,related>> to each other,
* a `where` clause, specifying a <<hql-where-clause,restriction>>,
@ -1559,6 +1560,16 @@ It may not refer to other roots declared in the same `from` clause.
A subquery may also occur in a <<hql-join-derived, join>>, in which case it may be a correlated subquery.
[[hql-from-cte]]
==== Common table expressions in `from` clause
A _Common table expression (CTE)_ is like a derived root with a name. The big difference is,
that the name can be referred to multiple times. It must declare an identification variable.
The CTE name can be used for a `from` clause root or a `join`, similar to entity names.
Refer to the <<hql-with-cte,with clause>> chapter for details about CTEs.
[[hql-join]]
=== Declaring joined entities
@ -2477,3 +2488,132 @@ This _almost certainly_ isn't the behavior you were hoping for, and in general w
====
In the next chapter we'll see a completely different way to write queries in Hibernate.
[[hql-with-cte]]
==== With clause
The `with` clause allows to specify _common table expressions (CTEs)_ which can be imagined like named subqueries.
Every uncorrelated subquery can be factored to a CTE in the `with` clause. The semantics are equivalent.
The `with` clause offers features beyond naming subqueries though:
* Specify materialization hints
* Recursive querying
===== Materialization hint
The materialization hint `MATERIALIZED` or `NOT MATERIALIZED` can be applied to tell the DBMS whether a CTE should
or shouldn't be materialized. Consult the database manual of the respective database for the exact meaning of the hint.
Usually, one can expect that `MATERIALIZED` will cause the subquery to be executed separately and saved into a temporary table,
whereas `NOT MATERIALIZED` will cause the subquery to be inlined into every use site and considered during optimizations separately.
[[hql-cte-materialized-example]]
====
[source, JAVA, indent=0]
----
include::{sourcedir}/HQLTest.java[tags=hql-cte-materialized-example, indent=0]
----
====
===== Recursive querying
The main use case for the `with` clause is to define a name for a subquery,
such that this subquery can refer to itself, which ultimately enables recursive querying.
Recursive CTEs must follow a very particular shape, which is
* Base query part
* `union` or `union all`
* Recursive query part
[[hql-cte-recursive-example]]
====
[source, JAVA, indent=0]
----
include::{sourcedir}/HQLTest.java[tags=hql-cte-recursive-example, indent=0]
----
====
The base query part represents the initial set of rows. When fetching a tree of data,
the base query part usually is the tree root.
The recursive query part is executed again and again until it produces no new rows.
The result of such a CTE is the base query part result _unioned_ together with all recursive query part executions.
Depending on whether `union all` or `union` (`distinct`) is used, duplicate rows are preserved or not.
Recursive queries additionally can have
* a `search` clause to hint the DBMS whether to use breadth or depth first searching
* a `cycle` clause to hint the DBMS how to determine that a cycle was reached
Defining the `search` clause requires specifying a name for an attribute in the `set` sub-clause,
that will be added to the CTE type and allows ordering results according to the search order.
[[hql-cte-recursive-search-bnf-example]]
====
[source, antlrv4, indent=0]
----
searchClause
: "SEARCH" ("BREADTH"|"DEPTH") "FIRST BY" searchSpecifications "SET" identifier
;
searchSpecifications
: searchSpecification ("," searchSpecification)*
;
searchSpecification
: identifier sortDirection? nullsPrecedence?
;
----
====
A DBMS has two possible orders when executing the recursive query part
* Depth first - handle the *newest* produced rows by the recursive query part first
* Breadth first - handle the *oldest* produced rows by the recursive query part first
[[hql-cte-recursive-search-example]]
====
[source, JAVA, indent=0]
----
include::{sourcedir}/HQLTest.java[tags=hql-cte-recursive-search-example, indent=0]
----
====
Recursive processing can lead to cycles which might lead to queries executing forever.
The `cycle` clause hints the DBMS which CTE attributes to track for the cycle detection.
It requires specifying a name for a cycle mark attribute in the `set` sub-clause,
that will be added to the CTE type and allows detecting if a cycle occurred for a result.
By default, the cycle mark attribute will be set to `true` when a cycle is detected and `false` otherwise.
The values to use can be explicitly specified through the `to` and `default` sub-clauses.
Optionally, it's also possible to specify a cycle path attribute name through the `using` clause
The cycle path attribute can be used to understand the traversal path that lead to a result.
[[hql-cte-recursive-cycle-bnf-example]]
====
[source, antlrv4, indent=0]
----
cycleClause
: "CYCLE" cteAttributes "SET" identifier ("TO" literal "DEFAULT" literal)? ("USING" identifier)?
;
----
====
[[hql-cte-recursive-cycle-example]]
====
[source, JAVA, indent=0]
----
include::{sourcedir}/HQLTest.java[tags=hql-cte-recursive-cycle-example, indent=0]
----
====
[IMPORTANT]
====
Hibernate merely translates recursive CTEs but doesn't attempt to emulate the feature.
Therefore, this feature will only work if the database supports recursive CTEs.
Hibernate does emulate the `search` and `cycle` clauses though if necessary, so you can safely use that.
Note that most modern database versions support recursive CTEs already.
====

View File

@ -2,7 +2,7 @@ selectStatement
: queryExpression
queryExpression
: orderedQuery (setOperator orderedQuery)*
: withClause? orderedQuery (setOperator orderedQuery)*
orderedQuery
: (query | "(" queryExpression ")") queryOrder?
@ -29,4 +29,32 @@ join
joinTarget
: path variable?
| "LATERAL"? "(" subquery ")" variable?
| "LATERAL"? "(" subquery ")" variable?
withClause
: "WITH" cte ("," cte)*
;
cte
: identifier AS ("NOT"? "MATERIALIZED")? "(" queryExpression ")" searchClause? cycleClause?
;
cteAttributes
: identifier ("," identifier)*
;
searchClause
: "SEARCH" ("BREADTH"|"DEPTH") "FIRST BY" searchSpecifications "SET" identifier
;
searchSpecifications
: searchSpecification ("," searchSpecification)*
;
searchSpecification
: identifier sortDirection? nullsPrecedence?
;
cycleClause
: "CYCLE" cteAttributes "SET" identifier ("TO" literal "DEFAULT" literal)? ("USING" identifier)?
;

View File

@ -3133,6 +3133,102 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
});
}
@Test
public void test_hql_cte_materialized_example() {
doInJPA(this::entityManagerFactory, entityManager -> {
//tag::hql-cte-materialized-example[]
List<Tuple> calls = entityManager.createQuery(
"with data as materialized(" +
" select p.person as owner, c.payment is not null as payed " +
" from Call c " +
" join c.phone p " +
" where p.number = :phoneNumber" +
")" +
"select d.owner, d.payed " +
"from data d",
Tuple.class)
.setParameter("phoneNumber", "123-456-7890")
.getResultList();
//end::hql-cte-materialized-example[]
});
}
@Test
@RequiresDialectFeature( DialectChecks.SupportsRecursiveCtes.class )
public void test_hql_cte_recursive_example() {
doInJPA(this::entityManagerFactory, entityManager -> {
//tag::hql-cte-recursive-example[]
List<Tuple> calls = entityManager.createQuery(
"with paymentConnectedPersons as(" +
" select a.owner owner " +
" from Account a where a.id = :startId " +
" union all" +
" select a2.owner owner " +
" from paymentConnectedPersons d " +
" join Account a on a.owner = d.owner " +
" join a.payments p " +
" join Account a2 on a2.owner = p.person" +
")" +
"select d.owner " +
"from paymentConnectedPersons d",
Tuple.class)
.setParameter("startId", 123L)
.getResultList();
//end::hql-cte-recursive-example[]
});
}
@Test
@RequiresDialectFeature( DialectChecks.SupportsRecursiveCtes.class )
public void test_hql_cte_recursive_search_example() {
doInJPA(this::entityManagerFactory, entityManager -> {
//tag::hql-cte-recursive-search-example[]
List<Tuple> calls = entityManager.createQuery(
"with paymentConnectedPersons as(" +
" select a.owner owner " +
" from Account a where a.id = :startId " +
" union all" +
" select a2.owner owner " +
" from paymentConnectedPersons d " +
" join Account a on a.owner = d.owner " +
" join a.payments p " +
" join Account a2 on a2.owner = p.person" +
") search breadth first by owner set orderAttr " +
"select d.owner " +
"from paymentConnectedPersons d",
Tuple.class)
.setParameter("startId", 123L)
.getResultList();
//end::hql-cte-recursive-search-example[]
});
}
@Test
@RequiresDialectFeature( DialectChecks.SupportsRecursiveCtes.class )
public void test_hql_cte_recursive_cycle_example() {
doInJPA(this::entityManagerFactory, entityManager -> {
//tag::hql-cte-recursive-cycle-example[]
List<Tuple> calls = entityManager.createQuery(
"with paymentConnectedPersons as(" +
" select a.owner owner " +
" from Account a where a.id = :startId " +
" union all" +
" select a2.owner owner " +
" from paymentConnectedPersons d " +
" join Account a on a.owner = d.owner " +
" join a.payments p " +
" join Account a2 on a2.owner = p.person" +
") cycle owner set cycleMark " +
"select d.owner, d.cycleMark " +
"from paymentConnectedPersons d",
Tuple.class)
.setParameter("startId", 123L)
.getResultList();
//end::hql-cte-recursive-cycle-example[]
});
}
@Test
@RequiresDialectFeature({
DialectChecks.SupportsSubqueryInOnClause.class,

View File

@ -155,6 +155,14 @@ ext {
'jdbc.url' : 'jdbc:mysql://' + dbHost + '/hibernate_orm_test',
'connection.init_sql' : ''
],
tidb_ci5 : [
'db.dialect' : 'org.hibernate.dialect.TiDBDialect',
'jdbc.driver': 'com.mysql.jdbc.Driver',
'jdbc.user' : 'root',
'jdbc.pass' : '',
'jdbc.url' : 'jdbc:mysql://' + dbHost + ':4000/test',
'connection.init_sql' : ''
],
postgis : [
'db.dialect' : 'org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect',
'jdbc.driver': 'org.postgresql.Driver',

View File

@ -37,16 +37,6 @@ public class CUBRIDSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonEmulateIntersect( lhs, operator, rhs );

View File

@ -80,16 +80,6 @@ public class CacheSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
}
}
@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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonEmulateIntersect( lhs, operator, rhs );

View File

@ -468,6 +468,11 @@ public class CockroachLegacyDialect extends Dialect {
return true;
}
@Override
public boolean supportsRecursiveCTE() {
return getVersion().isSameOrAfter( 20, 1 );
}
@Override
public String getNoColumnsInsertString() {
return "default values";

View File

@ -9,6 +9,7 @@ package org.hibernate.community.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.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
@ -52,6 +53,26 @@ public class CockroachLegacySqlAstTranslator<T extends JdbcOperation> extends Ab
}
}
@Override
protected void renderMaterializationHint(CteMaterialization materialization) {
if ( getDialect().getVersion().isSameOrAfter( 20, 2 ) ) {
if ( materialization == CteMaterialization.NOT_MATERIALIZED ) {
appendSql( "not " );
}
appendSql( "materialized " );
}
}
@Override
protected boolean supportsRowConstructor() {
return true;
}
@Override
protected boolean supportsArrayConstructor() {
return true;
}
@Override
protected String getForShare(int timeoutMillis) {
return " for share";
@ -116,16 +137,6 @@ public class CockroachLegacySqlAstTranslator<T extends JdbcOperation> extends Ab
}
}
@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
}
@Override
protected void renderPartitionItem(Expression expression) {
if ( expression instanceof Literal ) {

View File

@ -534,6 +534,11 @@ public class DB2LegacyDialect extends Dialect {
return false;
}
@Override
public boolean requiresCastForConcatenatingNonStrings() {
return true;
}
@Override
public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfiguration) {
return selectNullString(sqlType);
@ -756,6 +761,12 @@ public class DB2LegacyDialect extends Dialect {
return true;
}
@Override
public boolean supportsRecursiveCTE() {
// Supported at last since 9.7
return getDB2Version().isSameOrAfter( 9, 7 );
}
@Override
public boolean supportsOffsetInSubquery() {
return true;

View File

@ -9,6 +9,7 @@ package org.hibernate.community.dialect;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.ComparisonOperator;
@ -26,6 +27,9 @@ import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
import org.hibernate.sql.ast.tree.insert.InsertStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
@ -45,6 +49,70 @@ public class DB2LegacySqlAstTranslator<T extends JdbcOperation> extends Abstract
super( sessionFactory, statement );
}
@Override
protected boolean needsRecursiveKeywordInWithClause() {
return false;
}
@Override
protected boolean supportsWithClauseInSubquery() {
return false;
}
@Override
protected void renderTableReferenceJoins(TableGroup tableGroup) {
// When we are in a recursive CTE, we can't render joins on DB2...
// See https://modern-sql.com/feature/with-recursive/db2/error-345-state-42836
if ( isInRecursiveQueryPart() ) {
final List<TableReferenceJoin> joins = tableGroup.getTableReferenceJoins();
if ( joins == null || joins.isEmpty() ) {
return;
}
for ( TableReferenceJoin tableJoin : joins ) {
switch ( tableJoin.getJoinType() ) {
case CROSS:
case INNER:
break;
default:
throw new UnsupportedOperationException( "Can't emulate '" + tableJoin.getJoinType().getText() + "join' in a DB2 recursive query part" );
}
appendSql( COMA_SEPARATOR_CHAR );
renderNamedTableReference( tableJoin.getJoinedTableReference(), LockMode.NONE );
if ( tableJoin.getPredicate() != null && !tableJoin.getPredicate().isEmpty() ) {
addAdditionalWherePredicate( tableJoin.getPredicate() );
}
}
}
else {
super.renderTableReferenceJoins( tableGroup );
}
}
@Override
protected void renderTableGroupJoin(TableGroupJoin tableGroupJoin, List<TableGroupJoin> tableGroupJoinCollector) {
if ( isInRecursiveQueryPart() ) {
switch ( tableGroupJoin.getJoinType() ) {
case CROSS:
case INNER:
break;
default:
throw new UnsupportedOperationException( "Can't emulate '" + tableGroupJoin.getJoinType().getText() + "join' in a DB2 recursive query part" );
}
appendSql( COMA_SEPARATOR_CHAR );
renderTableGroup( tableGroupJoin.getJoinedGroup(), null, tableGroupJoinCollector );
if ( tableGroupJoin.getPredicate() != null && !tableGroupJoin.getPredicate().isEmpty() ) {
addAdditionalWherePredicate( tableGroupJoin.getPredicate() );
}
}
else {
super.renderTableGroupJoin( tableGroupJoin, tableGroupJoinCollector );
}
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );

View File

@ -123,6 +123,11 @@ public class DB2iLegacyDialect extends DB2LegacyDialect {
return getVersion().isSameOrAfter( 7, 1 );
}
@Override
public boolean supportsRecursiveCTE() {
return getVersion().isSameOrAfter( 7, 1 );
}
@Override
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
return new StandardSqlAstTranslatorFactory() {

View File

@ -125,6 +125,11 @@ public class DB2zLegacyDialect extends DB2LegacyDialect {
return true;
}
@Override
public boolean supportsRecursiveCTE() {
return getVersion().isSameOrAfter( 11 );
}
@Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
StringBuilder pattern = new StringBuilder();

View File

@ -61,6 +61,10 @@ public class DB2zLegacySqlAstTranslator<T extends JdbcOperation> extends DB2Lega
@Override
protected boolean renderPrimaryTableReference(TableGroup tableGroup, LockMode lockMode) {
if ( shouldInlineCte( tableGroup ) ) {
inlineCteTableGroup( tableGroup, lockMode );
return false;
}
final TableReference tableReference = tableGroup.getPrimaryTableReference();
if ( tableReference instanceof NamedTableReference ) {
return renderNamedTableReference( (NamedTableReference) tableReference, lockMode );

View File

@ -18,6 +18,7 @@ import org.hibernate.dialect.NationalizationSupport;
import org.hibernate.dialect.RowLockStrategy;
import org.hibernate.dialect.function.CaseLeastGreatestEmulation;
import org.hibernate.dialect.function.CastingConcatFunction;
import org.hibernate.dialect.function.ChrLiteralEmulation;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.function.CountFunction;
import org.hibernate.dialect.function.DerbyLpadEmulation;
@ -286,6 +287,16 @@ public class DerbyLegacyDialect extends Dialect {
// AVG by default uses the input type, so we possibly need to cast the argument type, hence a special function
functionFactory.avg_castingNonDoubleArguments( this, SqlAstNodeRenderingMode.DEFAULT );
// Note that Derby does not have chr() / ascii() functions.
// It does have a function named char(), but it's really a
// sort of to_char() function.
// We register an emulation instead, that can at least translate integer literals
queryEngine.getSqmFunctionRegistry().register(
"chr",
new ChrLiteralEmulation( queryEngine.getTypeConfiguration() )
);
functionFactory.concat_pipeOperator();
functionFactory.cot();
functionFactory.chr_char();
@ -604,6 +615,11 @@ public class DerbyLegacyDialect extends Dialect {
return getVersion().isSameOrAfter( 10, 5 );
}
@Override
public boolean requiresCastForConcatenatingNonStrings() {
return true;
}
@Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes( typeContributions, serviceRegistry );

View File

@ -41,6 +41,11 @@ public class DerbyLegacySqlAstTranslator<T extends JdbcOperation> extends Abstra
super( sessionFactory, statement );
}
@Override
protected boolean supportsWithClause() {
return false;
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );
@ -125,24 +130,6 @@ public class DerbyLegacySqlAstTranslator<T extends JdbcOperation> extends Abstra
return " with rs";
}
@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

View File

@ -136,16 +136,6 @@ public class FirebirdSqlAstTranslator<T extends JdbcOperation> extends AbstractS
}
}
@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
}
@Override
protected boolean supportsSimpleQueryGrouping() {
// Firebird is quite strict i.e. it requires `select .. union all select * from (select ...)`

View File

@ -768,6 +768,11 @@ public class H2LegacyDialect extends Dialect {
return getVersion().isSameOrAfter( 1, 4, 200 );
}
@Override
public boolean supportsRecursiveCTE() {
return getVersion().isSameOrAfter( 1, 4, 196 );
}
@Override
public boolean supportsFetchClause(FetchClauseType type) {
return getVersion().isSameOrAfter( 1, 4, 198 );

View File

@ -17,7 +17,9 @@ import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
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.cte.CteTableGroup;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
@ -46,6 +48,47 @@ public class H2LegacySqlAstTranslator<T extends JdbcOperation> extends AbstractS
super( sessionFactory, statement );
}
@Override
public void visitCteContainer(CteContainer cteContainer) {
// H2 has various bugs in different versions that make it impossible to use CTEs with parameters reliably
withParameterRenderingMode(
SqlAstNodeRenderingMode.INLINE_PARAMETERS,
() -> super.visitCteContainer( cteContainer )
);
}
@Override
protected boolean needsCteInlining() {
// CTEs in H2 are just so buggy, that we can't reliably use them
return true;
}
@Override
protected boolean shouldInlineCte(TableGroup tableGroup) {
return tableGroup instanceof CteTableGroup
&& !getCteStatement( tableGroup.getPrimaryTableReference().getTableId() ).isRecursive();
}
@Override
protected boolean supportsWithClauseInSubquery() {
return false;
}
@Override
protected boolean supportsRowConstructor() {
return getDialect().getVersion().isSameOrAfter( 2 );
}
@Override
protected boolean supportsArrayConstructor() {
return getDialect().getVersion().isSameOrAfter( 2 );
}
@Override
protected String getArrayContainsFunction() {
return "array_contains";
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );
@ -84,16 +127,6 @@ public class H2LegacySqlAstTranslator<T extends JdbcOperation> extends AbstractS
}
}
@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
}
@Override
protected void renderSelectTupleComparison(
List<SqlSelection> lhsExpressions,

View File

@ -59,6 +59,39 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
}
}
@Override
protected boolean supportsArrayConstructor() {
return true;
}
@Override
protected boolean supportsWithClauseInSubquery() {
// Doesn't support correlations in the WITH clause
return false;
}
@Override
protected boolean supportsRecursiveClauseArrayAndRowEmulation() {
// Even though HSQL supports the array constructor, it's illegal to use arrays in CTEs
return false;
}
@Override
protected void visitRecursivePath(Expression recursivePath, int sizeEstimate) {
// HSQL determines the type and size of a column in a recursive CTE based on the expression of the non-recursive part
// Due to that, we have to cast the path in the non-recursive path to a varchar of appropriate size to avoid data truncation errors
if ( sizeEstimate == -1 ) {
super.visitRecursivePath( recursivePath, sizeEstimate );
}
else {
appendSql( "cast(" );
recursivePath.accept( this );
appendSql( " as varchar(" );
appendSql( sizeEstimate );
appendSql( "))" );
}
}
// HSQL does not allow CASE expressions where all result arms contain plain parameters.
// At least one result arm must provide some type context for inference,
// so we cast the first result arm if we encounter this condition
@ -190,16 +223,6 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
}
}
@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
}
@Override
protected void renderSelectExpression(Expression expression) {
renderSelectExpressionWithCastedOrInlinedPlainParameters( expression );

View File

@ -86,16 +86,6 @@ public class InformixSqlAstTranslator<T extends JdbcOperation> extends AbstractS
}
}
@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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonEmulateIntersect( lhs, operator, rhs );

View File

@ -93,16 +93,6 @@ public class IngresSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
}
}
@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 void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonEmulateIntersect( lhs, operator, rhs );

View File

@ -50,7 +50,8 @@ public class MariaDBLegacyDialect extends MySQLLegacyDialect {
}
public MariaDBLegacyDialect(DialectResolutionInfo info) {
super(info);
super( createVersion( info ), getCharacterSetBytesPerCharacter( info.getDatabaseMetadata() ) );
registerKeywords( info );
}
@Override
@ -101,6 +102,17 @@ public class MariaDBLegacyDialect extends MySQLLegacyDialect {
return getVersion().isSameOrAfter( 10, 2 );
}
@Override
public boolean supportsLateral() {
// See https://jira.mariadb.org/browse/MDEV-19078
return false;
}
@Override
public boolean supportsRecursiveCTE() {
return getVersion().isSameOrAfter( 10, 2 );
}
@Override
public boolean supportsColumnCheck() {
return getVersion().isSameOrAfter( 10, 2 );

View File

@ -32,11 +32,37 @@ public class MariaDBLegacySqlAstTranslator<T extends JdbcOperation> extends Abst
super( sessionFactory, statement );
}
@Override
protected boolean supportsWithClause() {
return getDialect().getVersion().isSameOrAfter( 10, 2 );
}
@Override
protected boolean supportsWithClauseInSubquery() {
return false;
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );
}
@Override
protected void visitRecursivePath(Expression recursivePath, int sizeEstimate) {
// MariaDB determines the type and size of a column in a recursive CTE based on the expression of the non-recursive part
// Due to that, we have to cast the path in the non-recursive path to a varchar of appropriate size to avoid data truncation errors
if ( sizeEstimate == -1 ) {
super.visitRecursivePath( recursivePath, sizeEstimate );
}
else {
appendSql( "cast(" );
recursivePath.accept( this );
appendSql( " as char(" );
appendSql( sizeEstimate );
appendSql( "))" );
}
}
@Override
public void visitBooleanExpressionPredicate(BooleanExpressionPredicate booleanExpressionPredicate) {
final boolean isNegated = booleanExpressionPredicate.isNegated();
@ -91,16 +117,6 @@ public class MariaDBLegacySqlAstTranslator<T extends JdbcOperation> extends Abst
}
}
@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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonDistinctOperator( lhs, operator, rhs );

View File

@ -38,16 +38,6 @@ public class MaxDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonEmulateIntersect( lhs, operator, rhs );

View File

@ -38,16 +38,6 @@ public class MimerSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractS
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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonEmulateIntersect( lhs, operator, rhs );

View File

@ -123,17 +123,35 @@ public class MySQLLegacyDialect extends Dialect {
}
public MySQLLegacyDialect(DatabaseVersion version) {
this( version, 4 );
}
public MySQLLegacyDialect(DatabaseVersion version, int bytesPerCharacter) {
super( version );
registerKeyword( "key" );
maxVarcharLength = maxVarcharLength( getMySQLVersion(), 4 ); //conservative assumption
maxVarcharLength = maxVarcharLength( getMySQLVersion(), bytesPerCharacter ); //conservative assumption
maxVarbinaryLength = maxVarbinaryLength( getMySQLVersion() );
}
public MySQLLegacyDialect(DialectResolutionInfo info) {
super( info );
int bytesPerCharacter = getCharacterSetBytesPerCharacter( info.getDatabaseMetadata() );
maxVarcharLength = maxVarcharLength( getMySQLVersion(), bytesPerCharacter );
maxVarbinaryLength = maxVarbinaryLength( getMySQLVersion() );
this( createVersion( info ), getCharacterSetBytesPerCharacter( info.getDatabaseMetadata() ) );
registerKeywords( info );
}
protected static DatabaseVersion createVersion(DialectResolutionInfo info) {
final String versionString = info.getDatabaseVersion();
final String[] components = versionString.split( "\\." );
if ( components.length >= 3 ) {
try {
final int majorVersion = Integer.parseInt( components[0] );
final int minorVersion = Integer.parseInt( components[1] );
final int patchLevel = Integer.parseInt( components[2] );
return DatabaseVersion.make( majorVersion, minorVersion, patchLevel );
}
catch (NumberFormatException ex) {
// Ignore
}
}
return info.makeCopy();
}
@Override
@ -518,9 +536,9 @@ public class MySQLLegacyDialect extends Dialect {
// MySQL timestamp type defaults to precision 0 (seconds) but
// we want the standard default precision of 6 (microseconds)
functionFactory.sysdateExplicitMicros();
if ( getMySQLVersion().isSameOrAfter( 8, 2 ) ) {
if ( getMySQLVersion().isSameOrAfter( 8, 0, 2 ) ) {
functionFactory.windowFunctions();
if ( getMySQLVersion().isSameOrAfter( 8, 11 ) ) {
if ( getMySQLVersion().isSameOrAfter( 8, 0, 11 ) ) {
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
}
}
@ -1211,12 +1229,17 @@ public class MySQLLegacyDialect extends Dialect {
@Override
public boolean supportsWindowFunctions() {
return getMySQLVersion().isSameOrAfter( 8, 2 );
return getMySQLVersion().isSameOrAfter( 8, 0, 2 );
}
@Override
public boolean supportsLateral() {
return getMySQLVersion().isSameOrAfter( 8, 14 );
return getMySQLVersion().isSameOrAfter( 8, 0, 14 );
}
@Override
public boolean supportsRecursiveCTE() {
return getMySQLVersion().isSameOrAfter( 8, 0, 14 );
}
@Override
@ -1240,6 +1263,12 @@ public class MySQLLegacyDialect extends Dialect {
return supportsAliasLocks() ? RowLockStrategy.TABLE : RowLockStrategy.NONE;
}
@Override
protected void registerDefaultKeywords() {
super.registerDefaultKeywords();
registerKeyword( "key" );
}
boolean supportsForShare() {
return getMySQLVersion().isSameOrAfter( 8 );
}

View File

@ -10,7 +10,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.ComparisonOperator;
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.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
@ -38,6 +37,22 @@ public class MySQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstra
expression.accept( this );
}
@Override
protected void visitRecursivePath(Expression recursivePath, int sizeEstimate) {
// MySQL determines the type and size of a column in a recursive CTE based on the expression of the non-recursive part
// Due to that, we have to cast the path in the non-recursive path to a varchar of appropriate size to avoid data truncation errors
if ( sizeEstimate == -1 ) {
super.visitRecursivePath( recursivePath, sizeEstimate );
}
else {
appendSql( "cast(" );
recursivePath.accept( this );
appendSql( " as char(" );
appendSql( sizeEstimate );
appendSql( "))" );
}
}
@Override
public void visitBooleanExpressionPredicate(BooleanExpressionPredicate booleanExpressionPredicate) {
final boolean isNegated = booleanExpressionPredicate.isNegated();
@ -103,16 +118,6 @@ public class MySQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstra
}
}
@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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonDistinctOperator( lhs, operator, rhs );
@ -160,6 +165,11 @@ public class MySQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstra
return true;
}
@Override
protected boolean supportsWithClause() {
return getDialect().getVersion().isSameOrAfter( 8 );
}
@Override
protected String getFromDual() {
return " from dual";

View File

@ -1122,6 +1122,11 @@ public class OracleLegacyDialect extends Dialect {
return true;
}
@Override
public boolean supportsRecursiveCTE() {
return getVersion().isSameOrAfter( 11, 2 );
}
@Override
public boolean supportsLateral() {
return getVersion().isSameOrAfter( 12, 1 );

View File

@ -21,6 +21,7 @@ import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.Expression;
@ -54,6 +55,37 @@ public class OracleLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
super( sessionFactory, statement );
}
@Override
protected boolean needsRecursiveKeywordInWithClause() {
return false;
}
@Override
protected boolean supportsWithClauseInSubquery() {
// Oracle has some limitations, see ORA-32034, so we just report false here for simplicity
return false;
}
@Override
protected boolean supportsRecursiveSearchClause() {
return true;
}
@Override
protected boolean supportsRecursiveCycleClause() {
return true;
}
@Override
public void visitSqlSelection(SqlSelection sqlSelection) {
if ( getCurrentCteStatement() != null ) {
if ( getCurrentCteStatement().getMaterialization() == CteMaterialization.MATERIALIZED ) {
appendSql( "/*+ materialize */ " );
}
}
super.visitSqlSelection( sqlSelection );
}
@Override
protected LockStrategy determineLockingStrategy(
QuerySpec querySpec,
@ -169,31 +201,20 @@ public class OracleLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
true, // we need select aliases to avoid ORA-00918: column ambiguously defined
() -> {
final QueryPart currentQueryPart = getQueryPartStack().getCurrent();
final boolean needsParenthesis;
final boolean needsWrapper;
if ( currentQueryPart instanceof QueryGroup ) {
needsParenthesis = false;
// visitQuerySpec will add the select wrapper
needsWrapper = !currentQueryPart.hasOffsetOrFetchClause();
}
else {
needsParenthesis = !querySpec.isRoot();
needsWrapper = true;
}
if ( needsWrapper ) {
if ( needsParenthesis ) {
appendSql( '(' );
}
appendSql( "select * from " );
if ( !needsParenthesis ) {
appendSql( '(' );
}
appendSql( "select * from (" );
}
super.visitQuerySpec( querySpec );
if ( needsWrapper ) {
if ( !needsParenthesis ) {
appendSql( ')' );
}
appendSql( ')' );
}
appendSql( " where rownum<=" );
final Stack<Clause> clauseStack = getClauseStack();
@ -209,12 +230,6 @@ public class OracleLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
finally {
clauseStack.pop();
}
if ( needsWrapper ) {
if ( needsParenthesis ) {
appendSql( ')' );
}
}
}
);
}

View File

@ -60,6 +60,16 @@ public class PostgreSQLLegacySqlAstTranslator<T extends JdbcOperation> extends A
}
}
@Override
protected boolean supportsRowConstructor() {
return true;
}
@Override
protected boolean supportsArrayConstructor() {
return true;
}
@Override
public boolean supportsFilterClause() {
return getDialect().getVersion().isSameOrAfter( 9, 4 );
@ -117,13 +127,27 @@ public class PostgreSQLLegacySqlAstTranslator<T extends JdbcOperation> extends A
}
@Override
protected void renderSearchClause(CteStatement cte) {
// PostgreSQL does not support this, but it's just a hint anyway
protected boolean supportsRecursiveSearchClause() {
return getDialect().getVersion().isSameOrAfter( 14 );
}
@Override
protected void renderCycleClause(CteStatement cte) {
// PostgreSQL does not support this, but it can be emulated
protected boolean supportsRecursiveCycleClause() {
return getDialect().getVersion().isSameOrAfter( 14 );
}
@Override
protected boolean supportsRecursiveCycleUsingClause() {
return getDialect().getVersion().isSameOrAfter( 14 );
}
@Override
protected void renderStandardCycleClause(CteStatement cte) {
super.renderStandardCycleClause( cte );
if ( cte.getCycleMarkColumn() != null && cte.getCyclePathColumn() == null && supportsRecursiveCycleUsingClause() ) {
appendSql( " using " );
appendSql( determineCyclePathColumnName( cte ) );
}
}
@Override

View File

@ -77,16 +77,6 @@ public class RDMSOS2200SqlAstTranslator<T extends JdbcOperation> extends Abstrac
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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonEmulateIntersect( lhs, operator, rhs );

View File

@ -614,6 +614,11 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
return getVersion().isSameOrAfter( 9 );
}
@Override
public boolean supportsRecursiveCTE() {
return getVersion().isSameOrAfter( 9 );
}
@Override
public boolean supportsFetchClause(FetchClauseType type) {
return getVersion().isSameOrAfter( 11 );

View File

@ -52,6 +52,16 @@ public class SQLServerLegacySqlAstTranslator<T extends JdbcOperation> extends Ab
super( sessionFactory, statement );
}
@Override
protected boolean needsRecursiveKeywordInWithClause() {
return false;
}
@Override
protected boolean supportsWithClauseInSubquery() {
return false;
}
@Override
protected void renderTableGroupJoin(TableGroupJoin tableGroupJoin, List<TableGroupJoin> tableGroupJoinCollector) {
appendSql( WHITESPACE );
@ -87,6 +97,10 @@ public class SQLServerLegacySqlAstTranslator<T extends JdbcOperation> extends Ab
}
protected boolean renderPrimaryTableReference(TableGroup tableGroup, LockMode lockMode) {
if ( shouldInlineCte( tableGroup ) ) {
inlineCteTableGroup( tableGroup, lockMode );
return false;
}
final TableReference tableReference = tableGroup.getPrimaryTableReference();
if ( tableReference instanceof NamedTableReference ) {
return renderNamedTableReference( (NamedTableReference) tableReference, lockMode );
@ -377,16 +391,6 @@ public class SQLServerLegacySqlAstTranslator<T extends JdbcOperation> extends Ab
}
}
@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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonEmulateIntersect( lhs, operator, rhs );

View File

@ -105,16 +105,6 @@ public class SQLiteSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
}
}
@Override
protected void renderSearchClause(CteStatement cte) {
// SQLite does not support this, but it's just a hint anyway
}
@Override
protected void renderCycleClause(CteStatement cte) {
// SQLite does not support this, but it can be emulated
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
if ( rhs instanceof Any ) {

View File

@ -49,6 +49,11 @@ public class SybaseASELegacySqlAstTranslator<T extends JdbcOperation> extends Ab
super( sessionFactory, statement );
}
@Override
protected boolean supportsWithClause() {
return false;
}
// Sybase ASE does not allow CASE expressions where all result arms contain plain parameters.
// At least one result arm must provide some type context for inference,
// so we cast the first result arm if we encounter this condition
@ -152,16 +157,6 @@ public class SybaseASELegacySqlAstTranslator<T extends JdbcOperation> extends Ab
super.renderForUpdateClause( querySpec, forUpdateClause );
}
@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() ) {

View File

@ -157,16 +157,6 @@ public class SybaseAnywhereSqlAstTranslator<T extends JdbcOperation> extends Abs
}
}
@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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonEmulateIntersect( lhs, operator, rhs );

View File

@ -39,6 +39,11 @@ public class SybaseLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
super( sessionFactory, statement );
}
@Override
protected boolean supportsWithClause() {
return false;
}
// Sybase does not allow CASE expressions where all result arms contain plain parameters.
// At least one result arm must provide some type context for inference,
// so we cast the first result arm if we encounter this condition
@ -105,16 +110,6 @@ public class SybaseLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
// Sybase does not support the FOR UPDATE clause
}
@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 );

View File

@ -101,16 +101,6 @@ public class TeradataSqlAstTranslator<T extends JdbcOperation> extends AbstractS
}
}
@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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonEmulateIntersect( lhs, operator, rhs );

View File

@ -89,16 +89,6 @@ public class TimesTenSqlAstTranslator<T extends JdbcOperation> extends AbstractS
}
}
@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() );

View File

@ -149,6 +149,7 @@ ASC : [aA] [sS] [cC];
AVG : [aA] [vV] [gG];
BETWEEN : [bB] [eE] [tT] [wW] [eE] [eE] [nN];
BOTH : [bB] [oO] [tT] [hH];
BREADTH : [bB] [rR] [eE] [aA] [dD] [tT] [hH];
BY : [bB] [yY];
CASE : [cC] [aA] [sS] [eE];
CAST : [cC] [aA] [sS] [tT];
@ -161,10 +162,13 @@ CURRENT_DATE : [cC] [uU] [rR] [rR] [eE] [nN] [tT] '_' [dD] [aA] [tT] [eE];
CURRENT_INSTANT : [cC] [uU] [rR] [rR] [eE] [nN] [tT] '_' [iI] [nN] [sS] [tT] [aA] [nN] [tT]; //deprecated legacy
CURRENT_TIME : [cC] [uU] [rR] [rR] [eE] [nN] [tT] '_' [tT] [iI] [mM] [eE];
CURRENT_TIMESTAMP : [cC] [uU] [rR] [rR] [eE] [nN] [tT] '_' [tT] [iI] [mM] [eE] [sS] [tT] [aA] [mM] [pP];
CYCLE : [cC] [yY] [cC] [lL] [eE];
DATE : [dD] [aA] [tT] [eE];
DATETIME : [dD] [aA] [tT] [eE] [tT] [iI] [mM] [eE];
DAY : [dD] [aA] [yY];
DEFAULT : [dD] [eE] [fF] [aA] [uU] [lL] [tT];
DELETE : [dD] [eE] [lL] [eE] [tT] [eE];
DEPTH : [dD] [eE] [pP] [tT] [hH];
DESC : [dD] [eE] [sS] [cC];
DISTINCT : [dD] [iI] [sS] [tT] [iI] [nN] [cC] [tT];
ELEMENT : [eE] [lL] [eE] [mM] [eE] [nN] [tT];
@ -219,6 +223,7 @@ LOCAL_DATE : [lL] [oO] [cC] [aA] [lL] '_' [dD] [aA] [tT] [eE];
LOCAL_DATETIME : [lL] [oO] [cC] [aA] [lL] '_' [dD] [aA] [tT] [eE] [tT] [iI] [mM] [eE];
LOCAL_TIME : [lL] [oO] [cC] [aA] [lL] '_' [tT] [iI] [mM] [eE];
MAP : [mM] [aA] [pP];
MATERIALIZED : [mM] [aA] [tT] [eE] [rR] [iI] [aA] [lL] [iI] [zZ] [eE] [dD];
MAX : [mM] [aA] [xX];
MAXELEMENT : [mM] [aA] [xX] [eE] [lL] [eE] [mM] [eE] [nN] [tT];
MAXINDEX : [mM] [aA] [xX] [iI] [nN] [dD] [eE] [xX];
@ -262,6 +267,7 @@ RIGHT : [rR] [iI] [gG] [hH] [tT];
ROLLUP : [rR] [oO] [lL] [lL] [uU] [pP];
ROW : [rR] [oO] [wW];
ROWS : [rR] [oO] [wW] [sS];
SEARCH : [sS] [eE] [aA] [rR] [cC] [hH];
SECOND : [sS] [eE] [cC] [oO] [nN] [dD];
SELECT : [sS] [eE] [lL] [eE] [cC] [tT];
SET : [sS] [eE] [tT];
@ -275,6 +281,7 @@ 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];
TIMEZONE_MINUTE : [tT] [iI] [mM] [eE] [zZ] [oO] [nN] [eE] '_' [mM] [iI] [nN] [uU] [tT] [eE];
TO : [tT] [oO];
TRAILING : [tT] [rR] [aA] [iI] [lL] [iI] [nN] [gG];
TREAT : [tT] [rR] [eE] [aA] [tT];
TRIM : [tT] [rR] [iI] [mM];
@ -283,6 +290,7 @@ TYPE : [tT] [yY] [pP] [eE];
UNBOUNDED : [uU] [nN] [bB] [oO] [uU] [nN] [dD] [eE] [dD];
UNION : [uU] [nN] [iI] [oO] [nN];
UPDATE : [uU] [pP] [dD] [aA] [tT] [eE];
USING : [uU] [sS] [iI] [nN] [gG];
VALUE : [vV] [aA] [lL] [uU] [eE];
VALUES : [vV] [aA] [lL] [uU] [eE] [sS];
WEEK : [wW] [eE] [eE] [kK];

View File

@ -110,12 +110,40 @@ values
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// QUERY SPEC - general structure of root sqm or sub sqm
withClause
: WITH cte (COMMA cte)*
;
cte
: identifier AS (NOT? MATERIALIZED)? LEFT_PAREN queryExpression RIGHT_PAREN searchClause? cycleClause?
;
cteAttributes
: identifier (COMMA identifier)*
;
searchClause
: SEARCH (BREADTH|DEPTH) FIRST BY searchSpecifications SET identifier
;
searchSpecifications
: searchSpecification (COMMA searchSpecification)*
;
searchSpecification
: identifier sortDirection? nullsPrecedence?
;
cycleClause
: CYCLE cteAttributes SET identifier (TO literal DEFAULT literal)? (USING identifier)?
;
/**
* A toplevel query of subquery, which may be a union or intersection of subqueries
*/
queryExpression
: orderedQuery # SimpleQueryGroup
| orderedQuery (setOperator orderedQuery)+ # SetQueryGroup
: withClause? orderedQuery # SimpleQueryGroup
| withClause? orderedQuery (setOperator orderedQuery)+ # SetQueryGroup
;
/**
@ -1482,6 +1510,7 @@ rollup
| AVG
| BETWEEN
| BOTH
| BREADTH
| BY
| CASE
| CAST
@ -1494,10 +1523,13 @@ rollup
| CURRENT_INSTANT
| CURRENT_TIME
| CURRENT_TIMESTAMP
| CYCLE
| DATE
| DATETIME
| DAY
| DEFAULT
| DELETE
| DEPTH
| DESC
| DISTINCT
| ELEMENT
@ -1552,6 +1584,7 @@ rollup
| LOCAL_DATETIME
| LOCAL_TIME
| MAP
| MATERIALIZED
| MAX
| MAXELEMENT
| MAXINDEX
@ -1596,6 +1629,7 @@ rollup
| ROLLUP
| ROW
| ROWS
| SEARCH
| SECOND
| SELECT
| SET
@ -1609,6 +1643,7 @@ rollup
| TIMESTAMP
| TIMEZONE_HOUR
| TIMEZONE_MINUTE
| TO
| TRAILING
| TREAT
| TRIM
@ -1617,6 +1652,7 @@ rollup
| UNBOUNDED
| UNION
| UPDATE
| USING
| VALUE
| VALUES
| VERSION

View File

@ -29,6 +29,7 @@ import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.sqm.tree.SqmDmlStatement;
import org.hibernate.query.sqm.tree.SqmQuery;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.cte.SqmCteStatement;
import org.hibernate.sql.ast.tree.insert.InsertStatement;
@ -151,8 +152,8 @@ public class BulkOperationCleanupAction implements Executable, Serializable {
entityPersisters.add( metamodel.getEntityDescriptor( statement.getTarget().getEntityName() ) );
}
for ( SqmCteStatement<?> cteStatement : statement.getCteStatements() ) {
final SqmStatement<?> cteDefinition = cteStatement.getCteDefinition();
if ( cteDefinition instanceof SqmDmlStatement<?> && !( cteDefinition instanceof InsertStatement ) ) {
final SqmQuery<?> cteDefinition = cteStatement.getCteDefinition();
if ( cteDefinition instanceof SqmDmlStatement<?> ) {
entityPersisters.add(
metamodel.getEntityDescriptor( ( (SqmDmlStatement<?>) cteDefinition ).getTarget().getEntityName() )
);

View File

@ -290,6 +290,11 @@ public abstract class AbstractTransactSQLDialect extends Dialect {
return NullOrdering.SMALLEST;
}
@Override
public boolean requiresCastForConcatenatingNonStrings() {
return true;
}
@Override
public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy(
EntityMappingType entityDescriptor,

View File

@ -474,6 +474,11 @@ public class CockroachDialect extends Dialect {
return true;
}
@Override
public boolean supportsRecursiveCTE() {
return true;
}
@Override
public String getNoColumnsInsertString() {
return "default values";

View File

@ -9,6 +9,7 @@ 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.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
@ -52,6 +53,24 @@ public class CockroachSqlAstTranslator<T extends JdbcOperation> extends Abstract
}
}
@Override
protected void renderMaterializationHint(CteMaterialization materialization) {
if ( materialization == CteMaterialization.NOT_MATERIALIZED ) {
appendSql( "not " );
}
appendSql( "materialized " );
}
@Override
protected boolean supportsRowConstructor() {
return true;
}
@Override
protected boolean supportsArrayConstructor() {
return true;
}
@Override
protected String getForShare(int timeoutMillis) {
return " for share";
@ -90,16 +109,6 @@ public class CockroachSqlAstTranslator<T extends JdbcOperation> extends Abstract
}
}
@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
}
@Override
protected void renderPartitionItem(Expression expression) {
if ( expression instanceof Literal ) {

View File

@ -507,6 +507,11 @@ public class DB2Dialect extends Dialect {
return false;
}
@Override
public boolean requiresCastForConcatenatingNonStrings() {
return true;
}
@Override
public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfiguration) {
return selectNullString(sqlType);
@ -729,6 +734,12 @@ public class DB2Dialect extends Dialect {
return true;
}
@Override
public boolean supportsRecursiveCTE() {
// Supported at last since 9.7
return true;
}
@Override
public boolean supportsOffsetInSubquery() {
return true;

View File

@ -9,6 +9,7 @@ package org.hibernate.dialect;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.FetchClauseType;
@ -25,6 +26,9 @@ import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
import org.hibernate.sql.ast.tree.insert.InsertStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
@ -44,6 +48,70 @@ public class DB2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAst
super( sessionFactory, statement );
}
@Override
protected boolean needsRecursiveKeywordInWithClause() {
return false;
}
@Override
protected boolean supportsWithClauseInSubquery() {
return false;
}
@Override
protected void renderTableReferenceJoins(TableGroup tableGroup) {
// When we are in a recursive CTE, we can't render joins on DB2...
// See https://modern-sql.com/feature/with-recursive/db2/error-345-state-42836
if ( isInRecursiveQueryPart() ) {
final List<TableReferenceJoin> joins = tableGroup.getTableReferenceJoins();
if ( joins == null || joins.isEmpty() ) {
return;
}
for ( TableReferenceJoin tableJoin : joins ) {
switch ( tableJoin.getJoinType() ) {
case CROSS:
case INNER:
break;
default:
throw new UnsupportedOperationException( "Can't emulate '" + tableJoin.getJoinType().getText() + "join' in a DB2 recursive query part" );
}
appendSql( COMA_SEPARATOR_CHAR );
renderNamedTableReference( tableJoin.getJoinedTableReference(), LockMode.NONE );
if ( tableJoin.getPredicate() != null && !tableJoin.getPredicate().isEmpty() ) {
addAdditionalWherePredicate( tableJoin.getPredicate() );
}
}
}
else {
super.renderTableReferenceJoins( tableGroup );
}
}
@Override
protected void renderTableGroupJoin(TableGroupJoin tableGroupJoin, List<TableGroupJoin> tableGroupJoinCollector) {
if ( isInRecursiveQueryPart() ) {
switch ( tableGroupJoin.getJoinType() ) {
case CROSS:
case INNER:
break;
default:
throw new UnsupportedOperationException( "Can't emulate '" + tableGroupJoin.getJoinType().getText() + "join' in a DB2 recursive query part" );
}
appendSql( COMA_SEPARATOR_CHAR );
renderTableGroup( tableGroupJoin.getJoinedGroup(), null, tableGroupJoinCollector );
if ( tableGroupJoin.getPredicate() != null && !tableGroupJoin.getPredicate().isEmpty() ) {
addAdditionalWherePredicate( tableGroupJoin.getPredicate() );
}
}
else {
super.renderTableGroupJoin( tableGroupJoin, tableGroupJoinCollector );
}
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );

View File

@ -128,6 +128,11 @@ public class DB2iDialect extends DB2Dialect {
return true;
}
@Override
public boolean supportsRecursiveCTE() {
return true;
}
@Override
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
return new StandardSqlAstTranslatorFactory() {

View File

@ -123,6 +123,11 @@ public class DB2zDialect extends DB2Dialect {
return true;
}
@Override
public boolean supportsRecursiveCTE() {
return true;
}
@Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
StringBuilder pattern = new StringBuilder();

View File

@ -54,6 +54,10 @@ public class DB2zSqlAstTranslator<T extends JdbcOperation> extends DB2SqlAstTran
@Override
protected boolean renderPrimaryTableReference(TableGroup tableGroup, LockMode lockMode) {
if ( shouldInlineCte( tableGroup ) ) {
inlineCteTableGroup( tableGroup, lockMode );
return false;
}
final TableReference tableReference = tableGroup.getPrimaryTableReference();
if ( tableReference instanceof NamedTableReference ) {
return renderNamedTableReference( (NamedTableReference) tableReference, lockMode );

View File

@ -8,6 +8,7 @@ package org.hibernate.dialect;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.function.CastingConcatFunction;
import org.hibernate.dialect.function.ChrLiteralEmulation;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.function.CountFunction;
import org.hibernate.dialect.function.DerbyLpadEmulation;
@ -256,6 +257,12 @@ public class DerbyDialect extends Dialect {
// It does have a function named char(), but it's really a
// sort of to_char() function.
// We register an emulation instead, that can at least translate integer literals
queryEngine.getSqmFunctionRegistry().register(
"chr",
new ChrLiteralEmulation( queryEngine.getTypeConfiguration() )
);
functionFactory.concat_pipeOperator();
functionFactory.cot();
functionFactory.degrees();
@ -562,6 +569,11 @@ public class DerbyDialect extends Dialect {
return true;
}
@Override
public boolean requiresCastForConcatenatingNonStrings() {
return true;
}
@Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes( typeContributions, serviceRegistry );

View File

@ -41,6 +41,11 @@ public class DerbySqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
super( sessionFactory, statement );
}
@Override
protected boolean supportsWithClause() {
return false;
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );
@ -125,24 +130,6 @@ public class DerbySqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
return " with rs";
}
@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

View File

@ -2991,6 +2991,16 @@ public abstract class Dialect implements ConversionContext {
return true;
}
/**
* Does this dialect/database require casting of non-string arguments in a concat function?
*
* @return {@code true} if casting of non-string arguments in concat is required
* @since 6.2
*/
public boolean requiresCastForConcatenatingNonStrings() {
return false;
}
/**
* Does this dialect require that integer divisions be wrapped in {@code cast()}
* calls to tell the db parser the expected type.
@ -3574,6 +3584,16 @@ public abstract class Dialect implements ConversionContext {
return false;
}
/**
* Does this dialect/database support recursive CTEs (Common Table Expressions)?
*
* @return {@code true} if recursive CTEs are supported
* @since 6.2
*/
public boolean supportsRecursiveCTE() {
return false;
}
/**
* Does this dialect support {@code values} lists of form
* {@code VALUES (1), (2), (3)}?

View File

@ -735,6 +735,11 @@ public class H2Dialect extends Dialect {
return getVersion().isSameOrAfter( 1, 4, 200 );
}
@Override
public boolean supportsRecursiveCTE() {
return getVersion().isSameOrAfter( 1, 4, 196 );
}
@Override
public boolean supportsFetchClause(FetchClauseType type) {
return getVersion().isSameOrAfter( 1, 4, 198 );

View File

@ -17,7 +17,9 @@ import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
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.cte.CteTableGroup;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
@ -46,6 +48,47 @@ public class H2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstT
super( sessionFactory, statement );
}
@Override
public void visitCteContainer(CteContainer cteContainer) {
// H2 has various bugs in different versions that make it impossible to use CTEs with parameters reliably
withParameterRenderingMode(
SqlAstNodeRenderingMode.INLINE_PARAMETERS,
() -> super.visitCteContainer( cteContainer )
);
}
@Override
protected boolean needsCteInlining() {
// CTEs in H2 are just so buggy, that we can't reliably use them
return true;
}
@Override
protected boolean shouldInlineCte(TableGroup tableGroup) {
return tableGroup instanceof CteTableGroup
&& !getCteStatement( tableGroup.getPrimaryTableReference().getTableId() ).isRecursive();
}
@Override
protected boolean supportsWithClauseInSubquery() {
return false;
}
@Override
protected boolean supportsRowConstructor() {
return getDialect().getVersion().isSameOrAfter( 2 );
}
@Override
protected boolean supportsArrayConstructor() {
return getDialect().getVersion().isSameOrAfter( 2 );
}
@Override
protected String getArrayContainsFunction() {
return "array_contains";
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );
@ -84,16 +127,6 @@ public class H2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstT
}
}
@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
}
@Override
protected void renderSelectTupleComparison(
List<SqlSelection> lhsExpressions,

View File

@ -44,6 +44,18 @@ public class HANASqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
&& !isRowsOnlyFetchClauseType( queryPart );
}
@Override
protected boolean supportsWithClauseInSubquery() {
// HANA doesn't seem to support correlation, so we just report false here for simplicity
return false;
}
@Override
protected boolean isCorrelated(CteStatement cteStatement) {
// Report false here, because apparently HANA does not need the "lateral" keyword to correlate a from clause subquery in a subquery
return false;
}
@Override
public void visitQueryGroup(QueryGroup queryGroup) {
if ( shouldEmulateFetchClause( queryGroup ) ) {
@ -95,16 +107,6 @@ public class HANASqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
}
}
@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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonEmulateIntersect( lhs, operator, rhs );

View File

@ -632,6 +632,11 @@ public class HSQLDialect extends Dialect {
return true;
}
@Override
public boolean supportsRecursiveCTE() {
return getVersion().isSameOrAfter( 2 );
}
@Override
public boolean requiresFloatCastingOfIntegerDivision() {
return true;

View File

@ -17,7 +17,6 @@ import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
@ -58,6 +57,39 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
}
}
@Override
protected boolean supportsArrayConstructor() {
return true;
}
@Override
protected boolean supportsWithClauseInSubquery() {
// Doesn't support correlations in the WITH clause
return false;
}
@Override
protected boolean supportsRecursiveClauseArrayAndRowEmulation() {
// Even though HSQL supports the array constructor, it's illegal to use arrays in CTEs
return false;
}
@Override
protected void visitRecursivePath(Expression recursivePath, int sizeEstimate) {
// HSQL determines the type and size of a column in a recursive CTE based on the expression of the non-recursive part
// Due to that, we have to cast the path in the non-recursive path to a varchar of appropriate size to avoid data truncation errors
if ( sizeEstimate == -1 ) {
super.visitRecursivePath( recursivePath, sizeEstimate );
}
else {
appendSql( "cast(" );
recursivePath.accept( this );
appendSql( " as varchar(" );
appendSql( sizeEstimate );
appendSql( "))" );
}
}
// HSQL does not allow CASE expressions where all result arms contain plain parameters.
// At least one result arm must provide some type context for inference,
// so we cast the first result arm if we encounter this condition
@ -170,16 +202,6 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
}
}
@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
}
@Override
protected void renderSelectExpression(Expression expression) {
renderSelectExpressionWithCastedOrInlinedPlainParameters( expression );

View File

@ -56,7 +56,8 @@ public class MariaDBDialect extends MySQLDialect {
}
public MariaDBDialect(DialectResolutionInfo info) {
super(info);
super( createVersion( info ), getCharacterSetBytesPerCharacter( info.getDatabaseMetadata() ) );
registerKeywords( info );
}
@Override
@ -149,6 +150,17 @@ public class MariaDBDialect extends MySQLDialect {
return true;
}
@Override
public boolean supportsLateral() {
// See https://jira.mariadb.org/browse/MDEV-19078
return false;
}
@Override
public boolean supportsRecursiveCTE() {
return true;
}
@Override
public boolean supportsColumnCheck() {
return true;

View File

@ -32,11 +32,32 @@ public class MariaDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSq
super( sessionFactory, statement );
}
@Override
protected boolean supportsWithClauseInSubquery() {
return false;
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );
}
@Override
protected void visitRecursivePath(Expression recursivePath, int sizeEstimate) {
// MariaDB determines the type and size of a column in a recursive CTE based on the expression of the non-recursive part
// Due to that, we have to cast the path in the non-recursive path to a varchar of appropriate size to avoid data truncation errors
if ( sizeEstimate == -1 ) {
super.visitRecursivePath( recursivePath, sizeEstimate );
}
else {
appendSql( "cast(" );
recursivePath.accept( this );
appendSql( " as char(" );
appendSql( sizeEstimate );
appendSql( "))" );
}
}
@Override
public void visitBooleanExpressionPredicate(BooleanExpressionPredicate booleanExpressionPredicate) {
final boolean isNegated = booleanExpressionPredicate.isNegated();
@ -91,16 +112,6 @@ public class MariaDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSq
}
}
@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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonDistinctOperator( lhs, operator, rhs );

View File

@ -119,17 +119,37 @@ public class MySQLDialect extends Dialect {
}
public MySQLDialect(DatabaseVersion version) {
this( version, 4 );
}
public MySQLDialect(DatabaseVersion version, int bytesPerCharacter) {
super( version );
registerKeyword( "key" );
maxVarcharLength = maxVarcharLength( getMySQLVersion(), 4 ); //conservative assumption
maxVarcharLength = maxVarcharLength( getMySQLVersion(), bytesPerCharacter ); //conservative assumption
maxVarbinaryLength = maxVarbinaryLength( getMySQLVersion() );
}
public MySQLDialect(DialectResolutionInfo info) {
super( info );
int bytesPerCharacter = getCharacterSetBytesPerCharacter( info.getDatabaseMetadata() );
maxVarcharLength = maxVarcharLength( getMySQLVersion(), bytesPerCharacter );
maxVarbinaryLength = maxVarbinaryLength( getMySQLVersion() );
this( createVersion( info ), getCharacterSetBytesPerCharacter( info.getDatabaseMetadata() ) );
registerKeywords( info );
}
protected static DatabaseVersion createVersion(DialectResolutionInfo info) {
final String versionString = info.getDatabaseVersion();
if ( versionString != null ) {
final String[] components = versionString.split( "\\." );
if ( components.length >= 3 ) {
try {
final int majorVersion = Integer.parseInt( components[0] );
final int minorVersion = Integer.parseInt( components[1] );
final int patchLevel = Integer.parseInt( components[2] );
return DatabaseVersion.make( majorVersion, minorVersion, patchLevel );
}
catch (NumberFormatException ex) {
// Ignore
}
}
}
return info.makeCopy();
}
@Override
@ -532,9 +552,9 @@ public class MySQLDialect extends Dialect {
// MySQL timestamp type defaults to precision 0 (seconds) but
// we want the standard default precision of 6 (microseconds)
functionFactory.sysdateExplicitMicros();
if ( getMySQLVersion().isSameOrAfter( 8, 2 ) ) {
if ( getMySQLVersion().isSameOrAfter( 8, 0, 2 ) ) {
functionFactory.windowFunctions();
if ( getMySQLVersion().isSameOrAfter( 8, 11 ) ) {
if ( getMySQLVersion().isSameOrAfter( 8, 0, 11 ) ) {
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
}
}
@ -1219,12 +1239,17 @@ public class MySQLDialect extends Dialect {
@Override
public boolean supportsWindowFunctions() {
return getMySQLVersion().isSameOrAfter( 8, 2 );
return getMySQLVersion().isSameOrAfter( 8, 0, 2 );
}
@Override
public boolean supportsLateral() {
return getMySQLVersion().isSameOrAfter( 8, 14 );
return getMySQLVersion().isSameOrAfter( 8, 0, 14 );
}
@Override
public boolean supportsRecursiveCTE() {
return getMySQLVersion().isSameOrAfter( 8, 0, 14 );
}
@Override
@ -1248,6 +1273,12 @@ public class MySQLDialect extends Dialect {
return supportsAliasLocks() ? RowLockStrategy.TABLE : RowLockStrategy.NONE;
}
@Override
protected void registerDefaultKeywords() {
super.registerDefaultKeywords();
registerKeyword( "key" );
}
boolean supportsForShare() {
return getMySQLVersion().isSameOrAfter( 8 );
}

View File

@ -10,7 +10,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.ComparisonOperator;
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.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
@ -38,6 +37,22 @@ public class MySQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
expression.accept( this );
}
@Override
protected void visitRecursivePath(Expression recursivePath, int sizeEstimate) {
// MySQL determines the type and size of a column in a recursive CTE based on the expression of the non-recursive part
// Due to that, we have to cast the path in the non-recursive path to a varchar of appropriate size to avoid data truncation errors
if ( sizeEstimate == -1 ) {
super.visitRecursivePath( recursivePath, sizeEstimate );
}
else {
appendSql( "cast(" );
recursivePath.accept( this );
appendSql( " as char(" );
appendSql( sizeEstimate );
appendSql( "))" );
}
}
@Override
public void visitBooleanExpressionPredicate(BooleanExpressionPredicate booleanExpressionPredicate) {
final boolean isNegated = booleanExpressionPredicate.isNegated();
@ -103,16 +118,6 @@ public class MySQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
}
}
@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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonDistinctOperator( lhs, operator, rhs );
@ -170,6 +175,11 @@ public class MySQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
return false;
}
@Override
protected boolean supportsWithClause() {
return getDialect().getVersion().isSameOrAfter( 8 );
}
@Override
protected String getFromDual() {
return " from dual";

View File

@ -1073,6 +1073,11 @@ public class OracleDialect extends Dialect {
return true;
}
@Override
public boolean supportsRecursiveCTE() {
return true;
}
@Override
public boolean supportsLateral() {
return getVersion().isSameOrAfter( 12, 1 );

View File

@ -21,6 +21,7 @@ import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.Expression;
@ -54,6 +55,37 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
super( sessionFactory, statement );
}
@Override
protected boolean needsRecursiveKeywordInWithClause() {
return false;
}
@Override
protected boolean supportsWithClauseInSubquery() {
// Oracle has some limitations, see ORA-32034, so we just report false here for simplicity
return false;
}
@Override
protected boolean supportsRecursiveSearchClause() {
return true;
}
@Override
protected boolean supportsRecursiveCycleClause() {
return true;
}
@Override
public void visitSqlSelection(SqlSelection sqlSelection) {
if ( getCurrentCteStatement() != null ) {
if ( getCurrentCteStatement().getMaterialization() == CteMaterialization.MATERIALIZED ) {
appendSql( "/*+ materialize */ " );
}
}
super.visitSqlSelection( sqlSelection );
}
@Override
protected LockStrategy determineLockingStrategy(
QuerySpec querySpec,
@ -169,31 +201,20 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
true, // we need select aliases to avoid ORA-00918: column ambiguously defined
() -> {
final QueryPart currentQueryPart = getQueryPartStack().getCurrent();
final boolean needsParenthesis;
final boolean needsWrapper;
if ( currentQueryPart instanceof QueryGroup ) {
needsParenthesis = false;
// visitQuerySpec will add the select wrapper
needsWrapper = !currentQueryPart.hasOffsetOrFetchClause();
}
else {
needsParenthesis = !querySpec.isRoot();
needsWrapper = true;
}
if ( needsWrapper ) {
if ( needsParenthesis ) {
appendSql( '(' );
}
appendSql( "select * from " );
if ( !needsParenthesis ) {
appendSql( '(' );
}
appendSql( "select * from (" );
}
super.visitQuerySpec( querySpec );
if ( needsWrapper ) {
if ( !needsParenthesis ) {
appendSql( ')' );
}
appendSql( ')' );
}
appendSql( " where rownum<=" );
final Stack<Clause> clauseStack = getClauseStack();
@ -209,12 +230,6 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
finally {
clauseStack.pop();
}
if ( needsWrapper ) {
if ( needsParenthesis ) {
appendSql( ')' );
}
}
}
);
}

View File

@ -1184,6 +1184,11 @@ public class PostgreSQLDialect extends Dialect {
return true;
}
@Override
public boolean supportsRecursiveCTE() {
return true;
}
@Override
public boolean supportsFetchClause(FetchClauseType type) {
switch ( type ) {

View File

@ -60,6 +60,16 @@ public class PostgreSQLSqlAstTranslator<T extends JdbcOperation> extends Abstrac
}
}
@Override
protected boolean supportsRowConstructor() {
return true;
}
@Override
protected boolean supportsArrayConstructor() {
return true;
}
@Override
public boolean supportsFilterClause() {
return getDialect().getVersion().isSameOrAfter( 9, 4 );
@ -117,13 +127,27 @@ public class PostgreSQLSqlAstTranslator<T extends JdbcOperation> extends Abstrac
}
@Override
protected void renderSearchClause(CteStatement cte) {
// PostgreSQL does not support this, but it's just a hint anyway
protected boolean supportsRecursiveSearchClause() {
return getDialect().getVersion().isSameOrAfter( 14 );
}
@Override
protected void renderCycleClause(CteStatement cte) {
// PostgreSQL does not support this, but it can be emulated
protected boolean supportsRecursiveCycleClause() {
return getDialect().getVersion().isSameOrAfter( 14 );
}
@Override
protected boolean supportsRecursiveCycleUsingClause() {
return getDialect().getVersion().isSameOrAfter( 14 );
}
@Override
protected void renderStandardCycleClause(CteStatement cte) {
super.renderStandardCycleClause( cte );
if ( cte.getCycleMarkColumn() != null && cte.getCyclePathColumn() == null && supportsRecursiveCycleUsingClause() ) {
appendSql( " using " );
appendSql( determineCyclePathColumnName( cte ) );
}
}
@Override

View File

@ -614,6 +614,11 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
return true;
}
@Override
public boolean supportsRecursiveCTE() {
return true;
}
@Override
public boolean supportsFetchClause(FetchClauseType type) {
return getVersion().isSameOrAfter( 11 );

View File

@ -51,6 +51,16 @@ public class SQLServerSqlAstTranslator<T extends JdbcOperation> extends Abstract
super( sessionFactory, statement );
}
@Override
protected boolean needsRecursiveKeywordInWithClause() {
return false;
}
@Override
protected boolean supportsWithClauseInSubquery() {
return false;
}
@Override
protected void renderTableGroupJoin(TableGroupJoin tableGroupJoin, List<TableGroupJoin> tableGroupJoinCollector) {
appendSql( WHITESPACE );
@ -86,6 +96,10 @@ public class SQLServerSqlAstTranslator<T extends JdbcOperation> extends Abstract
}
protected boolean renderPrimaryTableReference(TableGroup tableGroup, LockMode lockMode) {
if ( shouldInlineCte( tableGroup ) ) {
inlineCteTableGroup( tableGroup, lockMode );
return false;
}
final TableReference tableReference = tableGroup.getPrimaryTableReference();
if ( tableReference instanceof NamedTableReference ) {
return renderNamedTableReference( (NamedTableReference) tableReference, lockMode );
@ -353,16 +367,6 @@ public class SQLServerSqlAstTranslator<T extends JdbcOperation> extends Abstract
}
}
@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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonEmulateIntersect( lhs, operator, rhs );

View File

@ -61,16 +61,6 @@ public class SpannerSqlAstTranslator<T extends JdbcOperation> extends AbstractSq
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
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonEmulateIntersect( lhs, operator, rhs );
@ -121,6 +111,10 @@ public class SpannerSqlAstTranslator<T extends JdbcOperation> extends AbstractSq
@Override
protected boolean renderPrimaryTableReference(TableGroup tableGroup, LockMode lockMode) {
if ( shouldInlineCte( tableGroup ) ) {
inlineCteTableGroup( tableGroup, lockMode );
return false;
}
final TableReference tableReference = tableGroup.getPrimaryTableReference();
if ( tableReference instanceof NamedTableReference ) {
return renderNamedTableReference( (NamedTableReference) tableReference, lockMode );

View File

@ -49,6 +49,11 @@ public class SybaseASESqlAstTranslator<T extends JdbcOperation> extends Abstract
super( sessionFactory, statement );
}
@Override
protected boolean supportsWithClause() {
return false;
}
// Sybase ASE does not allow CASE expressions where all result arms contain plain parameters.
// At least one result arm must provide some type context for inference,
// so we cast the first result arm if we encounter this condition
@ -138,16 +143,6 @@ public class SybaseASESqlAstTranslator<T extends JdbcOperation> extends Abstract
}
}
@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() ) {

View File

@ -39,6 +39,11 @@ public class SybaseSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
super( sessionFactory, statement );
}
@Override
protected boolean supportsWithClause() {
return false;
}
// Sybase does not allow CASE expressions where all result arms contain plain parameters.
// At least one result arm must provide some type context for inference,
// so we cast the first result arm if we encounter this condition
@ -105,16 +110,6 @@ public class SybaseSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
// Sybase does not support the FOR UPDATE clause
}
@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 );

View File

@ -39,7 +39,8 @@ public class TiDBDialect extends MySQLDialect {
}
public TiDBDialect(DialectResolutionInfo info) {
super(info);
super( createVersion( info ), getCharacterSetBytesPerCharacter( info.getDatabaseMetadata() ) );
registerKeywords( info );
}
@Override
@ -98,6 +99,11 @@ public class TiDBDialect extends MySQLDialect {
};
}
@Override
public boolean supportsRecursiveCTE() {
return true;
}
@Override
public boolean supportsNoWait() {
return true;

View File

@ -95,16 +95,6 @@ public class TiDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
}
}
@Override
protected void renderSearchClause(CteStatement cte) {
// TiDB does not support this, but it's just a hint anyway
}
@Override
protected void renderCycleClause(CteStatement cte) {
// TiDB does not support this, but it can be emulated
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonDistinctOperator( lhs, operator, rhs );

View File

@ -38,6 +38,7 @@ import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
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.ast.tree.select.SortSpecification;
import org.hibernate.sql.results.internal.ResolvedSqlSelection;
import org.hibernate.type.BasicType;
@ -417,7 +418,7 @@ public class AggregateWindowEmulationQueryTransformer implements QueryTransforme
final QueryPartTableGroup queryPartTableGroup = new QueryPartTableGroup(
navigablePath,
null,
subQuerySpec,
new SelectStatement( subQuerySpec ),
identifierVariable,
columnNames,
false,

View File

@ -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.function;
import java.util.List;
import java.util.Locale;
import org.hibernate.QueryException;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.INTEGER;
/**
* A chr implementation that translates integer literals to string literals.
*
* @author Christian Beikov
*/
public class ChrLiteralEmulation extends AbstractSqmSelfRenderingFunctionDescriptor {
public ChrLiteralEmulation(TypeConfiguration typeConfiguration) {
super(
"chr",
new ArgumentTypesValidator(
StandardArgumentsValidators.composite(
StandardArgumentsValidators.exactly( 1 ),
(arguments, functionName, queryEngine) -> {
if ( !( arguments.get( 0 ) instanceof SqmLiteral<?> ) ) {
throw new QueryException(
String.format(
Locale.ROOT,
"Emulation of function chr() supports only integer literals, but %s argument given",
arguments.get( 0 ).getClass().getName()
)
);
}
}
),
INTEGER
),
StandardFunctionReturnTypeResolvers.invariant(
typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.CHARACTER )
),
StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, INTEGER )
);
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> arguments,
SqlAstTranslator<?> walker) {
@SuppressWarnings("unchecked")
final QueryLiteral<Number> literal = (QueryLiteral<Number>) arguments.get( 0 );
sqlAppender.appendSql( '\'' );
sqlAppender.appendSql( (char) literal.getLiteralValue().intValue() );
sqlAppender.appendSql( '\'' );
}
}

View File

@ -27,6 +27,7 @@ import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Distinct;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
import org.hibernate.sql.ast.tree.expression.Star;
@ -54,7 +55,14 @@ public class CountFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
TypeConfiguration typeConfiguration,
SqlAstNodeRenderingMode defaultArgumentRenderingMode,
String concatOperator) {
this( dialect, typeConfiguration, defaultArgumentRenderingMode, concatOperator, null, false );
this(
dialect,
typeConfiguration,
defaultArgumentRenderingMode,
concatOperator,
null,
false
);
}
public CountFunction(
@ -142,6 +150,19 @@ public class CountFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
// '' -> \0 + argumentNumber
// In the end, the expression looks like the following:
// count(distinct coalesce(nullif(coalesce(col1 || '', '\0'), ''), '\01') || '\0' || coalesce(nullif(coalesce(col2 || '', '\0'), ''), '\02'))
final AbstractSqmSelfRenderingFunctionDescriptor chr =
(AbstractSqmSelfRenderingFunctionDescriptor) translator.getSessionFactory()
.getQueryEngine()
.getSqmFunctionRegistry()
.findFunctionDescriptor( "chr" );
final List<Expression> chrArguments = List.of(
new QueryLiteral<>(
0,
translator.getSessionFactory()
.getTypeConfiguration()
.getBasicTypeForJavaType( Integer.class )
)
);
if ( caseWrapper ) {
translator.getCurrentClauseStack().push( Clause.WHERE );
sqlAppender.appendSql( "case when " );
@ -161,11 +182,16 @@ public class CountFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
sqlAppender.appendSql( concatOperator );
sqlAppender.appendSql( "''" );
}
sqlAppender.appendSql( ",'\\0'),''),'\\0" );
sqlAppender.appendSql( SqlAppender.COMA_SEPARATOR_CHAR );
chr.render( sqlAppender, chrArguments, translator );
sqlAppender.appendSql( "),'')," );
chr.render( sqlAppender, chrArguments, translator );
sqlAppender.appendSql( concatOperator );
sqlAppender.appendSql( "'" );
sqlAppender.appendSql( argumentNumber );
sqlAppender.appendSql( "')" );
sqlAppender.appendSql( concatOperator );
sqlAppender.appendSql( "'\\0'" );
chr.render( sqlAppender, chrArguments, translator );
sqlAppender.appendSql( concatOperator );
sqlAppender.appendSql( "coalesce(nullif(coalesce(" );
needsConcat = renderCastedArgument( sqlAppender, translator, expressions.get( i ) );
@ -175,7 +201,12 @@ public class CountFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
sqlAppender.appendSql( concatOperator );
sqlAppender.appendSql( "''" );
}
sqlAppender.appendSql( ",'\\0'),''),'\\0" );
sqlAppender.appendSql( SqlAppender.COMA_SEPARATOR_CHAR );
chr.render( sqlAppender, chrArguments, translator );
sqlAppender.appendSql( "),'')," );
chr.render( sqlAppender, chrArguments, translator );
sqlAppender.appendSql( concatOperator );
sqlAppender.appendSql( "'" );
sqlAppender.appendSql( argumentNumber );
sqlAppender.appendSql( "')" );
if ( castDistinctStringConcat ) {

View File

@ -32,6 +32,11 @@ public interface Stack<T> {
*/
T getCurrent();
/**
* The element currently at the bottom of the stack
*/
T getRoot();
/**
* How many elements are currently on the stack?
*/

View File

@ -66,7 +66,15 @@ public final class StandardStack<T> implements Stack<T> {
if ( internalStack == null ) {
return null;
}
return convert( internalStack.peek() );
return convert( internalStack.peekFirst() );
}
@Override
public T getRoot() {
if ( internalStack == null ) {
return null;
}
return convert( internalStack.peekLast() );
}
@Override

View File

@ -31,6 +31,7 @@ import jakarta.persistence.criteria.Selection;
import jakarta.persistence.criteria.SetJoin;
import jakarta.persistence.criteria.Subquery;
import org.hibernate.Incubating;
import org.hibernate.query.sqm.NullPrecedence;
import org.hibernate.query.sqm.SortOrder;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
@ -104,6 +105,36 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
<T> JpaCriteriaQuery<T> except(boolean all, CriteriaQuery<? extends T> query1, CriteriaQuery<?>... queries);
default <T> JpaSubQuery<T> unionAll(Subquery<? extends T> query1, Subquery<?>... queries) {
return union( true, query1, queries );
}
default <T> JpaSubQuery<T> union(Subquery<? extends T> query1, Subquery<?>... queries) {
return union( false, query1, queries );
}
<T> JpaSubQuery<T> union(boolean all, Subquery<? extends T> query1, Subquery<?>... queries);
default <T> JpaSubQuery<T> intersectAll(Subquery<? extends T> query1, Subquery<?>... queries) {
return intersect( true, query1, queries );
}
default <T> JpaSubQuery<T> intersect(Subquery<? extends T> query1, Subquery<?>... queries) {
return intersect( false, query1, queries );
}
<T> JpaSubQuery<T> intersect(boolean all, Subquery<? extends T> query1, Subquery<?>... queries);
default <T> JpaSubQuery<T> exceptAll(Subquery<? extends T> query1, Subquery<?>... queries) {
return except( true, query1, queries );
}
default <T> JpaSubQuery<T> except(Subquery<? extends T> query1, Subquery<?>... queries) {
return except( false, query1, queries );
}
<T> JpaSubQuery<T> except(boolean all, Subquery<? extends T> query1, Subquery<?>... queries);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// JPA 3.1
@ -798,4 +829,65 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
* @return descending ordering corresponding to the expression
*/
JpaOrder desc(Expression<?> x, boolean nullsFirst);
/**
* Create a search ordering based on the sort order and null precedence of the value of the CTE attribute.
* @param cteAttribute CTE attribute used to define the ordering
* @param sortOrder The sort order
* @param nullPrecedence The null precedence
* @return ordering corresponding to the CTE attribute
*/
@Incubating
JpaSearchOrder search(JpaCteCriteriaAttribute cteAttribute, SortOrder sortOrder, NullPrecedence nullPrecedence);
/**
* Create a search ordering based on the sort order of the value of the CTE attribute.
* @param cteAttribute CTE attribute used to define the ordering
* @param sortOrder The sort order
* @return ordering corresponding to the CTE attribute
*/
@Incubating
JpaSearchOrder search(JpaCteCriteriaAttribute cteAttribute, SortOrder sortOrder);
/**
* Create a search ordering based on the ascending value of the CTE attribute.
* @param cteAttribute CTE attribute used to define the ordering
* @return ascending ordering corresponding to the CTE attribute
*/
@Incubating
JpaSearchOrder search(JpaCteCriteriaAttribute cteAttribute);
/**
* Create a search ordering by the ascending value of the CTE attribute.
* @param x CTE attribute used to define the ordering
* @return ascending ordering corresponding to the CTE attribute
*/
@Incubating
JpaSearchOrder asc(JpaCteCriteriaAttribute x);
/**
* Create a search ordering by the descending value of the CTE attribute.
* @param x CTE attribute used to define the ordering
* @return descending ordering corresponding to the CTE attribute
*/
@Incubating
JpaSearchOrder desc(JpaCteCriteriaAttribute x);
/**
* Create a search ordering by the ascending value of the CTE attribute.
* @param x CTE attribute used to define the ordering
* @param nullsFirst Whether <code>null</code> should be sorted first
* @return ascending ordering corresponding to the CTE attribute
*/
@Incubating
JpaSearchOrder asc(JpaCteCriteriaAttribute x, boolean nullsFirst);
/**
* Create a search ordering by the descending value of the CTE attribute.
* @param x CTE attribute used to define the ordering
* @param nullsFirst Whether <code>null</code> should be sorted first
* @return descending ordering corresponding to the CTE attribute
*/
@Incubating
JpaSearchOrder desc(JpaCteCriteriaAttribute x, boolean nullsFirst);
}

View File

@ -0,0 +1,77 @@
/*
* 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.criteria;
import java.util.Collection;
import java.util.function.Function;
import org.hibernate.Incubating;
import org.hibernate.query.sqm.tree.SqmJoinType;
import jakarta.persistence.criteria.AbstractQuery;
import jakarta.persistence.criteria.CriteriaQuery;
/**
* Common contract for criteria parts that can hold CTEs (common table expressions).
*/
@Incubating
public interface JpaCteContainer extends JpaCriteriaNode {
/**
* Returns the CTEs that are registered on this container.
*/
Collection<? extends JpaCteCriteria<?>> getCteCriterias();
/**
* Returns a CTE that is registered by the given name on this container, or any of its parents.
*/
<T> JpaCteCriteria<T> getCteCriteria(String cteName);
/**
* Registers the given {@link CriteriaQuery} and returns a {@link JpaCteCriteria},
* which can be used for querying.
*
* @see JpaCriteriaQuery#from(JpaCteCriteria)
* @see JpaFrom#join(JpaCteCriteria, SqmJoinType)
*/
<T> JpaCteCriteria<T> with(AbstractQuery<T> criteria);
/**
* Allows to register a recursive CTE. The base {@link CriteriaQuery} serves
* for the structure of the {@link JpaCteCriteria}, which is made available in the recursive criteria producer function,
* so that the recursive {@link CriteriaQuery} is able to refer to the CTE again.
*
* @see JpaCriteriaQuery#from(JpaCteCriteria)
* @see JpaFrom#join(JpaCteCriteria, SqmJoinType)
*/
<T> JpaCteCriteria<T> withRecursiveUnionAll(AbstractQuery<T> baseCriteria, Function<JpaCteCriteria<T>, AbstractQuery<T>> recursiveCriteriaProducer);
/**
* Allows to register a recursive CTE. The base {@link CriteriaQuery} serves
* for the structure of the {@link JpaCteCriteria}, which is made available in the recursive criteria producer function,
* so that the recursive {@link CriteriaQuery} is able to refer to the CTE again.
*
* @see JpaCriteriaQuery#from(JpaCteCriteria)
* @see JpaFrom#join(JpaCteCriteria, SqmJoinType)
*/
<T> JpaCteCriteria<T> withRecursiveUnionDistinct(AbstractQuery<T> baseCriteria, Function<JpaCteCriteria<T>, AbstractQuery<T>> recursiveCriteriaProducer);
/**
* Like {@link #with(AbstractQuery)} but assigns an explicit CTE name.
*/
<T> JpaCteCriteria<T> with(String name, AbstractQuery<T> criteria);
/**
* Like {@link #withRecursiveUnionAll(AbstractQuery, Function)} but assigns an explicit CTE name.
*/
<T> JpaCteCriteria<T> withRecursiveUnionAll(String name, AbstractQuery<T> baseCriteria, Function<JpaCteCriteria<T>, AbstractQuery<T>> recursiveCriteriaProducer);
/**
* Like {@link #withRecursiveUnionDistinct(AbstractQuery, Function)} but assigns an explicit CTE name.
*/
<T> JpaCteCriteria<T> withRecursiveUnionDistinct(String name, AbstractQuery<T> baseCriteria, Function<JpaCteCriteria<T>, AbstractQuery<T>> recursiveCriteriaProducer);
}

View File

@ -0,0 +1,125 @@
/*
* 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.criteria;
import java.util.Arrays;
import java.util.List;
import org.hibernate.Incubating;
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind;
/**
* A CTE (common table expression) criteria.
*/
@Incubating
public interface JpaCteCriteria<T> extends JpaCriteriaNode {
/**
* The name under which this CTE is registered.
*/
String getName();
/**
* The type of the CTE.
*/
JpaCteCriteriaType<T> getType();
/**
* The definition of the CTE.
*/
JpaSelectCriteria<?> getCteDefinition();
/**
* The container within this CTE is registered.
*/
JpaCteContainer getCteContainer();
/**
* The materialization hint for the CTE.
*/
CteMaterialization getMaterialization();
void setMaterialization(CteMaterialization materialization);
/**
* The kind of search (breadth-first or depth-first) that should be done for a recursive query.
* May be null if unspecified or if this is not a recursive query.
*/
CteSearchClauseKind getSearchClauseKind();
/**
* The order by which should be searched.
*/
List<JpaSearchOrder> getSearchBySpecifications();
/**
* The attribute name by which one can order the final CTE result, to achieve the search order.
* Note that an implicit {@link JpaCteCriteriaAttribute} will be made available for this.
*/
String getSearchAttributeName();
default void search(CteSearchClauseKind kind, String searchAttributeName, JpaSearchOrder... searchOrders) {
search( kind, searchAttributeName, Arrays.asList( searchOrders ) );
}
void search(CteSearchClauseKind kind, String searchAttributeName, List<JpaSearchOrder> searchOrders);
/**
* The attributes to use for cycle detection.
*/
List<JpaCteCriteriaAttribute> getCycleAttributes();
/**
* The attribute name which is used to mark when a cycle has been detected.
* Note that an implicit {@link JpaCteCriteriaAttribute} will be made available for this.
*/
String getCycleMarkAttributeName();
/**
* The attribute name that represents the computation path, which is used for cycle detection.
* Note that an implicit {@link JpaCteCriteriaAttribute} will be made available for this.
*/
String getCyclePathAttributeName();
/**
* The value which is set for the cycle mark attribute when a cycle is detected.
*/
Object getCycleValue();
/**
* The default value for the cycle mark attribute when no cycle is detected.
*/
Object getNoCycleValue();
default void cycle(String cycleMarkAttributeName, JpaCteCriteriaAttribute... cycleColumns) {
cycleUsing( cycleMarkAttributeName, null, Arrays.asList( cycleColumns ) );
}
default void cycle(String cycleMarkAttributeName, List<JpaCteCriteriaAttribute> cycleColumns) {
cycleUsing( cycleMarkAttributeName, null, true, false, cycleColumns );
}
default void cycleUsing(String cycleMarkAttributeName, String cyclePathAttributeName, JpaCteCriteriaAttribute... cycleColumns) {
cycleUsing( cycleMarkAttributeName, cyclePathAttributeName, Arrays.asList( cycleColumns ) );
}
default void cycleUsing(String cycleMarkAttributeName, String cyclePathAttributeName, List<JpaCteCriteriaAttribute> cycleColumns) {
cycleUsing( cycleMarkAttributeName, cyclePathAttributeName, true, false, cycleColumns );
}
default <X> void cycle(String cycleMarkAttributeName, X cycleValue, X noCycleValue, JpaCteCriteriaAttribute... cycleColumns) {
cycleUsing( cycleMarkAttributeName, null, cycleValue, noCycleValue, Arrays.asList( cycleColumns ) );
}
default <X> void cycle(String cycleMarkAttributeName, X cycleValue, X noCycleValue, List<JpaCteCriteriaAttribute> cycleColumns) {
cycleUsing( cycleMarkAttributeName, null, cycleValue, noCycleValue, cycleColumns );
}
default <X> void cycleUsing(String cycleMarkAttributeName, String cyclePathAttributeName, X cycleValue, X noCycleValue, JpaCteCriteriaAttribute... cycleColumns) {
cycleUsing( cycleMarkAttributeName, cyclePathAttributeName, cycleValue, noCycleValue, Arrays.asList( cycleColumns ) );
}
<X> void cycleUsing(String cycleMarkAttributeName, String cyclePathAttributeName, X cycleValue, X noCycleValue, List<JpaCteCriteriaAttribute> cycleColumns);
}

View File

@ -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.query.criteria;
import org.hibernate.Incubating;
/**
* Describes the attribute of a {@link JpaCteCriteriaType}.
*/
@Incubating
public interface JpaCteCriteriaAttribute extends JpaCriteriaNode {
/**
* The declaring type.
*/
JpaCteCriteriaType<?> getDeclaringType();
/**
* The name of the attribute.
*/
String getName();
/**
* The java type of the attribute.
*/
Class<?> getJavaType();
}

View File

@ -0,0 +1,39 @@
/*
* 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.criteria;
import java.util.List;
import org.hibernate.Incubating;
import org.hibernate.metamodel.model.domain.DomainType;
/**
* A CTE (common table expression) criteria type.
*/
@Incubating
public interface JpaCteCriteriaType<T> extends JpaCriteriaNode {
/**
* The name under which this CTE is registered.
*/
String getName();
/**
* The domain type of the CTE.
*/
DomainType<T> getType();
/**
* The attributes of the CTE type.
*/
List<JpaCteCriteriaAttribute> getAttributes();
/**
* Returns the found attribute or null.
*/
JpaCteCriteriaAttribute getAttribute(String name);
}

View File

@ -9,12 +9,14 @@ package org.hibernate.query.criteria;
import org.hibernate.Incubating;
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Predicate;
/**
* @author Christian Beikov
*/
@Incubating
public interface JpaDerivedJoin<T> extends JpaDerivedFrom<T>, SqmQualifiedJoin<T,T>, JpaJoinedFrom<T,T> {
public interface JpaDerivedJoin<T> extends JpaDerivedFrom<T>, JpaJoinedFrom<T,T> {
/**
* Specifies whether the subquery part can access previous from node aliases.
* Normally, subqueries in the from clause are unable to access other from nodes,
@ -23,4 +25,16 @@ public interface JpaDerivedJoin<T> extends JpaDerivedFrom<T>, SqmQualifiedJoin<T
*/
boolean isLateral();
@Override
JpaDerivedJoin<T> on(JpaExpression<Boolean> restriction);
@Override
JpaDerivedJoin<T> on(Expression<Boolean> restriction);
@Override
JpaDerivedJoin<T> on(JpaPredicate... restrictions);
@Override
JpaDerivedJoin<T> on(Predicate... restrictions);
}

View File

@ -8,10 +8,25 @@ package org.hibernate.query.criteria;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Predicate;
/**
* @author Steve Ebersole
*/
public interface JpaEntityJoin<T> extends JpaJoinedFrom<T,T> {
@Override
EntityDomainType<T> getModel();
@Override
JpaEntityJoin<T> on(JpaExpression<Boolean> restriction);
@Override
JpaEntityJoin<T> on(Expression<Boolean> restriction);
@Override
JpaEntityJoin<T> on(JpaPredicate... restrictions);
@Override
JpaEntityJoin<T> on(Predicate... restrictions);
}

View File

@ -10,8 +10,19 @@ import org.hibernate.Incubating;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.sqm.tree.SqmJoinType;
import jakarta.persistence.criteria.CollectionJoin;
import jakarta.persistence.criteria.From;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.ListJoin;
import jakarta.persistence.criteria.MapJoin;
import jakarta.persistence.criteria.SetJoin;
import jakarta.persistence.criteria.Subquery;
import jakarta.persistence.metamodel.CollectionAttribute;
import jakarta.persistence.metamodel.ListAttribute;
import jakarta.persistence.metamodel.MapAttribute;
import jakarta.persistence.metamodel.SetAttribute;
import jakarta.persistence.metamodel.SingularAttribute;
/**
* API extension to the JPA {@link From} contract
@ -45,4 +56,71 @@ public interface JpaFrom<O,T> extends JpaPath<T>, JpaFetchParent<O,T>, From<O,T>
@Incubating
<X> JpaDerivedJoin<X> join(Subquery<X> subquery, SqmJoinType joinType, boolean lateral);
@Incubating
<X> JpaJoinedFrom<?, X> join(JpaCteCriteria<X> cte);
@Incubating
<X> JpaJoinedFrom<?, X> join(JpaCteCriteria<X> cte, SqmJoinType joinType);
// Covariant overrides
@Override
<Y> JpaJoin<T, Y> join(SingularAttribute<? super T, Y> attribute);
@Override
<Y> JpaJoin<T, Y> join(SingularAttribute<? super T, Y> attribute, JoinType jt);
@Override
<Y> JpaCollectionJoin<T, Y> join(CollectionAttribute<? super T, Y> collection);
@Override
<Y> JpaSetJoin<T, Y> join(SetAttribute<? super T, Y> set);
@Override
<Y> JpaListJoin<T, Y> join(ListAttribute<? super T, Y> list);
@Override
<K, V> JpaMapJoin<T, K, V> join(MapAttribute<? super T, K, V> map);
@Override
<Y> JpaCollectionJoin<T, Y> join(CollectionAttribute<? super T, Y> collection, JoinType jt);
@Override
<Y> JpaSetJoin<T, Y> join(SetAttribute<? super T, Y> set, JoinType jt);
@Override
<Y> JpaListJoin<T, Y> join(ListAttribute<? super T, Y> list, JoinType jt);
@Override
<K, V> JpaMapJoin<T, K, V> join(MapAttribute<? super T, K, V> map, JoinType jt);
@Override
<X, Y> JpaJoin<X, Y> join(String attributeName);
@Override
<X, Y> JpaCollectionJoin<X, Y> joinCollection(String attributeName);
@Override
<X, Y> JpaSetJoin<X, Y> joinSet(String attributeName);
@Override
<X, Y> JpaListJoin<X, Y> joinList(String attributeName);
@Override
<X, K, V> JpaMapJoin<X, K, V> joinMap(String attributeName);
@Override
<X, Y> JpaJoin<X, Y> join(String attributeName, JoinType jt);
@Override
<X, Y> JpaCollectionJoin<X, Y> joinCollection(String attributeName, JoinType jt);
@Override
<X, Y> JpaSetJoin<X, Y> joinSet(String attributeName, JoinType jt);
@Override
<X, Y> JpaListJoin<X, Y> joinList(String attributeName, JoinType jt);
@Override
<X, K, V> JpaMapJoin<X, K, V> joinMap(String attributeName, JoinType jt);
}

View File

@ -6,13 +6,29 @@
*/
package org.hibernate.query.criteria;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Predicate;
/**
* Exists within the hierarchy mainly to support "entity joins".
*
* @see JpaEntityJoin
* @see org.hibernate.query.sqm.tree.from.SqmEntityJoin
* @see JpaDerivedJoin
* @see org.hibernate.query.sqm.tree.from.SqmDerivedJoin
*
* @author Steve Ebersole
*/
public interface JpaJoinedFrom<O,T> extends JpaFrom<O, T> {
JpaJoinedFrom<O, T> on(JpaExpression<Boolean> restriction);
JpaJoinedFrom<O, T> on(Expression<Boolean> restriction);
JpaJoinedFrom<O, T> on(JpaPredicate... restrictions);
JpaJoinedFrom<O, T> on(Predicate... restrictions);
JpaPredicate getOn();
}

View File

@ -22,5 +22,5 @@ import jakarta.persistence.criteria.CommonAbstractCriteria;
*
* @author Steve Ebersole
*/
public interface JpaQueryableCriteria<T> extends JpaCriteriaBase, JpaCriteriaNode {
public interface JpaQueryableCriteria<T> extends JpaCriteriaBase, JpaCriteriaNode, JpaCteContainer {
}

View File

@ -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.query.criteria;
import org.hibernate.Incubating;
import org.hibernate.query.sqm.NullPrecedence;
import org.hibernate.query.sqm.SortOrder;
import jakarta.persistence.criteria.Order;
/**
* Represents the search order for a recursive CTE (common table expression).
*
* @see JpaCteCriteria
*/
@Incubating
public interface JpaSearchOrder extends JpaCriteriaNode {
SortOrder getSortOrder();
/**
* Set the precedence for nulls for this search order element
*/
JpaSearchOrder nullPrecedence(NullPrecedence precedence);
/**
* The precedence for nulls for this search order element
*/
NullPrecedence getNullPrecedence();
/**
* Whether ascending ordering is in effect.
* @return boolean indicating whether ordering is ascending
*/
boolean isAscending();
/**
* Switch the ordering.
* @return a new <code>Order</code> instance with the reversed ordering
*/
JpaSearchOrder reverse();
/**
* Return the CTE attribute that is used for ordering.
* @return CTE attribute used for ordering
*/
JpaCteCriteriaAttribute getAttribute();
}

View File

@ -38,6 +38,15 @@ public interface JpaSelectCriteria<T> extends AbstractQuery<T>, JpaCriteriaBase
*/
<X> JpaDerivedRoot<X> from(Subquery<X> subquery);
/**
* Create and add a query root corresponding to the given cte,
* forming a cartesian product with any existing roots.
*
* @param cte the cte criteria
* @return query root corresponding to the given cte
*/
<X> JpaRoot<X> from(JpaCteCriteria<X> cte);
@Override
JpaSelectCriteria<T> distinct(boolean distinct);

View File

@ -8,10 +8,17 @@ package org.hibernate.query.criteria;
import java.util.List;
import java.util.Set;
import jakarta.persistence.criteria.CollectionJoin;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.ListJoin;
import jakarta.persistence.criteria.MapJoin;
import jakarta.persistence.criteria.Order;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Selection;
import jakarta.persistence.criteria.SetJoin;
import jakarta.persistence.criteria.Subquery;
import org.hibernate.query.sqm.FetchClauseType;
@ -22,7 +29,7 @@ import org.hibernate.query.sqm.tree.from.SqmJoin;
/**
* @author Steve Ebersole
*/
public interface JpaSubQuery<T> extends Subquery<T>, JpaSelectCriteria<T>, JpaExpression<T> {
public interface JpaSubQuery<T> extends Subquery<T>, JpaSelectCriteria<T>, JpaExpression<T>, JpaCteContainer {
JpaSubQuery<T> multiselect(Selection<?>... selections);
@ -93,4 +100,22 @@ public interface JpaSubQuery<T> extends Subquery<T>, JpaSelectCriteria<T>, JpaEx
@Override
JpaSubQuery<T> having(Predicate... restrictions);
@Override
<Y> JpaRoot<Y> correlate(Root<Y> parentRoot);
@Override
<X, Y> JpaJoin<X, Y> correlate(Join<X, Y> parentJoin);
@Override
<X, Y> JpaCollectionJoin<X, Y> correlate(CollectionJoin<X, Y> parentCollection);
@Override
<X, Y> JpaSetJoin<X, Y> correlate(SetJoin<X, Y> parentSet);
@Override
<X, Y> JpaListJoin<X, Y> correlate(ListJoin<X, Y> parentList);
@Override
<X, K, V> JpaMapJoin<X, K, V> correlate(MapJoin<X, K, V> parentMap);
}

View File

@ -227,52 +227,40 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory();
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() );
final boolean canUseInnerJoin = joinType == SqlAstJoinType.INNER || lhs.canUseInnerJoins();
final EntityPersister entityPersister = delegate.getEntityMappingType().getEntityPersister();
final LazyTableGroup lazyTableGroup = new LazyTableGroup(
canUseInnerJoin,
final LazyTableGroup lazyTableGroup = createRootTableGroupJoin(
navigablePath,
fetched,
() -> createTableGroupInternal(
canUseInnerJoin,
navigablePath,
fetched,
null,
sqlAliasBase,
sqlExpressionResolver,
creationContext
),
(np, tableExpression) -> {
if ( !tableExpression.isEmpty() && !entityPersister.containsTableReference( tableExpression ) ) {
return false;
}
if ( navigablePath.equals( np.getParent() ) ) {
return targetKeyPropertyNames.contains( np.getLocalName() );
}
final String relativePath = np.relativize( navigablePath );
if ( relativePath == null ) {
return false;
}
// Empty relative path means the navigable paths are equal,
// in which case we allow resolving the parent table group
return relativePath.isEmpty() || targetKeyPropertyNames.contains( relativePath );
},
this,
lhs,
explicitSourceAlias,
sqlAliasBase,
creationContext.getSessionFactory(),
lhs
requestedJoinType,
fetched,
null,
aliasBaseGenerator,
sqlExpressionResolver,
fromClauseAccess,
creationContext
);
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
lazyTableGroup.getNavigablePath(),
navigablePath,
joinType,
lazyTableGroup,
null
);
lazyTableGroup.setTableGroupInitializerCallback(
createTableGroupInitializerCallback(
lhs,
sqlExpressionResolver,
sessionFactory,
tableGroupJoin::applyPredicate
)
);
return tableGroupJoin;
}
private Consumer<TableGroup> createTableGroupInitializerCallback(
TableGroup lhs,
SqlExpressionResolver sqlExpressionResolver,
SessionFactoryImplementor sessionFactory,
Consumer<Predicate> predicateConsumer) {
// -----------------
// Collect the selectable mappings for the FK key side and target side
// As we will "resolve" the derived column references for these mappings
@ -350,8 +338,7 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
}
);
}
lazyTableGroup.setTableGroupInitializerCallback(
tg -> {
Consumer<TableGroup> tableGroupInitializerCallback = tg -> {
this.identifierMapping.forEachSelectable(
(i, selectableMapping) -> {
final SelectableMapping targetMapping = targetMappings.get( i );
@ -360,7 +347,7 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
targetMapping.getContainingTableExpression(),
false
);
tableGroupJoin.applyPredicate(
predicateConsumer.accept(
new ComparisonPredicate(
keyColumnReferences.get( i ),
ComparisonOperator.EQUAL,
@ -373,9 +360,8 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
);
}
);
}
);
return tableGroupJoin;
};
return tableGroupInitializerCallback;
}
public TableGroup createTableGroupInternal(
@ -415,7 +401,7 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
}
@Override
public TableGroup createRootTableGroupJoin(
public LazyTableGroup createRootTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
@ -426,18 +412,58 @@ public class AnonymousTupleEntityValuedModelPart implements EntityValuedModelPar
SqlExpressionResolver sqlExpressionResolver,
FromClauseAccess fromClauseAccess,
SqlAstCreationContext creationContext) {
return ( (TableGroupJoinProducer) delegate ).createRootTableGroupJoin(
final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() );
final boolean canUseInnerJoin = sqlAstJoinType == SqlAstJoinType.INNER || lhs.canUseInnerJoins();
final EntityPersister entityPersister = delegate.getEntityMappingType().getEntityPersister();
final LazyTableGroup lazyTableGroup = new LazyTableGroup(
canUseInnerJoin,
navigablePath,
lhs,
explicitSourceAlias,
sqlAstJoinType,
fetched,
predicateConsumer,
aliasBaseGenerator,
sqlExpressionResolver,
fromClauseAccess,
creationContext
() -> createTableGroupInternal(
canUseInnerJoin,
navigablePath,
fetched,
null,
sqlAliasBase,
sqlExpressionResolver,
creationContext
),
(np, tableExpression) -> {
if ( !tableExpression.isEmpty() && !entityPersister.containsTableReference( tableExpression ) ) {
return false;
}
if ( navigablePath.equals( np.getParent() ) ) {
return targetKeyPropertyNames.contains( np.getLocalName() );
}
final String relativePath = np.relativize( navigablePath );
if ( relativePath == null ) {
return false;
}
// Empty relative path means the navigable paths are equal,
// in which case we allow resolving the parent table group
return relativePath.isEmpty() || targetKeyPropertyNames.contains( relativePath );
},
this,
explicitSourceAlias,
sqlAliasBase,
creationContext.getSessionFactory(),
lhs
);
if ( predicateConsumer != null ) {
lazyTableGroup.setTableGroupInitializerCallback(
createTableGroupInitializerCallback(
lhs,
sqlExpressionResolver,
creationContext.getSessionFactory(),
predicateConsumer
)
);
}
return lazyTableGroup;
}
@Override

View File

@ -7,7 +7,9 @@
package org.hibernate.query.derived;
import org.hibernate.Incubating;
import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.EmbeddableDomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.PersistentAttribute;
import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource;
@ -19,6 +21,7 @@ import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.spi.NavigablePath;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.java.JavaType;
/**
@ -76,8 +79,8 @@ public class AnonymousTupleSqmPathSource<J> implements SqmPathSource<J> {
else {
navigablePath = lhs.getNavigablePath().append( intermediatePathSource.getPathName() ).append( getPathName() );
}
final SqmPathSource<J> nodeType = path.getNodeType();
if ( nodeType instanceof BasicSqmPathSource<?> ) {
final DomainType<?> domainType = path.getNodeType().getSqmPathType();
if ( domainType instanceof BasicDomainType<?> ) {
return new SqmBasicValuedSimplePath<>(
navigablePath,
this,
@ -85,7 +88,7 @@ public class AnonymousTupleSqmPathSource<J> implements SqmPathSource<J> {
lhs.nodeBuilder()
);
}
else if ( nodeType instanceof EmbeddedSqmPathSource<?> ) {
else if ( domainType instanceof EmbeddableDomainType<?> ) {
return new SqmEmbeddedValuedSimplePath<>(
navigablePath,
this,
@ -93,8 +96,7 @@ public class AnonymousTupleSqmPathSource<J> implements SqmPathSource<J> {
lhs.nodeBuilder()
);
}
else if ( nodeType instanceof EntitySqmPathSource<?> || nodeType instanceof EntityDomainType<?>
|| nodeType instanceof PersistentAttribute<?, ?> && nodeType.getSqmPathType() instanceof EntityDomainType<?> ) {
else if ( domainType instanceof EntityDomainType<?> ) {
return new SqmEntityValuedSimplePath<>(
navigablePath,
this,
@ -103,6 +105,6 @@ public class AnonymousTupleSqmPathSource<J> implements SqmPathSource<J> {
);
}
throw new UnsupportedOperationException( "Unsupported path source: " + nodeType );
throw new UnsupportedOperationException( "Unsupported path source: " + domainType );
}
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.query.derived;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -29,6 +30,7 @@ import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.model.domain.DomainType;
@ -43,6 +45,7 @@ import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.cte.CteColumn;
import org.hibernate.sql.ast.tree.from.LazyTableGroup;
import org.hibernate.sql.ast.tree.from.PluralTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
@ -304,6 +307,10 @@ public class AnonymousTupleTableGroupProducer implements TableGroupProducer, Map
}
}
public Map<String, ModelPart> getModelParts() {
return modelParts;
}
@Override
public String getSqlAliasStem() {
return aliasStem;
@ -314,6 +321,16 @@ public class AnonymousTupleTableGroupProducer implements TableGroupProducer, Map
return javaTypeDescriptor;
}
@Override
public int forEachSelectable(int offset, SelectableConsumer consumer) {
final int originalOffset = offset;
for ( ModelPart modelPart : modelParts.values() ) {
offset += modelPart.forEachSelectable( offset, consumer );
}
return offset - originalOffset;
}
//--------------------------------
// Support for using the anonymous tuple as table reference directly somewhere is not yet implemented
//--------------------------------
@ -371,5 +388,4 @@ public class AnonymousTupleTableGroupProducer implements TableGroupProducer, Map
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
throw new UnsupportedOperationException( "Not yet implemented" );
}
}

View File

@ -16,6 +16,7 @@ import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.UnsupportedMappingException;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.ManagedDomainType;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.metamodel.model.domain.SimpleDomainType;
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
@ -32,6 +33,8 @@ import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.ObjectArrayJavaType;
import jakarta.persistence.metamodel.Attribute;
/**
* @author Christian Beikov
@ -88,6 +91,53 @@ public class AnonymousTupleType<T> implements TupleType<T>, DomainType<T>, Retur
return new AnonymousTupleTableGroupProducer( this, aliasStem, sqlSelections, fromClauseAccess );
}
public List<String> determineColumnNames() {
final int componentCount = componentCount();
final List<String> columnNames = new ArrayList<>( componentCount );
for ( int i = 0; i < componentCount; i++ ) {
final SqmSelectableNode<?> selectableNode = getSelectableNode( i );
final String componentName = getComponentName( i );
if ( selectableNode instanceof SqmPath<?> ) {
addColumnNames(
columnNames,
( (SqmPath<?>) selectableNode ).getNodeType().getSqmPathType(),
componentName
);
}
else {
columnNames.add( componentName );
}
}
return columnNames;
}
private static void addColumnNames(List<String> columnNames, DomainType<?> domainType, String componentName) {
if ( domainType instanceof EntityDomainType<?> ) {
final EntityDomainType<?> entityDomainType = (EntityDomainType<?>) domainType;
final SingularPersistentAttribute<?, ?> idAttribute = entityDomainType.findIdAttribute();
final String idPath;
if ( idAttribute == null ) {
idPath = componentName;
}
else {
idPath = componentName + "_" + idAttribute.getName();
}
addColumnNames( columnNames, entityDomainType.getIdentifierDescriptor().getSqmPathType(), idPath );
}
else if ( domainType instanceof ManagedDomainType<?> ) {
for ( Attribute<?, ?> attribute : ( (ManagedDomainType<?>) domainType ).getAttributes() ) {
if ( !( attribute instanceof SingularPersistentAttribute<?, ?> ) ) {
throw new IllegalArgumentException( "Only embeddables without collections are supported" );
}
final DomainType<?> attributeType = ( (SingularPersistentAttribute<?, ?>) attribute ).getType();
addColumnNames( columnNames, attributeType, componentName + "_" + attribute.getName() );
}
}
else {
columnNames.add( componentName );
}
}
@Override
public int componentCount() {
return components.length;
@ -114,6 +164,10 @@ public class AnonymousTupleType<T> implements TupleType<T>, DomainType<T>, Retur
return index == null ? null : components[index].getExpressible();
}
protected Integer getIndex(String componentName) {
return componentIndexMap.get( componentName );
}
public SqmSelectableNode<?> getSelectableNode(int index) {
return components[index];
}

View File

@ -0,0 +1,101 @@
/*
* 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.derived;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.Incubating;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.query.sqm.tree.cte.SqmCteStatement;
import org.hibernate.query.sqm.tree.cte.SqmCteTable;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.cte.CteColumn;
import org.hibernate.type.BasicType;
/**
* The table group producer for a CTE tuple type.
*
* Exposes additional access to some special model parts for recursive CTE attributes.
*
* @author Christian Beikov
*/
@Incubating
public class CteTupleTableGroupProducer extends AnonymousTupleTableGroupProducer {
private final AnonymousTupleBasicValuedModelPart searchModelPart;
private final AnonymousTupleBasicValuedModelPart cycleMarkModelPart;
private final AnonymousTupleBasicValuedModelPart cyclePathModelPart;
public CteTupleTableGroupProducer(
SqmCteTable<?> sqmCteTable,
String aliasStem,
List<SqlSelection> sqlSelections,
FromClauseAccess fromClauseAccess) {
super( sqmCteTable, aliasStem, sqlSelections, fromClauseAccess );
final SqmCteStatement<?> cteStatement = sqmCteTable.getCteStatement();
final BasicType<String> stringType = cteStatement.nodeBuilder()
.getTypeConfiguration()
.getBasicTypeForJavaType( String.class );
this.searchModelPart = createModelPart( cteStatement.getSearchAttributeName(), stringType );
this.cycleMarkModelPart = createModelPart(
cteStatement.getCycleMarkAttributeName(),
cteStatement.getCycleLiteral() == null
? null
: (BasicType<?>) cteStatement.getCycleLiteral().getNodeType()
);
this.cyclePathModelPart = createModelPart( cteStatement.getCyclePathAttributeName(), stringType );
}
private static AnonymousTupleBasicValuedModelPart createModelPart(String attributeName, BasicType<?> basicType) {
if ( attributeName != null ) {
return new AnonymousTupleBasicValuedModelPart(
attributeName,
attributeName,
basicType,
basicType
);
}
return null;
}
public List<CteColumn> determineCteColumns() {
final List<CteColumn> columns = new ArrayList<>( getModelParts().size() );
forEachSelectable(
(selectionIndex, selectableMapping) -> {
columns.add(
new CteColumn(
selectableMapping.getSelectionExpression(),
selectableMapping.getJdbcMapping()
)
);
}
);
return columns;
}
@Override
public ModelPart findSubPart(String name, EntityMappingType treatTargetType) {
final ModelPart subPart = super.findSubPart( name, treatTargetType );
if ( subPart != null ) {
return subPart;
}
if ( searchModelPart != null && name.equals( searchModelPart.getPartName() ) ) {
return searchModelPart;
}
if ( cycleMarkModelPart != null && name.equals( cycleMarkModelPart.getPartName() ) ) {
return cycleMarkModelPart;
}
if ( cyclePathModelPart != null && name.equals( cyclePathModelPart.getPartName() ) ) {
return cyclePathModelPart;
}
return null;
}
}

View File

@ -17,7 +17,11 @@ import org.hibernate.query.sqm.SqmJoinable;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.spi.SqmCreationHelper;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.cte.SqmCteStatement;
import org.hibernate.query.sqm.tree.domain.SqmCteRoot;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor;
import org.hibernate.query.sqm.tree.from.SqmCteJoin;
import org.hibernate.query.sqm.tree.from.SqmEntityJoin;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmJoin;
@ -270,7 +274,7 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer {
private final StringBuilder path = new StringBuilder();
private SqmEntityJoin<?> join;
private SqmPath<?> join;
public ExpectingEntityJoinDelegate(
String identifier,
@ -301,6 +305,12 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer {
.getJpaMetamodel()
.resolveHqlEntityReference( fullPath );
if ( joinedEntityType == null ) {
final SqmCteStatement<?> cteStatement = creationState.findCteStatement( fullPath );
if ( cteStatement != null ) {
join = new SqmCteJoin<>( cteStatement, alias, joinType, sqmRoot );
creationState.getCurrentProcessingState().getPathRegistry().register( join );
return;
}
throw new SemanticException( "Could not resolve join path - " + fullPath );
}

View File

@ -10,13 +10,22 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.internal.util.collections.StandardStack;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.criteria.JpaCteCriteria;
import org.hibernate.query.criteria.JpaCteCriteriaAttribute;
import org.hibernate.query.criteria.JpaSearchOrder;
import org.hibernate.query.sqm.tree.cte.SqmCteTableColumn;
import org.hibernate.query.sqm.tree.cte.SqmSearchClauseSpecification;
import org.hibernate.query.sqm.tree.domain.SqmCteRoot;
import org.hibernate.query.sqm.tree.domain.SqmDerivedRoot;
import org.hibernate.query.sqm.tree.from.SqmCteJoin;
import org.hibernate.query.sqm.tree.from.SqmDerivedJoin;
import org.hibernate.query.sqm.tree.select.SqmSelectQuery;
import org.hibernate.spi.NavigablePath;
import org.hibernate.query.hql.spi.SqmCreationOptions;
import org.hibernate.query.hql.spi.SqmCreationProcessingState;
@ -81,6 +90,9 @@ import org.hibernate.query.sqm.tree.update.SqmSetClause;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.type.descriptor.java.JavaType;
import jakarta.persistence.criteria.AbstractQuery;
import jakarta.persistence.criteria.CriteriaQuery;
/**
* Handles splitting queries containing unmapped polymorphic references.
*
@ -215,17 +227,129 @@ public class QuerySplitter {
public Object visitCteContainer(SqmCteContainer consumer) {
final SqmCteContainer processingQuery = (SqmCteContainer) getProcessingStateStack().getCurrent()
.getProcessingQuery();
processingQuery.setWithRecursive( consumer.isWithRecursive() );
for ( SqmCteStatement<?> cteStatement : consumer.getCteStatements() ) {
processingQuery.addCteStatement( visitCteStatement( cteStatement ) );
visitCteStatement( cteStatement );
}
return processingQuery;
}
@Override
public SqmCteStatement<?> visitCteStatement(SqmCteStatement<?> sqmCteStatement) {
// No need to copy anything here
return sqmCteStatement;
final SqmCteContainer processingQuery = (SqmCteContainer) getProcessingStateStack().getCurrent()
.getProcessingQuery();
final SqmSelectQuery<?> cteDefinition = sqmCteStatement.getCteDefinition();
final SqmQueryPart<?> queryPart = cteDefinition.getQueryPart();
JpaCteCriteria<?> cteCriteria = null;
if ( cteDefinition.getCteStatements().isEmpty()
&& queryPart instanceof SqmQueryGroup<?> && queryPart.getSortSpecifications().isEmpty()
&& queryPart.getFetchExpression() == null && queryPart.getOffsetExpression() == null ) {
final SqmQueryGroup<?> queryGroup = (SqmQueryGroup<?>) queryPart;
boolean unionDistinct = false;
switch ( queryGroup.getSetOperator() ) {
case UNION:
unionDistinct = true;
case UNION_ALL:
if ( queryGroup.getQueryParts().size() == 2 ) {
final SqmSelectQuery<Object> nonRecursiveStatement = visitSelectQuery(
cteDefinition,
queryGroup.getQueryParts().get( 0 )
);
final Function<JpaCteCriteria<Object>, AbstractQuery<Object>> recursiveCriteriaProducer = jpaCteCriteria -> {
return visitSelectQuery( cteDefinition, queryGroup.getQueryParts().get( 1 ) );
};
if ( sqmCteStatement.getName() == null ) {
if ( unionDistinct ) {
cteCriteria = processingQuery.withRecursiveUnionDistinct(
nonRecursiveStatement,
recursiveCriteriaProducer
);
}
else {
cteCriteria = processingQuery.withRecursiveUnionAll(
nonRecursiveStatement,
recursiveCriteriaProducer
);
}
}
else {
if ( unionDistinct ) {
cteCriteria = processingQuery.withRecursiveUnionDistinct(
sqmCteStatement.getName(),
nonRecursiveStatement,
recursiveCriteriaProducer
);
}
else {
cteCriteria = processingQuery.withRecursiveUnionAll(
sqmCteStatement.getName(),
nonRecursiveStatement,
recursiveCriteriaProducer
);
}
}
if ( sqmCteStatement.getSearchClauseKind() != null ) {
final List<JpaSearchOrder> searchBySpecifications = sqmCteStatement.getSearchBySpecifications();
final List<JpaSearchOrder> newSearchBySpecifications = new ArrayList<>( searchBySpecifications.size() );
for ( JpaSearchOrder searchBySpecification : searchBySpecifications ) {
newSearchBySpecifications.add(
new SqmSearchClauseSpecification(
(SqmCteTableColumn) cteCriteria.getType().getAttribute( searchBySpecification.getAttribute().getName() ),
searchBySpecification.getSortOrder(),
searchBySpecification.getNullPrecedence()
)
);
}
cteCriteria.search(
sqmCteStatement.getSearchClauseKind(),
sqmCteStatement.getSearchAttributeName(),
newSearchBySpecifications
);
}
if ( sqmCteStatement.getCycleMarkAttributeName() != null ) {
final List<JpaCteCriteriaAttribute> cycleAttributes = sqmCteStatement.getCycleAttributes();
final List<JpaCteCriteriaAttribute> newCycleAttributes = new ArrayList<>( cycleAttributes.size() );
for ( JpaCteCriteriaAttribute cycleAttribute : cycleAttributes ) {
newCycleAttributes.add(
cteCriteria.getType().getAttribute( cycleAttribute.getName() )
);
}
cteCriteria.cycleUsing(
sqmCteStatement.getCycleMarkAttributeName(),
sqmCteStatement.getCyclePathAttributeName(),
sqmCteStatement.getCycleValue(),
sqmCteStatement.getNoCycleValue(),
newCycleAttributes
);
}
}
}
}
if ( cteCriteria == null ) {
if ( sqmCteStatement.getName() == null ) {
cteCriteria = processingQuery.with( visitSelectQuery( cteDefinition ) );
}
else {
cteCriteria = processingQuery.with( sqmCteStatement.getName(), visitSelectQuery( cteDefinition ) );
}
}
if ( sqmCteStatement.getMaterialization() != null ) {
cteCriteria.setMaterialization( sqmCteStatement.getMaterialization() );
}
return (SqmCteStatement<?>) cteCriteria;
}
@Override
public SqmCteStatement<?> findCteStatement(String name) {
return processingStateStack.findCurrentFirst(
state -> {
if ( state.getProcessingQuery() instanceof SqmCteContainer ) {
final SqmCteContainer container = (SqmCteContainer) state.getProcessingQuery();
return container.getCteStatement( name );
}
return null;
}
);
}
@Override
@ -285,6 +409,43 @@ public class QuerySplitter {
return copy;
}
@Override
protected SqmSelectQuery<Object> visitSelectQuery(SqmSelectQuery<?> selectQuery) {
if ( selectQuery instanceof SqmSelectStatement<?> ) {
return (SqmSelectQuery<Object>) visitSelectStatement( (SqmSelectStatement<?>) selectQuery );
}
else {
return visitSubQueryExpression( (SqmSubQuery<?>) selectQuery );
}
}
protected SqmSelectQuery<Object> visitSelectQuery(SqmSelectQuery<?> selectQuery, SqmQueryPart<?> queryPart) {
if ( selectQuery instanceof SqmSelectStatement<?> ) {
final SqmSelectStatement<?> sqmSelectStatement = (SqmSelectStatement<?>) selectQuery;
//noinspection rawtypes
return (SqmSelectQuery<Object>) visitSelectStatement(
new SqmSelectStatement(
queryPart,
sqmSelectStatement.getResultType(),
sqmSelectStatement.getQuerySource(),
sqmSelectStatement.nodeBuilder()
)
);
}
else {
final SqmSubQuery<?> sqmSubQuery = (SqmSubQuery<?>) selectQuery;
//noinspection rawtypes
return visitSubQueryExpression(
new SqmSubQuery(
processingStateStack.getCurrent().getProcessingQuery(),
queryPart,
sqmSubQuery.getResultType(),
sqmSubQuery.nodeBuilder()
)
);
}
}
@Override
public SqmQueryPart<R> visitQueryPart(SqmQueryPart<?> queryPart) {
return (SqmQueryPart<R>) super.visitQueryPart( queryPart );
@ -411,6 +572,25 @@ public class QuerySplitter {
return copy;
}
@Override
public SqmCteRoot<?> visitRootCte(SqmCteRoot<?> sqmRoot) {
SqmFrom<?, ?> sqmFrom = sqmFromCopyMap.get( sqmRoot );
if ( sqmFrom != null ) {
return (SqmCteRoot<?>) sqmFrom;
}
final SqmCteRoot<?> copy = new SqmCteRoot<>(
(SqmCteStatement<?>) sqmRoot.getCte().accept( this ),
sqmRoot.getExplicitAlias()
);
getProcessingStateStack().getCurrent().getPathRegistry().register( copy );
sqmFromCopyMap.put( sqmRoot, copy );
sqmPathCopyMap.put( sqmRoot.getNavigablePath(), copy );
if ( currentFromClauseCopy != null ) {
currentFromClauseCopy.addRoot( copy );
}
return copy;
}
@Override
public SqmCrossJoin<?> visitCrossJoin(SqmCrossJoin<?> join) {
final SqmFrom<?, ?> sqmFrom = sqmFromCopyMap.get( join );
@ -506,6 +686,26 @@ public class QuerySplitter {
return copy;
}
@Override
public SqmCteJoin<?> visitQualifiedCteJoin(SqmCteJoin<?> join) {
SqmFrom<?, ?> sqmFrom = sqmFromCopyMap.get( join );
if ( sqmFrom != null ) {
return (SqmCteJoin<?>) sqmFrom;
}
final SqmRoot<?> sqmRoot = (SqmRoot<?>) sqmFromCopyMap.get( join.findRoot() );
final SqmCteJoin copy = new SqmCteJoin(
(SqmCteStatement<?>) join.getCte().accept( this ),
join.getExplicitAlias(),
join.getSqmJoinType(),
sqmRoot
);
getProcessingStateStack().getCurrent().getPathRegistry().register( copy );
sqmFromCopyMap.put( join, copy );
sqmPathCopyMap.put( join.getNavigablePath(), copy );
sqmRoot.addSqmJoin( copy );
return copy;
}
@Override
public SqmBasicValuedSimplePath<?> visitBasicValuedPath(SqmBasicValuedSimplePath<?> path) {
final SqmPathRegistry pathRegistry = getProcessingStateStack().getCurrent().getPathRegistry();

View File

@ -61,6 +61,10 @@ import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource;
import org.hibernate.query.PathException;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.SemanticException;
import org.hibernate.query.criteria.JpaCteCriteria;
import org.hibernate.query.criteria.JpaCteCriteriaAttribute;
import org.hibernate.query.criteria.JpaCteCriteriaType;
import org.hibernate.query.criteria.JpaSearchOrder;
import org.hibernate.query.hql.HqlLogging;
import org.hibernate.query.hql.spi.DotIdentifierConsumer;
import org.hibernate.query.hql.spi.SemanticPathPart;
@ -103,9 +107,12 @@ import org.hibernate.query.sqm.tree.SqmJoinType;
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.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.AbstractSqmFrom;
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
import org.hibernate.query.sqm.tree.domain.SqmCteRoot;
import org.hibernate.query.sqm.tree.domain.SqmDerivedRoot;
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
@ -151,6 +158,7 @@ import org.hibernate.query.sqm.tree.expression.SqmTuple;
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
import org.hibernate.query.sqm.tree.from.SqmCteJoin;
import org.hibernate.query.sqm.tree.from.SqmDerivedJoin;
import org.hibernate.query.sqm.tree.from.SqmEntityJoin;
import org.hibernate.query.sqm.tree.from.SqmFrom;
@ -195,6 +203,8 @@ import org.hibernate.query.sqm.tree.select.SqmSortSpecification;
import org.hibernate.query.sqm.tree.select.SqmSubQuery;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
@ -301,6 +311,12 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
private ParameterCollector parameterCollector;
private ParameterStyle parameterStyle;
private boolean isExtractingJdbcTemporalType;
// Provides access to the current CTE that is being processed, which is potentially recursive
// This is necessary, so that the recursive query part of a CTE can access its own structure.
// Note that the structure is based on the non-recursive query part, so there is no cycle
private JpaCteCriteria<?> currentPotentialRecursiveCte;
public SemanticQueryBuilder(
Class<R> expectedResultType,
SqmCreationOptions creationOptions,
@ -617,10 +633,284 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Query spec
@Override
public Object visitWithClause(HqlParser.WithClauseContext ctx) {
if ( creationOptions.useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation(
StrictJpaComplianceViolation.Type.CTES
);
}
final List<ParseTree> children = ctx.children;
for ( int i = 1; i < children.size(); i += 2 ) {
visitCte( (HqlParser.CteContext) children.get( i ) );
}
return null;
}
@Override
public Object visitCte(HqlParser.CteContext ctx) {
final SqmCteContainer cteContainer = (SqmCteContainer) processingStateStack.getCurrent().getProcessingQuery();
final String name = visitIdentifier( (HqlParser.IdentifierContext) ctx.children.get( 0 ) );
final TerminalNode thirdChild = (TerminalNode) ctx.getChild( 2 );
final int queryExpressionIndex;
final CteMaterialization materialization;
switch ( thirdChild.getSymbol().getType() ) {
case HqlParser.NOT:
materialization = CteMaterialization.NOT_MATERIALIZED;
queryExpressionIndex = 5;
break;
case HqlParser.MATERIALIZED:
materialization = CteMaterialization.MATERIALIZED;
queryExpressionIndex = 4;
break;
default:
materialization = null;
queryExpressionIndex = 3;
break;
}
final HqlParser.QueryExpressionContext queryExpressionContext = (HqlParser.QueryExpressionContext) ctx.getChild( queryExpressionIndex );
final SqmSelectQuery<Object> cte;
if ( cteContainer instanceof SqmSubQuery<?> ) {
cte = new SqmSubQuery<>(
processingStateStack.getCurrent().getProcessingQuery(),
creationContext.getNodeBuilder()
);
}
else {
cte = new SqmSelectStatement<>( creationContext.getNodeBuilder() );
}
processingStateStack.push(
new SqmQueryPartCreationProcessingStateStandardImpl(
processingStateStack.getCurrent(),
cte,
this
)
);
final JpaCteCriteria<?> oldCte = currentPotentialRecursiveCte;
try {
currentPotentialRecursiveCte = null;
if ( queryExpressionContext instanceof HqlParser.SetQueryGroupContext ) {
final HqlParser.SetQueryGroupContext setContext = (HqlParser.SetQueryGroupContext) queryExpressionContext;
// A recursive query is only possible if the child count is lower than 5 e.g. `withClause? q1 op q2`
if ( setContext.getChildCount() < 5 ) {
final SetOperator setOperator = (SetOperator) setContext.getChild( setContext.getChildCount() - 2 )
.accept( this );
switch ( setOperator ) {
case UNION:
case UNION_ALL:
final HqlParser.OrderedQueryContext nonRecursiveQueryContext;
final HqlParser.OrderedQueryContext recursiveQueryContext;
// On count == 4, we have a withClause at index 0
if ( setContext.getChildCount() == 4 ) {
nonRecursiveQueryContext = (HqlParser.OrderedQueryContext) setContext.getChild( 1 );
recursiveQueryContext = (HqlParser.OrderedQueryContext) setContext.getChild( 3 );
}
else {
nonRecursiveQueryContext = (HqlParser.OrderedQueryContext) setContext.getChild( 0 );
recursiveQueryContext = (HqlParser.OrderedQueryContext) setContext.getChild( 2 );
}
// First visit the non-recursive part
nonRecursiveQueryContext.accept( this );
// Visiting the possibly recursive part must happen within the call to SqmCteContainer.with,
// because in there, the SqmCteStatement/JpaCteCriteria is available for use in the recursive part.
// The structure (SqmCteTable) for the SqmCteStatement is based on the non-recursive part,
// which is necessary to have, so that the SqmCteRoot/SqmCteJoin can resolve sub-paths.
final SqmSelectStatement<Object> recursivePart = new SqmSelectStatement<>( creationContext.getNodeBuilder() );
processingStateStack.pop();
processingStateStack.push(
new SqmQueryPartCreationProcessingStateStandardImpl(
processingStateStack.getCurrent(),
recursivePart,
this
)
);
final JpaCteCriteria<Object> cteDefinition;
if ( setOperator == SetOperator.UNION ) {
cteDefinition = cteContainer.withRecursiveUnionDistinct(
name,
cte,
cteCriteria -> {
currentPotentialRecursiveCte = cteCriteria;
recursiveQueryContext.accept( this );
return recursivePart;
}
);
}
else {
cteDefinition = cteContainer.withRecursiveUnionAll(
name,
cte,
cteCriteria -> {
currentPotentialRecursiveCte = cteCriteria;
recursiveQueryContext.accept( this );
return recursivePart;
}
);
}
if ( materialization != null ) {
cteDefinition.setMaterialization( materialization );
}
final ParseTree lastChild = ctx.getChild( ctx.getChildCount() - 1 );
final ParseTree potentialSearchClause;
if ( lastChild instanceof HqlParser.CycleClauseContext ) {
applyCycleClause( cteDefinition, (HqlParser.CycleClauseContext) lastChild );
potentialSearchClause = ctx.getChild( ctx.getChildCount() - 2 );
}
else {
potentialSearchClause = lastChild;
}
if ( potentialSearchClause instanceof HqlParser.SearchClauseContext ) {
applySearchClause( cteDefinition, (HqlParser.SearchClauseContext) potentialSearchClause );
}
return null;
}
}
}
queryExpressionContext.accept( this );
final JpaCteCriteria<Object> cteDefinition = cteContainer.with( name, cte );
if ( materialization != null ) {
cteDefinition.setMaterialization( materialization );
}
}
finally {
processingStateStack.pop();
currentPotentialRecursiveCte = oldCte;
}
return null;
}
private void applyCycleClause(JpaCteCriteria<?> cteDefinition, HqlParser.CycleClauseContext ctx) {
final HqlParser.CteAttributesContext attributesContext = (HqlParser.CteAttributesContext) ctx.getChild( 1 );
final String cycleMarkAttributeName = visitIdentifier( (HqlParser.IdentifierContext) ctx.getChild( 3 ) );
final List<JpaCteCriteriaAttribute> cycleAttributes = new ArrayList<>( ( attributesContext.getChildCount() + 1 ) >> 1 );
final List<ParseTree> children = attributesContext.children;
final JpaCteCriteriaType<?> type = cteDefinition.getType();
for ( int i = 0; i < children.size(); i += 2 ) {
final String attributeName = visitIdentifier( (HqlParser.IdentifierContext) children.get( i ) );
final JpaCteCriteriaAttribute attribute = type.getAttribute( attributeName );
if ( attribute == null ) {
throw new SemanticException(
String.format(
"Cycle attribute '%s' not found in the CTE %s",
attributeName,
cteDefinition.getName()
)
);
}
cycleAttributes.add( attribute );
}
final String cyclePathAttributeName;
final Object cycleValue;
final Object noCycleValue;
if ( ctx.getChildCount() > 4 ) {
if ( ctx.getChildCount() > 6 ) {
final SqmLiteral<?> cycleLiteral = (SqmLiteral<?>) visitLiteral( (HqlParser.LiteralContext) ctx.getChild( 5 ) );
final SqmLiteral<?> noCycleLiteral = (SqmLiteral<?>) visitLiteral( (HqlParser.LiteralContext) ctx.getChild( 7 ) );
cycleValue = cycleLiteral.getLiteralValue();
noCycleValue = noCycleLiteral.getLiteralValue();
}
else {
cycleValue = Boolean.TRUE;
noCycleValue = Boolean.FALSE;
}
final ParseTree lastChild = ctx.getChild( ctx.getChildCount() - 1 );
if ( lastChild instanceof HqlParser.IdentifierContext ) {
cyclePathAttributeName = visitIdentifier( (HqlParser.IdentifierContext) lastChild );
}
else {
cyclePathAttributeName = null;
}
}
else {
cyclePathAttributeName = null;
cycleValue = Boolean.TRUE;
noCycleValue = Boolean.FALSE;
}
cteDefinition.cycleUsing( cycleMarkAttributeName, cyclePathAttributeName, cycleValue, noCycleValue, cycleAttributes );
}
private void applySearchClause(JpaCteCriteria<?> cteDefinition, HqlParser.SearchClauseContext ctx) {
final CteSearchClauseKind kind;
if ( ( (TerminalNode) ctx.getChild( 1 ) ).getSymbol().getType() == HqlParser.BREADTH ) {
kind = CteSearchClauseKind.BREADTH_FIRST;
}
else {
kind = CteSearchClauseKind.DEPTH_FIRST;
}
final String searchAttributeName = visitIdentifier( (HqlParser.IdentifierContext) ctx.getChild( ctx.getChildCount() - 1 ) );
final HqlParser.SearchSpecificationsContext searchCtx = (HqlParser.SearchSpecificationsContext) ctx.getChild( 4 );
final List<JpaSearchOrder> searchOrders = new ArrayList<>( ( searchCtx.getChildCount() + 1 ) >> 1 );;
final List<ParseTree> children = searchCtx.children;
final JpaCteCriteriaType<?> type = cteDefinition.getType();
for ( int i = 0; i < children.size(); i += 2 ) {
final HqlParser.SearchSpecificationContext specCtx = (HqlParser.SearchSpecificationContext) children.get( i );
final String attributeName = visitIdentifier( (HqlParser.IdentifierContext) specCtx.getChild( 0 ) );
final JpaCteCriteriaAttribute attribute = type.getAttribute( attributeName );
if ( attribute == null ) {
throw new SemanticException(
String.format(
"Search attribute '%s' not found in the CTE %s",
attributeName,
cteDefinition.getName()
)
);
}
SortOrder sortOrder = SortOrder.ASCENDING;
NullPrecedence nullPrecedence = NullPrecedence.NONE;
int index = 1;
if ( index < specCtx.getChildCount() ) {
if ( specCtx.getChild( index ) instanceof HqlParser.SortDirectionContext ) {
final HqlParser.SortDirectionContext sortCtx = (HqlParser.SortDirectionContext) specCtx.getChild( index );
switch ( ( (TerminalNode) sortCtx.getChild( 0 ) ).getSymbol().getType() ) {
case HqlParser.ASC:
sortOrder = SortOrder.ASCENDING;
break;
case HqlParser.DESC:
sortOrder = SortOrder.DESCENDING;
break;
default:
throw new SemanticException( "Unrecognized sort ordering: " + sortCtx.getText() );
}
index++;
}
if ( index < specCtx.getChildCount() ) {
final HqlParser.NullsPrecedenceContext nullsPrecedenceContext = (HqlParser.NullsPrecedenceContext) specCtx.getChild( index );
switch ( ( (TerminalNode) nullsPrecedenceContext.getChild( 1 ) ).getSymbol().getType() ) {
case HqlParser.FIRST:
nullPrecedence = NullPrecedence.FIRST;
break;
case HqlParser.LAST:
nullPrecedence = NullPrecedence.LAST;
break;
default:
throw new SemanticException( "Unrecognized null precedence: " + nullsPrecedenceContext.getText() );
}
}
}
searchOrders.add(
creationContext.getNodeBuilder().search(
attribute,
sortOrder,
nullPrecedence
)
);
}
cteDefinition.search( kind, searchAttributeName, searchOrders );
}
@Override
public SqmQueryPart<Object> visitSimpleQueryGroup(HqlParser.SimpleQueryGroupContext ctx) {
final int lastChild = ctx.getChildCount() - 1;
if ( lastChild != 0 ) {
ctx.getChild( 0 ).accept( this );
}
//noinspection unchecked
return (SqmQueryPart<Object>) ctx.getChild( 0 ).accept( this );
return (SqmQueryPart<Object>) ctx.getChild( lastChild ).accept( this );
}
@Override
@ -654,14 +944,22 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override
public SqmQueryGroup<Object> visitSetQueryGroup(HqlParser.SetQueryGroupContext ctx) {
final List<ParseTree> children = ctx.children;
final int firstIndex;
if ( children.get( 0 ) instanceof HqlParser.WithClauseContext ) {
children.get( 0 ).accept( this );
firstIndex = 1;
}
else {
firstIndex = 0;
}
if ( creationOptions.useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation(
StrictJpaComplianceViolation.Type.SET_OPERATIONS
);
}
final List<ParseTree> children = ctx.children;
//noinspection unchecked
final SqmQueryPart<Object> firstQueryPart = (SqmQueryPart<Object>) children.get( 0 ).accept( this );
final SqmQueryPart<Object> firstQueryPart = (SqmQueryPart<Object>) children.get( firstIndex ).accept( this );
SqmQueryGroup<Object> queryGroup;
if ( firstQueryPart instanceof SqmQueryGroup<?>) {
queryGroup = (SqmQueryGroup<Object>) firstQueryPart;
@ -672,7 +970,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
setCurrentQueryPart( queryGroup );
final int size = children.size();
final SqmCreationProcessingState firstProcessingState = processingStateStack.pop();
for ( int i = 1; i < size; i += 2 ) {
for ( int i = firstIndex + 1; i < size; i += 2 ) {
final SetOperator operator = visitSetOperator( (HqlParser.SetOperatorContext) children.get( i ) );
final HqlParser.OrderedQueryContext simpleQueryCtx =
(HqlParser.OrderedQueryContext) children.get( i + 1 );
@ -1625,6 +1923,12 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
throw new SemanticException( "Could not resolve entity or correlation path '" + name + "'" );
}
final SqmCteStatement<?> cteStatement = findCteStatement( name );
if ( cteStatement != null ) {
final SqmCteRoot<?> root = new SqmCteRoot<>( cteStatement, alias );
pathRegistry.register( root );
return root;
}
throw new UnknownEntityException( "Could not resolve root entity '" + name + "'", name );
}
checkFQNEntityNameJpaComplianceViolationIfNeeded( name, entityDescriptor );
@ -1652,6 +1956,22 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
return sqmRoot;
}
@Override
public SqmCteStatement<?> findCteStatement(String name) {
if ( currentPotentialRecursiveCte != null && name.equals( currentPotentialRecursiveCte.getName() ) ) {
return (SqmCteStatement<?>) currentPotentialRecursiveCte;
}
return processingStateStack.findCurrentFirst(
state -> {
if ( state.getProcessingQuery() instanceof SqmCteContainer ) {
final SqmCteContainer container = (SqmCteContainer) state.getProcessingQuery();
return container.getCteStatement( name );
}
return null;
}
);
}
@Override
public SqmRoot<?> visitRootSubquery(HqlParser.RootSubqueryContext ctx) {
if ( getCreationOptions().useStrictJpaCompliance() ) {
@ -1867,7 +2187,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
final HqlParser.JoinRestrictionContext qualifiedJoinRestrictionContext = parserJoin.joinRestriction();
if ( join instanceof SqmEntityJoin<?> || join instanceof SqmDerivedJoin<?> ) {
if ( join instanceof SqmEntityJoin<?> || join instanceof SqmDerivedJoin<?> || join instanceof SqmCteJoin<?> ) {
sqmRoot.addSqmJoin( join );
}
else if ( join instanceof SqmAttributeJoin<?, ?> ) {
@ -2165,7 +2485,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
final Enum<?> enumValue;
if ( possibleEnumValues != null && ( enumValue = possibleEnumValues.get( enumType ) ) != null ) {
DotIdentifierConsumer dotIdentifierConsumer = dotIdentifierConsumerStack.getCurrent();
dotIdentifierConsumer.consumeIdentifier( enumValue.getClass().getCanonicalName(), true, false );
dotIdentifierConsumer.consumeIdentifier( enumValue.getClass().getName(), true, false );
dotIdentifierConsumer.consumeIdentifier( enumValue.name(), false, true );
return (SqmExpression<?>) dotIdentifierConsumerStack.getCurrent().getConsumedPart();
}
@ -3940,8 +4260,6 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
}
private boolean isExtractingJdbcTemporalType;
@Override
public Object visitExtractFunction(HqlParser.ExtractFunctionContext ctx) {
final SqmExpression<?> expressionToExtract = (SqmExpression<?>) ctx.getChild( ctx.getChildCount() - 2 )

View File

@ -9,6 +9,7 @@ package org.hibernate.query.hql.spi;
import org.hibernate.Incubating;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.sqm.spi.SqmCreationContext;
import org.hibernate.query.sqm.tree.cte.SqmCteStatement;
/**
* Models the state pertaining to the creation of a single SQM.
@ -39,4 +40,6 @@ public interface SqmCreationState {
default SqmCreationProcessingState getCurrentProcessingState() {
return getProcessingStateStack().getCurrent();
}
SqmCteStatement<?> findCteStatement(String name);
}

Some files were not shown because too many files have changed in this diff Show More