Skip to content

Commit 45083fc

Browse files
committed
HHH-19972 Ignore PK violation when inserting just primary key columns
1 parent 017b078 commit 45083fc

File tree

9 files changed

+227
-20
lines changed

9 files changed

+227
-20
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
@@ -48,6 +48,7 @@
4848
import org.hibernate.internal.CoreMessageLogger;
4949
import org.hibernate.internal.util.JdbcExceptionHelper;
5050
import org.hibernate.internal.util.config.ConfigurationHelper;
51+
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
5152
import org.hibernate.query.SemanticException;
5253
import org.hibernate.query.sqm.IntervalType;
5354
import org.hibernate.query.sqm.TemporalUnit;
@@ -59,6 +60,9 @@
5960
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
6061
import org.hibernate.sql.ast.tree.Statement;
6162
import org.hibernate.sql.exec.spi.JdbcOperation;
63+
import org.hibernate.sql.model.MutationOperation;
64+
import org.hibernate.sql.model.internal.OptionalTableUpdate;
65+
import org.hibernate.sql.model.jdbc.OptionalTableUpdateWithUpsertOperation;
6266
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
6367
import org.hibernate.type.JavaObjectType;
6468
import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
@@ -658,6 +662,14 @@ protected <T extends JdbcOperation> SqlAstTranslator<T> buildTranslator(
658662
};
659663
}
660664

