Skip to content

Commit faf80dc

Browse files
committed
Rework multi-table handling and non-select query plan code to allow caching and help Hibernate Reactive
1 parent f8fde28 commit faf80dc

File tree

49 files changed

+4785
-3819
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+4785
-3819
lines changed

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,9 +1558,9 @@ else if ( modelPart instanceof VirtualModelPart ) {
15581558
}
15591559

15601560
public static Expression buildColumnReferenceExpression(
1561-
TableGroup tableGroup,
1561+
@Nullable TableGroup tableGroup,
15621562
ModelPart modelPart,
1563-
SqlExpressionResolver sqlExpressionResolver,
1563+
@Nullable SqlExpressionResolver sqlExpressionResolver,
15641564
SessionFactoryImplementor sessionFactory) {
15651565
final int jdbcTypeCount = modelPart.getJdbcTypeCount();
15661566
if ( modelPart instanceof EmbeddableValuedModelPart ) {
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.query.sqm.internal;
6+
7+
import org.checkerframework.checker.nullness.qual.Nullable;
8+
import org.hibernate.action.internal.BulkOperationCleanupAction;
9+
import org.hibernate.query.spi.DomainQueryExecutionContext;
10+
import org.hibernate.query.spi.NonSelectQueryPlan;
11+
import org.hibernate.query.sqm.mutation.spi.MultiTableHandler;
12+
import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult;
13+
import org.hibernate.query.sqm.tree.SqmDmlStatement;
14+
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
15+
16+
17+
/**
18+
* @since 7.1
19+
*/
20+
public abstract class AbstractMultiTableMutationQueryPlan<S extends SqmDmlStatement<?>, F> implements NonSelectQueryPlan {
21+
private final S statement;
22+
private final DomainParameterXref domainParameterXref;
23+
private final F strategy;
24+
25+
private volatile MultiTableHandler handler;
26+
27+
public AbstractMultiTableMutationQueryPlan(S statement, DomainParameterXref domainParameterXref, F strategy) {
28+
this.statement = statement;
29+
this.domainParameterXref = domainParameterXref;
30+
this.strategy = strategy;
31+
}
32+
33+
protected abstract MultiTableHandlerBuildResult buildHandler(
34+
S statement,
35+
DomainParameterXref domainParameterXref,
36+
F strategy,
37+
DomainQueryExecutionContext context);
38+
39+
@Override
40+
public int executeUpdate(DomainQueryExecutionContext context) {
41+
BulkOperationCleanupAction.schedule( context.getSession(), statement );
42+
final Interpretation interpretation = getInterpretation( context );
43+
return interpretation.handler().execute( interpretation.jdbcParameterBindings(), context );
44+
}
45+
46+
// For Hibernate Reactive
47+
protected Interpretation getInterpretation(DomainQueryExecutionContext context) {
48+
Interpretation builtInterpretation = null;
49+
MultiTableHandler localCopy = handler;
50+
51+
if ( localCopy == null ) {
52+
synchronized (this) {
53+
localCopy = handler;
54+
if ( localCopy == null ) {
55+
final MultiTableHandlerBuildResult buildResult = buildHandler(
56+
statement,
57+
domainParameterXref,
58+
strategy,
59+
context
60+
);
61+
builtInterpretation = new Interpretation(
62+
buildResult.multiTableHandler(),
63+
buildResult.firstJdbcParameterBindings()
64+
);
65+
localCopy = buildResult.multiTableHandler();
66+
handler = localCopy;
67+
}
68+
else {
69+
builtInterpretation = updateInterpretation( localCopy, context );
70+
}
71+
}
72+
}
73+
else {
74+
builtInterpretation = updateInterpretation( localCopy, context );
75+
}
76+
return builtInterpretation != null ? builtInterpretation
77+
: new Interpretation( localCopy, localCopy.createJdbcParameterBindings( context ) );
78+
}
79+
80+
private @Nullable Interpretation updateInterpretation(
81+
MultiTableHandler localCopy,
82+
DomainQueryExecutionContext context) {
83+
Interpretation builtInterpretation = null;
84+
85+
// If the translation depends on parameter bindings or it isn't compatible with the current query options,
86+
// we have to rebuild the JdbcSelect, which is still better than having to translate from SQM to SQL AST again
87+
if ( localCopy.dependsOnParameterBindings() ) {
88+
final JdbcParameterBindings jdbcParameterBindings = localCopy.createJdbcParameterBindings( context );
89+
// If the translation depends on the limit or lock options, we have to rebuild the JdbcSelect
90+
// We could avoid this by putting the lock options into the cache key
91+
if ( !localCopy.isCompatibleWith( jdbcParameterBindings, context.getQueryOptions() ) ) {
92+
final MultiTableHandlerBuildResult buildResult = buildHandler(
93+
statement,
94+
domainParameterXref,
95+
strategy,
96+
context
97+
);
98+
localCopy = buildResult.multiTableHandler();
99+
builtInterpretation = new Interpretation(
100+
buildResult.multiTableHandler(),
101+
buildResult.firstJdbcParameterBindings()
102+
);
103+
handler = localCopy;
104+
}
105+
else {
106+
builtInterpretation = new Interpretation( localCopy, jdbcParameterBindings );
107+
}
108+
}
109+
return builtInterpretation;
110+
}
111+
112+
// For Hibernate Reactive
113+
protected record Interpretation(
114+
MultiTableHandler handler,
115+
JdbcParameterBindings jdbcParameterBindings
116+
) {}
117+
118+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.query.sqm.internal;
6+
7+
import org.hibernate.metamodel.mapping.MappingModelExpressible;
8+
import org.hibernate.query.spi.QueryParameterImplementor;
9+
import org.hibernate.query.sqm.tree.expression.SqmParameter;
10+
import org.hibernate.sql.ast.tree.Statement;
11+
import org.hibernate.sql.exec.spi.JdbcOperationQuery;
12+
import org.hibernate.sql.exec.spi.JdbcParametersList;
13+
14+
import java.util.List;
15+
import java.util.Map;
16+
17+
/**
18+
* @since 7.1
19+
*/
20+
public record CacheableSqmInterpretation<S extends Statement, J extends JdbcOperationQuery>(
21+
S statement,
22+
J jdbcOperation,
23+
Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> jdbcParamsXref,
24+
Map<SqmParameter<?>, MappingModelExpressible<?>> sqmParameterMappingModelTypes) {
25+
26+
}

hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java

Lines changed: 36 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
import org.hibernate.engine.spi.SharedSessionContractImplementor;
1919
import org.hibernate.engine.spi.SubselectFetch;
2020
import org.hibernate.internal.EmptyScrollableResults;
21+
import org.hibernate.internal.util.MutableObject;
2122
import org.hibernate.metamodel.mapping.MappingModelExpressible;
2223
import org.hibernate.query.QueryTypeMismatchException;
2324
import org.hibernate.query.TupleTransformer;
2425
import org.hibernate.query.spi.DomainQueryExecutionContext;
2526
import org.hibernate.query.spi.QueryOptions;
26-
import org.hibernate.query.spi.QueryParameterImplementor;
2727
import org.hibernate.query.spi.ScrollableResultsImplementor;
2828
import org.hibernate.query.spi.SelectQueryPlan;
2929
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
@@ -79,7 +79,7 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
7979
private final SqmInterpreter<List<R>, Void> listInterpreter;
8080
private final SqmInterpreter<ScrollableResultsImplementor<R>, ScrollMode> scrollInterpreter;
8181

82-
private volatile CacheableSqmInterpretation cacheableSqmInterpretation;
82+
private volatile CacheableSqmInterpretation<SelectStatement, JdbcOperationQuerySelect> cacheableSqmInterpretation;
8383

8484
public ConcreteSqmSelectQueryPlan(
8585
SqmSelectStatement<?> sqm,
@@ -97,16 +97,16 @@ public ConcreteSqmSelectQueryPlan(
9797
: ListResultsConsumer.UniqueSemantic.ALLOW;
9898
this.executeQueryInterpreter = (resultsConsumer, executionContext, sqmInterpretation, jdbcParameterBindings) -> {
9999
final SharedSessionContractImplementor session = executionContext.getSession();
100-
final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect();
100+
final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.jdbcOperation();
101101
try {
102102
final SubselectFetch.RegistrationHandler subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler(
103103
session.getPersistenceContext().getBatchFetchQueue(),
104-
sqmInterpretation.selectStatement,
104+
sqmInterpretation.statement(),
105105
JdbcParametersList.empty(),
106106
jdbcParameterBindings
107107
);
108108
session.autoFlushIfRequired( jdbcSelect.getAffectedTableNames(), true );
109-
final Expression fetchExpression = sqmInterpretation.selectStatement.getQueryPart()
109+
final Expression fetchExpression = sqmInterpretation.statement().getQueryPart()
110110
.getFetchClauseExpression();
111111
final int resultCountEstimate = fetchExpression != null
112112
? interpretIntExpression( fetchExpression, jdbcParameterBindings )
@@ -127,17 +127,17 @@ public ConcreteSqmSelectQueryPlan(
127127
};
128128
this.listInterpreter = (unused, executionContext, sqmInterpretation, jdbcParameterBindings) -> {
129129
final SharedSessionContractImplementor session = executionContext.getSession();
130-
final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect();
130+
final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.jdbcOperation();
131131
try {
132132
final SubselectFetch.RegistrationHandler subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler(
133133
session.getPersistenceContext().getBatchFetchQueue(),
134-
sqmInterpretation.selectStatement,
134+
sqmInterpretation.statement(),
135135
JdbcParametersList.empty(),
136136
jdbcParameterBindings
137137
);
138138
session.autoFlushIfRequired( jdbcSelect.getAffectedTableNames(), true );
139139
final Expression fetchExpression =
140-
sqmInterpretation.selectStatement.getQueryPart()
140+
sqmInterpretation.statement().getQueryPart()
141141
.getFetchClauseExpression();
142142
final int resultCountEstimate = fetchExpression != null
143143
? interpretIntExpression( fetchExpression, jdbcParameterBindings )
@@ -160,7 +160,7 @@ public ConcreteSqmSelectQueryPlan(
160160

161161
this.scrollInterpreter = (scrollMode, executionContext, sqmInterpretation, jdbcParameterBindings) -> {
162162
final SharedSessionContractImplementor session = executionContext.getSession();
163-
final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect();
163+
final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.jdbcOperation();
164164
try {
165165
// final SubselectFetch.RegistrationHandler subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler(
166166
// executionContext.getSession().getPersistenceContext().getBatchFetchQueue(),
@@ -173,7 +173,7 @@ public ConcreteSqmSelectQueryPlan(
173173
session.getFactory().getJdbcServices().getJdbcSelectExecutor();
174174
session.autoFlushIfRequired( jdbcSelect.getAffectedTableNames(), true );
175175
final Expression fetchExpression =
176-
sqmInterpretation.selectStatement.getQueryPart()
176+
sqmInterpretation.statement().getQueryPart()
177177
.getFetchClauseExpression();
178178
final int resultCountEstimate = fetchExpression != null
179179
? interpretIntExpression( fetchExpression, jdbcParameterBindings )
@@ -401,7 +401,7 @@ private <T, X> T withCacheableSqmInterpretation(DomainQueryExecutionContext exec
401401
// to protect access. However, synchronized is much simpler here. We will verify
402402
// during throughput testing whether this is an issue and consider changes then
403403

404-
CacheableSqmInterpretation localCopy = cacheableSqmInterpretation;
404+
CacheableSqmInterpretation<SelectStatement, JdbcOperationQuerySelect> localCopy = cacheableSqmInterpretation;
405405
JdbcParameterBindings jdbcParameterBindings = null;
406406

407407
executionContext.getSession().autoPreFlush();
@@ -410,23 +410,23 @@ private <T, X> T withCacheableSqmInterpretation(DomainQueryExecutionContext exec
410410
synchronized ( this ) {
411411
localCopy = cacheableSqmInterpretation;
412412
if ( localCopy == null ) {
413-
localCopy = buildCacheableSqmInterpretation( sqm, domainParameterXref, executionContext );
414-
jdbcParameterBindings = localCopy.firstParameterBindings;
415-
localCopy.firstParameterBindings = null;
413+
final MutableObject<JdbcParameterBindings> mutableValue = new MutableObject<>();
414+
localCopy = buildInterpretation( sqm, domainParameterXref, executionContext, mutableValue );
415+
jdbcParameterBindings = mutableValue.get();
416416
cacheableSqmInterpretation = localCopy;
417417
}
418418
else {
419419
// If the translation depends on parameter bindings or it isn't compatible with the current query options,
420420
// we have to rebuild the JdbcSelect, which is still better than having to translate from SQM to SQL AST again
421-
if ( localCopy.jdbcSelect.dependsOnParameterBindings() ) {
421+
if ( localCopy.jdbcOperation().dependsOnParameterBindings() ) {
422422
jdbcParameterBindings = createJdbcParameterBindings( localCopy, executionContext );
423423
}
424424
// If the translation depends on the limit or lock options, we have to rebuild the JdbcSelect
425425
// We could avoid this by putting the lock options into the cache key
426-
if ( !localCopy.jdbcSelect.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) {
427-
localCopy = buildCacheableSqmInterpretation( sqm, domainParameterXref, executionContext );
428-
jdbcParameterBindings = localCopy.firstParameterBindings;
429-
localCopy.firstParameterBindings = null;
426+
if ( !localCopy.jdbcOperation().isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) {
427+
final MutableObject<JdbcParameterBindings> mutableValue = new MutableObject<>();
428+
localCopy = buildInterpretation( sqm, domainParameterXref, executionContext, mutableValue );
429+
jdbcParameterBindings = mutableValue.get();
430430
cacheableSqmInterpretation = localCopy;
431431
}
432432
}
@@ -435,15 +435,15 @@ private <T, X> T withCacheableSqmInterpretation(DomainQueryExecutionContext exec
435435
else {
436436
// If the translation depends on parameter bindings or it isn't compatible with the current query options,
437437
// we have to rebuild the JdbcSelect, which is still better than having to translate from SQM to SQL AST again
438-
if ( localCopy.jdbcSelect.dependsOnParameterBindings() ) {
438+
if ( localCopy.jdbcOperation().dependsOnParameterBindings() ) {
439439
jdbcParameterBindings = createJdbcParameterBindings( localCopy, executionContext );
440440
}
441441
// If the translation depends on the limit or lock options, we have to rebuild the JdbcSelect
442442
// We could avoid this by putting the lock options into the cache key
443-
if ( !localCopy.jdbcSelect.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) {
444-
localCopy = buildCacheableSqmInterpretation( sqm, domainParameterXref, executionContext );
445-
jdbcParameterBindings = localCopy.firstParameterBindings;
446-
localCopy.firstParameterBindings = null;
443+
if ( !localCopy.jdbcOperation().isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) {
444+
final MutableObject<JdbcParameterBindings> mutableValue = new MutableObject<>();
445+
localCopy = buildInterpretation( sqm, domainParameterXref, executionContext, mutableValue );
446+
jdbcParameterBindings = mutableValue.get();
447447
cacheableSqmInterpretation = localCopy;
448448
}
449449
}
@@ -455,26 +455,29 @@ private <T, X> T withCacheableSqmInterpretation(DomainQueryExecutionContext exec
455455
return interpreter.interpret( context, executionContext, localCopy, jdbcParameterBindings );
456456
}
457457

458-
private JdbcParameterBindings createJdbcParameterBindings(CacheableSqmInterpretation sqmInterpretation, DomainQueryExecutionContext executionContext) {
458+
// For Hibernate Reactive
459+
protected JdbcParameterBindings createJdbcParameterBindings(CacheableSqmInterpretation<SelectStatement, JdbcOperationQuerySelect> sqmInterpretation, DomainQueryExecutionContext executionContext) {
459460
return SqmUtil.createJdbcParameterBindings(
460461
executionContext.getQueryParameterBindings(),
461462
domainParameterXref,
462-
sqmInterpretation.getJdbcParamsXref(),
463+
sqmInterpretation.jdbcParamsXref(),
463464
new SqmParameterMappingModelResolutionAccess() {
464465
//this is pretty ugly!
465466
@Override @SuppressWarnings("unchecked")
466467
public <T> MappingModelExpressible<T> getResolvedMappingModelType(SqmParameter<T> parameter) {
467-
return (MappingModelExpressible<T>) sqmInterpretation.getSqmParameterMappingModelTypes().get(parameter);
468+
return (MappingModelExpressible<T>) sqmInterpretation.sqmParameterMappingModelTypes().get(parameter);
468469
}
469470
},
470471
executionContext.getSession()
471472
);
472473
}
473474

474-
private static CacheableSqmInterpretation buildCacheableSqmInterpretation(
475+
// For Hibernate Reactive
476+
protected static CacheableSqmInterpretation<SelectStatement, JdbcOperationQuerySelect> buildInterpretation(
475477
SqmSelectStatement<?> sqm,
476478
DomainParameterXref domainParameterXref,
477-
DomainQueryExecutionContext executionContext) {
479+
DomainQueryExecutionContext executionContext,
480+
MutableObject<JdbcParameterBindings> firstJdbcParameterBindingsConsumer) {
478481
final SharedSessionContractImplementor session = executionContext.getSession();
479482
final SessionFactoryImplementor sessionFactory = session.getFactory();
480483

@@ -511,57 +514,23 @@ public <T> MappingModelExpressible<T> getResolvedMappingModelType(SqmParameter<T
511514
},
512515
session
513516
);
514-
515-
return new CacheableSqmInterpretation(
517+
firstJdbcParameterBindingsConsumer.set( jdbcParameterBindings );
518+
return new CacheableSqmInterpretation<>(
516519
sqmInterpretation.getSqlAst(),
517520
selectTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ),
518521
jdbcParamsXref,
519-
sqmInterpretation.getSqmParameterMappingModelTypeResolutions(),
520-
jdbcParameterBindings
522+
sqmInterpretation.getSqmParameterMappingModelTypeResolutions()
521523
);
522524
}
523525

524526
private interface SqmInterpreter<T, X> {
525527
T interpret(
526528
X context,
527529
DomainQueryExecutionContext executionContext,
528-
CacheableSqmInterpretation sqmInterpretation,
530+
CacheableSqmInterpretation<SelectStatement, JdbcOperationQuerySelect> sqmInterpretation,
529531
JdbcParameterBindings jdbcParameterBindings);
530532
}
531533

532-
private static class CacheableSqmInterpretation {
533-
private final SelectStatement selectStatement;
534-
private final JdbcOperationQuerySelect jdbcSelect;
535-
private final Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> jdbcParamsXref;
536-
private final Map<SqmParameter<?>, MappingModelExpressible<?>> sqmParameterMappingModelTypes;
537-
private transient JdbcParameterBindings firstParameterBindings;
538-
539-
CacheableSqmInterpretation(
540-
SelectStatement selectStatement,
541-
JdbcOperationQuerySelect jdbcSelect,
542-
Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> jdbcParamsXref,
543-
Map<SqmParameter<?>, MappingModelExpressible<?>> sqmParameterMappingModelTypes,
544-
JdbcParameterBindings firstParameterBindings) {
545-
this.selectStatement = selectStatement;
546-
this.jdbcSelect = jdbcSelect;
547-
this.jdbcParamsXref = jdbcParamsXref;
548-
this.sqmParameterMappingModelTypes = sqmParameterMappingModelTypes;
549-
this.firstParameterBindings = firstParameterBindings;
550-
}
551-
552-
JdbcOperationQuerySelect getJdbcSelect() {
553-
return jdbcSelect;
554-
}
555-
556-
Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> getJdbcParamsXref() {
557-
return jdbcParamsXref;
558-
}
559-
560-
public Map<SqmParameter<?>, MappingModelExpressible<?>> getSqmParameterMappingModelTypes() {
561-
return sqmParameterMappingModelTypes;
562-
}
563-
}
564-
565534
private static class MySqmJdbcExecutionContextAdapter extends SqmJdbcExecutionContextAdapter {
566535
private final SubselectFetch.RegistrationHandler subSelectFetchKeyHandler;
567536
private final String hql;

0 commit comments

Comments
 (0)