Skip to content

Commit 859df47

Browse files
blafondDavideD
authored andcommitted
[#1768] Add upsert support for non-PG DBs
1 parent 1c08fc7 commit 859df47

12 files changed

+844
-9
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/* Hibernate, Relational Persistence for Idiomatic Java
2+
*
3+
* SPDX-License-Identifier: Apache-2.0
4+
* Copyright: Red Hat Inc. and Hibernate Authors
5+
*/
6+
package org.hibernate.reactive.dialect;
7+
8+
import org.hibernate.dialect.Dialect;
9+
import org.hibernate.dialect.DialectDelegateWrapper;
10+
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
11+
import org.hibernate.dialect.OracleSqlAstTranslator;
12+
import org.hibernate.engine.spi.SessionFactoryImplementor;
13+
import org.hibernate.persister.entity.mutation.EntityTableMapping;
14+
import org.hibernate.reactive.sql.model.ReactiveDeleteOrUpsertOperation;
15+
import org.hibernate.sql.ast.Clause;
16+
import org.hibernate.sql.ast.tree.Statement;
17+
import org.hibernate.sql.exec.spi.JdbcOperation;
18+
import org.hibernate.sql.model.MutationOperation;
19+
import org.hibernate.sql.model.internal.OptionalTableUpdate;
20+
import org.hibernate.sql.model.jdbc.UpsertOperation;
21+
22+
public class ReactiveOracleSqlAstTranslator<T extends JdbcOperation> extends OracleSqlAstTranslator<T> {
23+
public ReactiveOracleSqlAstTranslator(
24+
SessionFactoryImplementor sessionFactory,
25+
Statement statement) {
26+
super( sessionFactory, statement );
27+
}
28+
29+
@Override
30+
public MutationOperation createMergeOperation(OptionalTableUpdate optionalTableUpdate) {
31+
renderUpsertStatement( optionalTableUpdate );
32+
33+
final UpsertOperation upsertOperation = new UpsertOperation(
34+
optionalTableUpdate.getMutatingTable().getTableMapping(),
35+
optionalTableUpdate.getMutationTarget(),
36+
getSql(),
37+
getParameterBinders()
38+
);
39+
40+
return new ReactiveDeleteOrUpsertOperation(
41+
optionalTableUpdate.getMutationTarget(),
42+
(EntityTableMapping) optionalTableUpdate.getMutatingTable().getTableMapping(),
43+
upsertOperation,
44+
optionalTableUpdate
45+
);
46+
}
47+
48+
public MutationOperation createReactiveMergeOperation(OptionalTableUpdate optionalTableUpdate) {
49+
renderUpsertStatement( optionalTableUpdate );
50+
51+
final UpsertOperation upsertOperation = new UpsertOperation(
52+
optionalTableUpdate.getMutatingTable().getTableMapping(),
53+
optionalTableUpdate.getMutationTarget(),
54+
getSql(),
55+
getParameterBinders()
56+
);
57+
58+
return new ReactiveDeleteOrUpsertOperation(
59+
optionalTableUpdate.getMutationTarget(),
60+
(EntityTableMapping) optionalTableUpdate.getMutatingTable().getTableMapping(),
61+
upsertOperation,
62+
optionalTableUpdate
63+
);
64+
}
65+
66+
private void renderUpsertStatement(OptionalTableUpdate optionalTableUpdate) {
67+
// template:
68+
//
69+
// merge into [table] as t
70+
// using values([bindings]) as s ([column-names])
71+
// on t.[key] = s.[key]
72+
// when not matched
73+
// then insert ...
74+
// when matched
75+
// then update ...
76+
77+
renderMergeInto( optionalTableUpdate );
78+
appendSql( " " );
79+
renderMergeUsing( optionalTableUpdate );
80+
appendSql( " " );
81+
renderMergeOn( optionalTableUpdate );
82+
appendSql( " " );
83+
renderMergeInsert( optionalTableUpdate );
84+
appendSql( " " );
85+
renderMergeUpdate( optionalTableUpdate );
86+
}
87+
88+
@Override
89+
protected boolean rendersTableReferenceAlias(Clause clause) {
90+
// todo (6.0) : For now we just skip the alias rendering in the delete and update clauses
91+
// We need some dialect support if we want to support joins in delete and update statements
92+
switch ( clause ) {
93+
case DELETE:
94+
case UPDATE: {
95+
final Dialect realDialect = DialectDelegateWrapper.extractRealDialect( getDialect() );
96+
return realDialect.getDmlTargetColumnQualifierSupport() == DmlTargetColumnQualifierSupport.TABLE_ALIAS;
97+
}
98+
}
99+
return true;
100+
}
101+
}

hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/dialect/internal/ReactiveStandardDialectResolver.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,19 @@
88
import org.hibernate.dialect.CockroachDialect;
99
import org.hibernate.dialect.Database;
1010
import org.hibernate.dialect.Dialect;
11+
import org.hibernate.dialect.DialectDelegateWrapper;
1112
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
1213
import org.hibernate.engine.jdbc.dialect.spi.DialectResolver;
14+
import org.hibernate.engine.spi.SessionFactoryImplementor;
15+
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
16+
import org.hibernate.reactive.dialect.ReactiveOracleSqlAstTranslator;
17+
import org.hibernate.sql.ast.SqlAstTranslator;
18+
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
19+
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
20+
import org.hibernate.sql.ast.tree.Statement;
21+
import org.hibernate.sql.exec.spi.JdbcOperation;
22+
import org.hibernate.sql.model.MutationOperation;
23+
import org.hibernate.sql.model.internal.OptionalTableUpdate;
1324

1425
import static org.hibernate.dialect.CockroachDialect.parseVersion;
1526

@@ -25,7 +36,34 @@ public Dialect resolveDialect(DialectResolutionInfo info) {
2536

2637
for ( Database database : Database.values() ) {
2738
if ( database.matchesResolutionInfo( info ) ) {
28-
return database.createDialect( info );
39+
Dialect dialect = database.createDialect( info );
40+
if ( info.getDatabaseName().toUpperCase().startsWith( "ORACLE" ) ) {
41+
return new DialectDelegateWrapper( dialect ) {
42+
@Override
43+
public MutationOperation createOptionalTableUpdateOperation(
44+
EntityMutationTarget mutationTarget,
45+
OptionalTableUpdate optionalTableUpdate,
46+
SessionFactoryImplementor factory) {
47+
final ReactiveOracleSqlAstTranslator<?> translator = new ReactiveOracleSqlAstTranslator<>(
48+
factory,
49+
optionalTableUpdate
50+
);
51+
return translator.createReactiveMergeOperation( optionalTableUpdate );
52+
}
53+
54+
@Override
55+
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
56+
return new StandardSqlAstTranslatorFactory() {
57+
@Override
58+
protected <T extends JdbcOperation> SqlAstTranslator<T> buildTranslator(
59+
SessionFactoryImplementor sessionFactory, Statement statement) {
60+
return new ReactiveOracleSqlAstTranslator<>( sessionFactory, statement );
61+
}
62+
};
63+
}
64+
};
65+
}
66+
return dialect;
2967
}
3068
}
3169

hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorSingleSelfExecuting.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,53 @@
55
*/
66
package org.hibernate.reactive.engine.jdbc.mutation.internal;
77

8+
9+
import java.lang.invoke.MethodHandles;
10+
import java.util.concurrent.CompletionStage;
11+
12+
import org.hibernate.engine.jdbc.mutation.TableInclusionChecker;
813
import org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleSelfExecuting;
914
import org.hibernate.engine.spi.SharedSessionContractImplementor;
1015
import org.hibernate.reactive.engine.jdbc.env.internal.ReactiveMutationExecutor;
16+
import org.hibernate.reactive.logging.impl.Log;
17+
import org.hibernate.reactive.logging.impl.LoggerFactory;
18+
import org.hibernate.reactive.sql.model.ReactiveSelfExecutingUpdateOperation;
1119
import org.hibernate.sql.model.SelfExecutingUpdateOperation;
20+
import org.hibernate.sql.model.ValuesAnalysis;
21+
22+
import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture;
1223

1324
public class ReactiveMutationExecutorSingleSelfExecuting extends MutationExecutorSingleSelfExecuting
1425
implements ReactiveMutationExecutor {
1526

16-
public ReactiveMutationExecutorSingleSelfExecuting(SelfExecutingUpdateOperation operation, SharedSessionContractImplementor session) {
27+
private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() );
28+
29+
private final SelfExecutingUpdateOperation operation;
30+
31+
public ReactiveMutationExecutorSingleSelfExecuting(
32+
SelfExecutingUpdateOperation operation,
33+
SharedSessionContractImplementor session) {
1734
super( operation, session );
35+
this.operation = operation;
36+
}
37+
38+
@Override
39+
protected void performSelfExecutingOperations(
40+
ValuesAnalysis valuesAnalysis,
41+
TableInclusionChecker inclusionChecker,
42+
SharedSessionContractImplementor session) {
43+
throw LOG.nonReactiveMethodCall( "performReactiveSelfExecutingOperation" );
44+
}
45+
46+
@Override
47+
public CompletionStage<Void> performReactiveSelfExecutingOperations(
48+
ValuesAnalysis valuesAnalysis,
49+
TableInclusionChecker inclusionChecker,
50+
SharedSessionContractImplementor session) {
51+
if ( inclusionChecker.include( operation.getTableDetails() ) && operation instanceof ReactiveSelfExecutingUpdateOperation ) {
52+
return ( (ReactiveSelfExecutingUpdateOperation) operation )
53+
.performReactiveMutation( getJdbcValueBindings(), valuesAnalysis, session );
54+
}
55+
return voidFuture();
1856
}
1957
}

hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveAbstractReturningDelegate.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.hibernate.dialect.CockroachDialect;
1212
import org.hibernate.dialect.DB2Dialect;
1313
import org.hibernate.dialect.Dialect;
14+
import org.hibernate.dialect.DialectDelegateWrapper;
1415
import org.hibernate.dialect.MySQLDialect;
1516
import org.hibernate.dialect.OracleDialect;
1617
import org.hibernate.dialect.SQLServerDialect;
@@ -69,15 +70,16 @@ && getPersister().getFactory().getJdbcServices().getDialect() instanceof Cockroa
6970

7071
private static String createInsert(PreparedStatementDetails insertStatementDetails, String identifierColumnName, Dialect dialect) {
7172
final String sqlEnd = " returning " + identifierColumnName;
72-
if ( dialect instanceof MySQLDialect ) {
73+
Dialect realDialect = DialectDelegateWrapper.extractRealDialect( dialect );
74+
if ( realDialect instanceof MySQLDialect ) {
7375
// For some reasons ORM generates a query with an invalid syntax
7476
String sql = insertStatementDetails.getSqlString();
7577
int index = sql.lastIndexOf( sqlEnd );
7678
return index > -1
7779
? sql.substring( 0, index )
7880
: sql;
7981
}
80-
if ( dialect instanceof SQLServerDialect ) {
82+
if ( realDialect instanceof SQLServerDialect ) {
8183
String sql = insertStatementDetails.getSqlString();
8284
int index = sql.lastIndexOf( sqlEnd );
8385
// FIXME: this is a hack for HHH-16365
@@ -94,12 +96,12 @@ private static String createInsert(PreparedStatementDetails insertStatementDetai
9496
}
9597
return sql;
9698
}
97-
if ( dialect instanceof DB2Dialect ) {
99+
if ( realDialect instanceof DB2Dialect ) {
98100
// ORM query: select id from new table ( insert into IntegerTypeEntity values ( ))
99101
// Correct : select id from new table ( insert into LongTypeEntity (id) values (default))
100102
return insertStatementDetails.getSqlString().replace( " values ( ))", " (" + identifierColumnName + ") values (default))" );
101103
}
102-
if ( dialect instanceof OracleDialect ) {
104+
if ( realDialect instanceof OracleDialect ) {
103105
final String valuesStr = " values ( )";
104106
String sql = insertStatementDetails.getSqlString();
105107
int index = sql.lastIndexOf( sqlEnd );

hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveMergeCoordinatorStandardScopeFactory.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,25 @@
55
*/
66
package org.hibernate.reactive.persister.entity.impl;
77

8+
9+
import org.hibernate.dialect.OracleDialect;
810
import org.hibernate.engine.spi.SessionFactoryImplementor;
911
import org.hibernate.persister.entity.AbstractEntityPersister;
1012
import org.hibernate.persister.entity.mutation.MergeCoordinator;
1113
import org.hibernate.reactive.persister.entity.mutation.ReactiveMergeCoordinator;
1214
import org.hibernate.reactive.persister.entity.mutation.ReactiveScopedUpdateCoordinator;
1315
import org.hibernate.reactive.persister.entity.mutation.ReactiveUpdateCoordinator;
16+
import org.hibernate.reactive.sql.model.ReactiveOptionalTableUpdateOperation;
17+
import org.hibernate.sql.model.ModelMutationLogging;
18+
import org.hibernate.sql.model.MutationOperation;
19+
import org.hibernate.sql.model.MutationOperationGroup;
20+
import org.hibernate.sql.model.ValuesAnalysis;
21+
import org.hibernate.sql.model.ast.MutationGroup;
22+
import org.hibernate.sql.model.ast.TableMutation;
23+
import org.hibernate.sql.model.internal.MutationOperationGroupFactory;
24+
import org.hibernate.sql.model.internal.OptionalTableUpdate;
25+
import org.hibernate.sql.model.jdbc.DeleteOrUpsertOperation;
26+
import org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation;
1427

1528
public class ReactiveMergeCoordinatorStandardScopeFactory extends MergeCoordinator
1629
implements ReactiveUpdateCoordinator {
@@ -30,4 +43,67 @@ public ReactiveScopedUpdateCoordinator makeScopedCoordinator() {
3043
this.getVersionUpdateBatchkey()
3144
);
3245
}
46+
47+
@Override
48+
protected MutationOperationGroup createOperationGroup(ValuesAnalysis valuesAnalysis, MutationGroup mutationGroup) {
49+
final int numberOfTableMutations = mutationGroup.getNumberOfTableMutations();
50+
switch ( numberOfTableMutations ) {
51+
case 0:
52+
return MutationOperationGroupFactory.noOperations( mutationGroup );
53+
case 1: {
54+
MutationOperation operation = createOperation( valuesAnalysis, mutationGroup.getSingleTableMutation() );
55+
return operation == null
56+
? MutationOperationGroupFactory.noOperations( mutationGroup )
57+
: MutationOperationGroupFactory.singleOperation( mutationGroup, operation );
58+
}
59+
default: {
60+
MutationOperation[] operations = new MutationOperation[numberOfTableMutations];
61+
int outputIndex = 0;
62+
int skipped = 0;
63+
for ( int i = 0; i < mutationGroup.getNumberOfTableMutations(); i++ ) {
64+
final TableMutation<?> tableMutation = mutationGroup.getTableMutation( i );
65+
MutationOperation operation = createOperation( valuesAnalysis, tableMutation );
66+
if ( operation != null ) {
67+
operations[outputIndex++] = operation;
68+
}
69+
else {
70+
skipped++;
71+
ModelMutationLogging.MODEL_MUTATION_LOGGER.debugf(
72+
"Skipping table update - %s",
73+
tableMutation.getTableName()
74+
);
75+
}
76+
}
77+
if ( skipped != 0 ) {
78+
final MutationOperation[] trimmed = new MutationOperation[outputIndex];
79+
System.arraycopy( operations, 0, trimmed, 0, outputIndex );
80+
operations = trimmed;
81+
}
82+
return MutationOperationGroupFactory.manyOperations( mutationGroup.getMutationType(), entityPersister, operations );
83+
}
84+
}
85+
}
86+
87+
// FIXME: We could add this method in ORM and override only this code
88+
protected MutationOperation createOperation(ValuesAnalysis valuesAnalysis, TableMutation<?> singleTableMutation) {
89+
MutationOperation operation = singleTableMutation.createMutationOperation( valuesAnalysis, factory() );
90+
if ( operation instanceof OptionalTableUpdateOperation ) {
91+
// We need to plug in our own reactive operation
92+
return new ReactiveOptionalTableUpdateOperation(
93+
operation.getMutationTarget(),
94+
(OptionalTableUpdate) singleTableMutation,
95+
factory()
96+
);
97+
}
98+
else if ( operation instanceof DeleteOrUpsertOperation &&
99+
factory().getJdbcServices().getDialect() instanceof OracleDialect ) {
100+
OracleDialect dialect = ((OracleDialect)factory().getJdbcServices().getDialect());
101+
return dialect.createOptionalTableUpdateOperation(
102+
( (OptionalTableUpdate)operation).getMutationTarget(),
103+
(OptionalTableUpdate) operation,
104+
factory()
105+
);
106+
}
107+
return operation;
108+
}
33109
}

hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveMergeCoordinator.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import org.hibernate.persister.entity.mutation.EntityTableMapping;
1212
import org.hibernate.sql.model.MutationOperation;
1313
import org.hibernate.sql.model.MutationOperationGroup;
14+
import org.hibernate.sql.model.ValuesAnalysis;
15+
import org.hibernate.sql.model.ast.MutationGroup;
1416
import org.hibernate.sql.model.ast.builder.AbstractTableUpdateBuilder;
1517
import org.hibernate.sql.model.ast.builder.TableMergeBuilder;
1618

@@ -33,4 +35,10 @@ public ReactiveMergeCoordinator(
3335
protected <O extends MutationOperation> AbstractTableUpdateBuilder<O> newTableUpdateBuilder(EntityTableMapping tableMapping) {
3436
return new TableMergeBuilder<>( entityPersister(), tableMapping, factory() );
3537
}
38+
39+
@Override
40+
protected MutationOperationGroup createOperationGroup(ValuesAnalysis valuesAnalysis, MutationGroup mutationGroup) {
41+
return super.createOperationGroup( valuesAnalysis, mutationGroup );
42+
}
43+
3644
}

hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveTypeContributor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.hibernate.dialect.CockroachDialect;
2222
import org.hibernate.dialect.DB2Dialect;
2323
import org.hibernate.dialect.Dialect;
24+
import org.hibernate.dialect.DialectDelegateWrapper;
2425
import org.hibernate.dialect.MySQLDialect;
2526
import org.hibernate.dialect.OracleDialect;
2627
import org.hibernate.dialect.PostgreSQLDialect;
@@ -99,7 +100,7 @@ private void registerReactiveChanges(TypeContributions typeContributions, Servic
99100
}
100101

101102
private Dialect dialect(ServiceRegistry serviceRegistry) {
102-
return serviceRegistry.getService( JdbcEnvironment.class ).getDialect();
103+
return DialectDelegateWrapper.extractRealDialect( serviceRegistry.getService( JdbcEnvironment.class ).getDialect() );
103104
}
104105

105106
/**

0 commit comments

Comments
 (0)