665+
@Override
666+
public MutationOperation createOptionalTableUpdateOperation(
667+
EntityMutationTarget mutationTarget,
668+
OptionalTableUpdate optionalTableUpdate,
669+
SessionFactoryImplementor factory) {
670+
return new OptionalTableUpdateWithUpsertOperation( mutationTarget, optionalTableUpdate, factory );
671+
}
672+
661673
@Override
662674
public NationalizationSupport getNationalizationSupport() {
663675
// TEXT / STRING inherently support nationalized data

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl;
3030
import org.hibernate.sql.exec.spi.JdbcOperation;
3131
import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert;
32+
import org.hibernate.sql.model.internal.OptionalTableInsert;
33+
import org.hibernate.sql.model.internal.TableInsertStandard;
3234

3335
/**
3436
* A SQL AST translator for Cockroach.
@@ -49,6 +51,40 @@ public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeti
4951
super.visitBinaryArithmeticExpression(arithmeticExpression);
5052
}
5153

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

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
import org.hibernate.sql.exec.spi.JdbcOperation;
8080
import org.hibernate.sql.model.MutationOperation;
8181
import org.hibernate.sql.model.internal.OptionalTableUpdate;
82-
import org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation;
82+
import org.hibernate.sql.model.jdbc.OptionalTableUpdateWithUpsertOperation;
8383
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
8484
import org.hibernate.type.JavaObjectType;
8585
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
@@ -1594,7 +1594,7 @@ private static MutationOperation withoutMerge(
15941594
EntityMutationTarget mutationTarget,
15951595
OptionalTableUpdate optionalTableUpdate,
15961596
SessionFactoryImplementor factory) {
1597-
return new OptionalTableUpdateOperation( mutationTarget, optionalTableUpdate, factory );
1597+
return new OptionalTableUpdateWithUpsertOperation( mutationTarget, optionalTableUpdate, factory );
15981598
}
15991599

16001600
@Override

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

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

@@ -60,6 +61,40 @@ protected String getArrayContainsFunction() {
6061
return super.getArrayContainsFunction();
6162
}
6263

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

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

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import org.hibernate.dialect.function.CommonFunctionFactory;
1515
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
1616
import org.hibernate.engine.spi.SessionFactoryImplementor;
17-
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
1817
import org.hibernate.query.sqm.CastType;
1918
import org.hibernate.query.sqm.TemporalUnit;
2019
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
@@ -24,9 +23,6 @@
2423
import org.hibernate.sql.ast.tree.Statement;
2524
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
2625
import org.hibernate.sql.exec.spi.JdbcOperation;
27-
import org.hibernate.sql.model.MutationOperation;
28-
import org.hibernate.sql.model.internal.OptionalTableUpdate;
29-
import org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation;
3026

3127
import jakarta.persistence.TemporalType;
3228

@@ -136,16 +132,6 @@ public String getSelectGUIDString() {
136132
return "select uuid_generate_v1";
137133
}
138134

139-
@Override
140-
public MutationOperation createOptionalTableUpdateOperation(
141-
EntityMutationTarget mutationTarget,
142-
OptionalTableUpdate optionalTableUpdate,
143-
SessionFactoryImplementor factory) {
144-
// Postgres Plus does not support full merge semantics -
145-
// https://www.enterprisedb.com/docs/migrating/oracle/oracle_epas_comparison/notable_differences/
146-
return new OptionalTableUpdateOperation( mutationTarget, optionalTableUpdate, factory );
147-
}
148-
149135
@Override
150136
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
151137
return new StandardSqlAstTranslatorFactory() {

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
@@ -207,6 +207,7 @@
207207
import org.hibernate.sql.model.ast.ColumnWriteFragment;
208208
import org.hibernate.sql.model.ast.RestrictedTableMutation;
209209
import org.hibernate.sql.model.ast.TableMutation;
210+
import org.hibernate.sql.model.internal.OptionalTableInsert;
210211
import org.hibernate.sql.model.internal.OptionalTableUpdate;
211212
import org.hibernate.sql.model.internal.TableDeleteCustomSql;
212213
import org.hibernate.sql.model.internal.TableDeleteStandard;
@@ -8825,6 +8826,9 @@ private T translateTableMutation(TableMutation<?> mutation) {
88258826

88268827
@Override
88278828
public void visitStandardTableInsert(TableInsertStandard tableInsert) {
8829+
if ( tableInsert instanceof OptionalTableInsert ) {
8830+
throw new IllegalQueryOperationException( "Optional table insert is not supported" );
8831+
}
88288832
getCurrentClauseStack().push( Clause.INSERT );
88298833
try {
88308834
renderInsertInto( tableInsert );
@@ -8838,7 +8842,7 @@ public void visitStandardTableInsert(TableInsertStandard tableInsert) {
88388842
}
88398843
}
88408844

8841-
private void renderInsertInto(TableInsertStandard tableInsert) {
8845+
protected void renderInsertInto(TableInsertStandard tableInsert) {
88428846
applySqlComment( tableInsert.getMutationComment() );
88438847

88448848
if ( tableInsert.getNumberOfValueBindings() == 0 ) {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.sql.model.internal;
8+
9+
import org.checkerframework.checker.nullness.qual.Nullable;
10+
import org.hibernate.sql.ast.tree.expression.ColumnReference;
11+
import org.hibernate.sql.model.MutationTarget;
12+
import org.hibernate.sql.model.ast.ColumnValueBinding;
13+
import org.hibernate.sql.model.ast.ColumnValueParameter;
14+
import org.hibernate.sql.model.ast.MutatingTableReference;
15+
16+
import java.util.List;
17+
18+
public class OptionalTableInsert extends TableInsertStandard {
19+
20+
private final @Nullable String constraintName;
21+
private final List<String> constraintColumnNames;
22+
23+
public OptionalTableInsert(
24+
MutatingTableReference mutatingTable,
25+
MutationTarget<?> mutationTarget,
26+
List<ColumnValueBinding> valueBindings,
27+
List<ColumnReference> returningColumns,
28+
List<ColumnValueParameter> parameters,
29+
@Nullable String constraintName,
30+
List<String> constraintColumnNames) {
31+
super( mutatingTable, mutationTarget, valueBindings, returningColumns, parameters );
32+
this.constraintName = constraintName;
33+
this.constraintColumnNames = constraintColumnNames;
34+
}
35+
36+
public @Nullable String getConstraintName() {
37+
return constraintName;
38+
}
39+
40+
public List<String> getConstraintColumnNames() {
41+
return constraintColumnNames;
42+
}
43+
}

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

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,22 @@ public TableMapping getTableDetails() {
112112
return tableMapping;
113113
}
114114

115+
public List<ColumnValueBinding> getValueBindings() {
116+
return valueBindings;
117+
}
118+
119+
public List<ColumnValueBinding> getKeyBindings() {
120+
return keyBindings;
121+
}
122+
123+
public List<ColumnValueBinding> getOptimisticLockBindings() {
124+
return optimisticLockBindings;
125+
}
126+
127+
public List<ColumnValueParameter> getParameters() {
128+
return parameters;
129+
}
130+
115131
@Override
116132
public JdbcValueDescriptor findValueDescriptor(String columnName, ParameterUsage usage) {
117133
for ( int i = 0; i < jdbcValueDescriptors.size(); i++ ) {
@@ -404,8 +420,7 @@ private boolean performUpdate(
404420
}
405421

406422
private void performInsert(JdbcValueBindings jdbcValueBindings, SharedSessionContractImplementor session) {
407-
final JdbcInsertMutation jdbcInsert = createJdbcInsert( session );
408-
423+
final JdbcMutationOperation jdbcInsert = createJdbcOptionalInsert( session );
409424
final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator();
410425
final PreparedStatement insertStatement = createStatementDetails( jdbcInsert, jdbcCoordinator );
411426

@@ -445,7 +460,17 @@ private void performInsert(JdbcValueBindings jdbcValueBindings, SharedSessionCon
445460
}
446461
}
447462

448-
private JdbcInsertMutation createJdbcInsert(SharedSessionContractImplementor session) {
463+
/*
464+
* Used by Hibernate Reactive
465+
*/
466+
protected JdbcMutationOperation createJdbcOptionalInsert(SharedSessionContractImplementor session) {
467+
return createJdbcInsert( session );
468+
}
469+
470+
/*
471+
* Used by Hibernate Reactive
472+
*/
473+
protected JdbcInsertMutation createJdbcInsert(SharedSessionContractImplementor session) {
449474
final TableInsert tableInsert;
450475
if ( tableMapping.getInsertDetails() != null
451476
&& tableMapping.getInsertDetails().getCustomSql() != null ) {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.sql.model.jdbc;
8+
9+
import org.hibernate.engine.jdbc.mutation.internal.MutationQueryOptions;
10+
import org.hibernate.engine.spi.SessionFactoryImplementor;
11+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
12+
import org.hibernate.internal.util.collections.CollectionHelper;
13+
import org.hibernate.persister.entity.EntityPersister;
14+
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
15+
import org.hibernate.sql.model.ast.MutatingTableReference;
16+
import org.hibernate.sql.model.ast.TableMutation;
17+
import org.hibernate.sql.model.internal.OptionalTableInsert;
18+
import org.hibernate.sql.model.internal.OptionalTableUpdate;
19+
20+
import java.util.Arrays;
21+
import java.util.Collections;
22+
23+
/**
24+
* Uses {@link org.hibernate.sql.model.internal.OptionalTableInsert} for the insert operation,
25+
* to avoid primary key constraint violations when inserting only primary key columns.
26+
*/
27+
public class OptionalTableUpdateWithUpsertOperation extends OptionalTableUpdateOperation {
28+
29+
public OptionalTableUpdateWithUpsertOperation(
30+
EntityMutationTarget mutationTarget,
31+
OptionalTableUpdate upsert,
32+
@SuppressWarnings("unused") SessionFactoryImplementor factory) {
33+
super( mutationTarget, upsert, factory );
34+
}
35+
36+
@Override
37+
protected JdbcMutationOperation createJdbcOptionalInsert(SharedSessionContractImplementor session) {
38+
if ( getTableDetails().getInsertDetails() != null
39+
&& getTableDetails().getInsertDetails().getCustomSql() != null
40+
|| !getValueBindings().isEmpty() ) {
41+
return super.createJdbcOptionalInsert( session );
42+
}
43+
else {
44+
// Ignore a primary key violation on insert when inserting just the primary key columns
45+
final TableMutation<? extends JdbcMutationOperation> tableInsert = new OptionalTableInsert(
46+
new MutatingTableReference( getTableDetails() ),
47+
getMutationTarget(),
48+
CollectionHelper.combine( getValueBindings(), getKeyBindings() ),
49+
Collections.emptyList(),
50+
getParameters(),
51+
null,
52+
Arrays.asList( ((EntityPersister) getMutationTarget()).getIdentifierColumnNames() )
53+
);
54+
55+
final SessionFactoryImplementor factory = session.getSessionFactory();
56+
return factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory()
57+
.buildModelMutationTranslator( tableInsert, factory )
58+
.translate( null, MutationQueryOptions.INSTANCE );
59+
}
60+
}
61+
62+
@Override
63+
public String toString() {
64+
return "OptionalTableUpdateWithUpsertOperation(" + getTableDetails() + ")";
65+
}
66+
}

0 commit comments

Comments
 (0)