Skip to content

Commit 9ce4619

Browse files
committed
HHH-19972 Ignore PK violation when inserting just primary key columns
1 parent 8a2f5d4 commit 9ce4619

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.internal.InformationExtractorPostgreSQLImpl;
6670
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
6771
import org.hibernate.tool.schema.extract.spi.ExtractionContext;
@@ -676,6 +680,14 @@ protected <T extends JdbcOperation> SqlAstTranslator<T> buildTranslator(
676680
};
677681
}
678682

683+
@Override
684+
public MutationOperation createOptionalTableUpdateOperation(
685+
EntityMutationTarget mutationTarget,
686+
OptionalTableUpdate optionalTableUpdate,
687+
SessionFactoryImplementor factory) {
688+
return new OptionalTableUpdateWithUpsertOperation( mutationTarget, optionalTableUpdate, factory );
689+
}
690+
679691
@Override
680692
public NationalizationSupport getNationalizationSupport() {
681693
// 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.internal.InformationExtractorPostgreSQLImpl;
8990
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
9091
import org.hibernate.tool.schema.extract.spi.ExtractionContext;
@@ -1587,7 +1588,7 @@ public MutationOperation createOptionalTableUpdateOperation(
15871588
.createMergeOperation( optionalTableUpdate );
15881589
}
15891590
else {
1590-
return super.createOptionalTableUpdateOperation( mutationTarget, optionalTableUpdate, factory );
1591+
return new OptionalTableUpdateWithUpsertOperation( mutationTarget, optionalTableUpdate, factory );
15911592
}
15921593
}
15931594

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
@@ -188,6 +188,7 @@
188188
import org.hibernate.sql.model.ast.ColumnWriteFragment;
189189
import org.hibernate.sql.model.ast.RestrictedTableMutation;
190190
import org.hibernate.sql.model.ast.TableMutation;
191+
import org.hibernate.sql.model.internal.OptionalTableInsert;
191192
import org.hibernate.sql.model.internal.OptionalTableUpdate;
192193
import org.hibernate.sql.model.internal.TableDeleteCustomSql;
193194
import org.hibernate.sql.model.internal.TableDeleteStandard;
@@ -8508,6 +8509,9 @@ private T translateTableMutation(TableMutation<?> mutation) {
85088509

85098510
@Override
85108511
public void visitStandardTableInsert(TableInsertStandard tableInsert) {
8512+
if ( tableInsert instanceof OptionalTableInsert ) {
8513+
throw new IllegalQueryOperationException( "Optional table insert is not supported" );
8514+
}
85118515
getCurrentClauseStack().push( Clause.INSERT );
85128516
try {
85138517
renderInsertInto( tableInsert );
@@ -8521,7 +8525,7 @@ public void visitStandardTableInsert(TableInsertStandard tableInsert) {
85218525
}
85228526
}
85238527

8524-
private void renderInsertInto(TableInsertStandard tableInsert) {
8528+
protected void renderInsertInto(TableInsertStandard tableInsert) {
85258529
applySqlComment( tableInsert.getMutationComment() );
85268530

85278531
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++ ) {
@@ -375,7 +391,7 @@ protected JdbcMutationOperation createJdbcUpdate(SharedSessionContractImplemento
375391
}
376392

377393
private void performInsert(JdbcValueBindings jdbcValueBindings, SharedSessionContractImplementor session) {
378-
final JdbcInsertMutation jdbcInsert = createJdbcInsert( session );
394+
final JdbcMutationOperation jdbcInsert = createJdbcOptionalInsert( session );
379395
final JdbcServices jdbcServices = session.getJdbcServices();
380396
final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator();
381397
final PreparedStatement insertStatement = createStatementDetails( jdbcInsert, jdbcCoordinator );
@@ -413,6 +429,13 @@ private void performInsert(JdbcValueBindings jdbcValueBindings, SharedSessionCon
413429
}
414430
}
415431

432+
/*
433+
* Used by Hibernate Reactive
434+
*/
435+
protected JdbcMutationOperation createJdbcOptionalInsert(SharedSessionContractImplementor session) {
436+
return createJdbcInsert( session );
437+
}
438+
416439
/*
417440
* Used by Hibernate Reactive
418441
*/
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)