Skip to content

Commit d47bf5a

Browse files
committed
HHH-19972 Ignore PK violation when inserting just primary key columns
1 parent 0bdd592 commit d47bf5a

File tree

8 files changed

+217
-3
lines changed

8 files changed

+217
-3
lines changed

hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
5252
import org.hibernate.internal.util.StringHelper;
5353
import org.hibernate.internal.util.config.ConfigurationHelper;
54+
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
5455
import org.hibernate.query.SemanticException;
5556
import org.hibernate.query.common.TemporalUnit;
5657
import org.hibernate.query.sqm.IntervalType;
@@ -62,6 +63,9 @@
6263
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
6364
import org.hibernate.sql.ast.tree.Statement;
6465
import org.hibernate.sql.exec.spi.JdbcOperation;
66+
import org.hibernate.sql.model.MutationOperation;
67+
import org.hibernate.sql.model.internal.OptionalTableUpdate;
68+
import org.hibernate.sql.model.jdbc.OptionalTableUpdateWithUpsertOperation;
6569
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
6670
import org.hibernate.type.JavaObjectType;
6771
import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
@@ -672,6 +676,14 @@ protected <T extends JdbcOperation> SqlAstTranslator<T> buildTranslator(
672676
};
673677
}
674678

679+
@Override
680+
public MutationOperation createOptionalTableUpdateOperation(
681+
EntityMutationTarget mutationTarget,
682+
OptionalTableUpdate optionalTableUpdate,
683+
SessionFactoryImplementor factory) {
684+
return new OptionalTableUpdateWithUpsertOperation( mutationTarget, optionalTableUpdate, factory );
685+
}
686+
675687
@Override
676688
public NationalizationSupport getNationalizationSupport() {
677689
// TEXT / STRING inherently support nationalized data

hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
import org.hibernate.sql.exec.spi.JdbcOperation;
8686
import org.hibernate.sql.model.MutationOperation;
8787
import org.hibernate.sql.model.internal.OptionalTableUpdate;
88+
import org.hibernate.sql.model.jdbc.OptionalTableUpdateWithUpsertOperation;
8889
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
8990
import org.hibernate.tool.schema.internal.StandardTableExporter;
9091
import org.hibernate.tool.schema.spi.Exporter;
@@ -1574,7 +1575,7 @@ public MutationOperation createOptionalTableUpdateOperation(
15741575
.createMergeOperation( optionalTableUpdate );
15751576
}
15761577
else {
1577-
return super.createOptionalTableUpdateOperation( mutationTarget, optionalTableUpdate, factory );
1578+
return new OptionalTableUpdateWithUpsertOperation( mutationTarget, optionalTableUpdate, factory );
15781579
}
15791580
}
15801581

hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/CockroachSqlAstTranslator.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl;
2828
import org.hibernate.sql.exec.spi.JdbcOperation;
2929
import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert;
30+
import org.hibernate.sql.model.internal.OptionalTableInsert;
31+
import org.hibernate.sql.model.internal.TableInsertStandard;
3032

3133
/**
3234
* A SQL AST translator for Cockroach.
@@ -47,6 +49,39 @@ public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeti
4749
super.visitBinaryArithmeticExpression(arithmeticExpression);
4850
}
4951

52+
@Override
53+
public void visitStandardTableInsert(TableInsertStandard tableInsert) {
54+
getCurrentClauseStack().push( Clause.INSERT );
55+
try {
56+
renderInsertInto( tableInsert );
57+
if ( tableInsert instanceof OptionalTableInsert optionalTableInsert ) {
58+
appendSql( " on conflict " );
59+
final String constraintName = optionalTableInsert.getConstraintName();
60+
if ( constraintName != null ) {
61+
appendSql( " on constraint " );
62+
appendSql( constraintName );
63+
}
64+
else {
65+
char separator = '(';
66+
for ( String constraintColumnName : optionalTableInsert.getConstraintColumnNames() ) {
67+
appendSql( separator );
68+
appendSql( constraintColumnName );
69+
separator = ',';
70+
}
71+
appendSql( ')' );
72+
}
73+
appendSql( " do nothing" );
74+
}
75+
76+
if ( tableInsert.getNumberOfReturningColumns() > 0 ) {
77+
visitReturningColumns( tableInsert::getReturningColumns );
78+
}
79+
}
80+
finally {
81+
getCurrentClauseStack().pop();
82+
}
83+
}
84+
5085
@Override
5186
protected JdbcOperationQueryInsert translateInsert(InsertSelectStatement sqlAst) {
5287
visitInsertStatement( sqlAst );

hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/PostgreSQLSqlAstTranslator.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl;
3333
import org.hibernate.sql.exec.spi.JdbcOperation;
3434
import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert;
35+
import org.hibernate.sql.model.internal.OptionalTableInsert;
3536
import org.hibernate.sql.model.internal.TableInsertStandard;
3637
import org.hibernate.type.SqlTypes;
3738

@@ -59,6 +60,39 @@ protected String getArrayContainsFunction() {
5960
return super.getArrayContainsFunction();
6061
}
6162

63+
@Override
64+
public void visitStandardTableInsert(TableInsertStandard tableInsert) {
65+
getCurrentClauseStack().push( Clause.INSERT );
66+
try {
67+
renderInsertInto( tableInsert );
68+
if ( tableInsert instanceof OptionalTableInsert optionalTableInsert ) {
69+
appendSql( " on conflict " );
70+
final String constraintName = optionalTableInsert.getConstraintName();
71+
if ( constraintName != null ) {
72+
appendSql( " on constraint " );
73+
appendSql( constraintName );
74+
}
75+
else {
76+
char separator = '(';
77+
for ( String constraintColumnName : optionalTableInsert.getConstraintColumnNames() ) {
78+
appendSql( separator );
79+
appendSql( constraintColumnName );
80+
separator = ',';
81+
}
82+
appendSql( ')' );
83+
}
84+
appendSql( " do nothing" );
85+
}
86+
87+
if ( tableInsert.getNumberOfReturningColumns() > 0 ) {
88+
visitReturningColumns( tableInsert::getReturningColumns );
89+
}
90+
}
91+
finally {
92+
getCurrentClauseStack().pop();
93+
}
94+
}
95+
6296
@Override
6397
protected void renderInsertIntoNoColumns(TableInsertStandard tableInsert) {
6498
renderIntoIntoAndTable( tableInsert );

hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@
150150
import org.hibernate.sql.model.ast.ColumnWriteFragment;
151151
import org.hibernate.sql.model.ast.RestrictedTableMutation;
152152
import org.hibernate.sql.model.ast.TableMutation;
153+
import org.hibernate.sql.model.internal.OptionalTableInsert;
153154
import org.hibernate.sql.model.internal.OptionalTableUpdate;
154155
import org.hibernate.sql.model.internal.TableDeleteCustomSql;
155156
import org.hibernate.sql.model.internal.TableDeleteStandard;
@@ -8443,6 +8444,9 @@ private T translateTableMutation(TableMutation<?> mutation) {
84438444

84448445
@Override
84458446
public void visitStandardTableInsert(TableInsertStandard tableInsert) {
8447+
if ( tableInsert instanceof OptionalTableInsert ) {
8448+
throw new IllegalQueryOperationException( "Optional table insert is not supported" );
8449+
}
84468450
getCurrentClauseStack().push( Clause.INSERT );
84478451
try {
84488452
renderInsertInto( tableInsert );
@@ -8456,7 +8460,7 @@ public void visitStandardTableInsert(TableInsertStandard tableInsert) {
84568460
}
84578461
}
84588462

8459-
private void renderInsertInto(TableInsertStandard tableInsert) {
8463+
protected void renderInsertInto(TableInsertStandard tableInsert) {
84608464
applySqlComment( tableInsert.getMutationComment() );
84618465

84628466
if ( tableInsert.getNumberOfValueBindings() == 0 ) {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.sql.model.internal;
6+
7+
import org.checkerframework.checker.nullness.qual.Nullable;
8+
import org.hibernate.sql.ast.tree.expression.ColumnReference;
9+
import org.hibernate.sql.model.MutationTarget;
10+
import org.hibernate.sql.model.ast.ColumnValueBinding;
11+
import org.hibernate.sql.model.ast.ColumnValueParameter;
12+
import org.hibernate.sql.model.ast.MutatingTableReference;
13+
14+
import java.util.List;
15+
16+
public class OptionalTableInsert extends TableInsertStandard {
17+
18+
private final @Nullable String constraintName;
19+
private final List<String> constraintColumnNames;
20+
21+
public OptionalTableInsert(
22+
MutatingTableReference mutatingTable,
23+
MutationTarget<?> mutationTarget,
24+
List<ColumnValueBinding> valueBindings,
25+
List<ColumnReference> returningColumns,
26+
List<ColumnValueParameter> parameters,
27+
@Nullable String constraintName,
28+
List<String> constraintColumnNames) {
29+
super( mutatingTable, mutationTarget, valueBindings, returningColumns, parameters );
30+
this.constraintName = constraintName;
31+
this.constraintColumnNames = constraintColumnNames;
32+
}
33+
34+
public @Nullable String getConstraintName() {
35+
return constraintName;
36+
}
37+
38+
public List<String> getConstraintColumnNames() {
39+
return constraintColumnNames;
40+
}
41+
}

hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,22 @@ public TableMapping getTableDetails() {
105105
return tableMapping;
106106
}
107107

108+
public List<ColumnValueBinding> getValueBindings() {
109+
return valueBindings;
110+
}
111+
112+
public List<ColumnValueBinding> getKeyBindings() {
113+
return keyBindings;
114+
}
115+
116+
public List<ColumnValueBinding> getOptimisticLockBindings() {
117+
return optimisticLockBindings;
118+
}
119+
120+
public List<ColumnValueParameter> getParameters() {
121+
return parameters;
122+
}
123+
108124
@Override
109125
public JdbcValueDescriptor findValueDescriptor(String columnName, ParameterUsage usage) {
110126
for ( int i = 0; i < jdbcValueDescriptors.size(); i++ ) {
@@ -378,7 +394,7 @@ protected JdbcMutationOperation createJdbcUpdate(SharedSessionContractImplemento
378394
}
379395

380396
private void performInsert(JdbcValueBindings jdbcValueBindings, SharedSessionContractImplementor session) {
381-
final JdbcInsertMutation jdbcInsert = createJdbcInsert( session );
397+
final JdbcMutationOperation jdbcInsert = createJdbcOptionalInsert( session );
382398
final JdbcServices jdbcServices = session.getJdbcServices();
383399
final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator();
384400
final PreparedStatement insertStatement = createStatementDetails( jdbcInsert, jdbcCoordinator );
@@ -416,6 +432,13 @@ private void performInsert(JdbcValueBindings jdbcValueBindings, SharedSessionCon
416432
}
417433
}
418434

435+
/*
436+
* Used by Hibernate Reactive
437+
*/
438+
protected JdbcMutationOperation createJdbcOptionalInsert(SharedSessionContractImplementor session) {
439+
return createJdbcInsert( session );
440+
}
441+
419442
/*
420443
* Used by Hibernate Reactive
421444
*/
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.sql.model.jdbc;
6+
7+
import org.hibernate.engine.jdbc.mutation.internal.MutationQueryOptions;
8+
import org.hibernate.engine.spi.SessionFactoryImplementor;
9+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
10+
import org.hibernate.internal.util.collections.CollectionHelper;
11+
import org.hibernate.persister.entity.EntityPersister;
12+
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
13+
import org.hibernate.sql.model.ast.MutatingTableReference;
14+
import org.hibernate.sql.model.ast.TableMutation;
15+
import org.hibernate.sql.model.internal.OptionalTableInsert;
16+
import org.hibernate.sql.model.internal.OptionalTableUpdate;
17+
18+
import java.util.Arrays;
19+
import java.util.Collections;
20+
21+
/**
22+
* Uses {@link org.hibernate.sql.model.internal.OptionalTableInsert} for the insert operation,
23+
* to avoid primary key constraint violations when inserting only primary key columns.
24+
*/
25+
public class OptionalTableUpdateWithUpsertOperation extends OptionalTableUpdateOperation {
26+
27+
public OptionalTableUpdateWithUpsertOperation(
28+
EntityMutationTarget mutationTarget,
29+
OptionalTableUpdate upsert,
30+
@SuppressWarnings("unused") SessionFactoryImplementor factory) {
31+
super( mutationTarget, upsert, factory );
32+
}
33+
34+
@Override
35+
protected JdbcMutationOperation createJdbcOptionalInsert(SharedSessionContractImplementor session) {
36+
if ( getTableDetails().getInsertDetails() != null
37+
&& getTableDetails().getInsertDetails().getCustomSql() != null
38+
|| !getValueBindings().isEmpty() ) {
39+
return super.createJdbcOptionalInsert( session );
40+
}
41+
else {
42+
// Ignore a primary key violation on insert when inserting just the primary key columns
43+
final TableMutation<? extends JdbcMutationOperation> tableInsert = new OptionalTableInsert(
44+
new MutatingTableReference( getTableDetails() ),
45+
getMutationTarget(),
46+
CollectionHelper.combine( getValueBindings(), getKeyBindings() ),
47+
Collections.emptyList(),
48+
getParameters(),
49+
null,
50+
Arrays.asList( ((EntityPersister) getMutationTarget()).getIdentifierColumnNames() )
51+
);
52+
53+
final SessionFactoryImplementor factory = session.getSessionFactory();
54+
return factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory()
55+
.buildModelMutationTranslator( tableInsert, factory )
56+
.translate( null, MutationQueryOptions.INSTANCE );
57+
}
58+
}
59+
60+
@Override
61+
public String toString() {
62+
return "OptionalTableUpdateWithUpsertOperation(" + getTableDetails() + ")";
63+
}
64+
}

0 commit comments

Comments
 (0)