HHH-15328 Add support for CTE WITH clause
This commit is contained in:
parent
215d411ffa
commit
90a752a0ee
|
@ -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.
|
||||
====
|
|
@ -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)?
|
||||
;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ...)`
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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( ')' );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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() ) {
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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() );
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() )
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -474,6 +474,11 @@ public class CockroachDialect extends Dialect {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRecursiveCTE() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNoColumnsInsertString() {
|
||||
return "default values";
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -128,6 +128,11 @@ public class DB2iDialect extends DB2Dialect {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRecursiveCTE() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
|
||||
return new StandardSqlAstTranslatorFactory() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)}?
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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( ')' );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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() ) {
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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( '\'' );
|
||||
}
|
||||
|
||||
}
|
|
@ -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 ) {
|
||||
|
|
|
@ -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?
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue