8000 HHH-18818 Fix ID conflicts between CTE batch inserts and optimizer strategies by stringintech · Pull Request #9232 · hibernate/hibernate-orm · GitHub
[go: up one dir, main page]

Skip to content

HHH-18818 Fix ID conflicts between CTE batch inserts and optimizer strategies #9232

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@
*/
package org.hibernate.id.enhanced;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.id.IntegralDataTypeHolder;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.jboss.logging.Logger;

import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.hibernate.HibernateException;
import org.hibernate.id.IntegralDataTypeHolder;

import org.jboss.logging.Logger;

/**
* Optimizer which applies a 'hilo' algorithm in memory to achieve
* optimization.
Expand Down Expand Up @@ -197,4 +202,20 @@ public IntegralDataTypeHolder getHiValue() {
lock.unlock();
}
}

@Override
public Expression createLowValueExpression(Expression databaseValue, SessionFactoryImplementor sessionFactory) {
BasicValuedMapping integerType = sessionFactory.getTypeConfiguration().getBasicTypeForJavaType( Integer.class );
return new BinaryArithmeticExpression(
new BinaryArithmeticExpression(
databaseValue,
BinaryArithmeticOperator.MULTIPLY,
new QueryLiteral<>( incrementSize, integerType ),
integerType
),
BinaryArithmeticOperator.SUBTRACT,
new QueryLiteral<>( incrementSize - 1, integerType ),
integerType
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@
*/
package org.hibernate.id.enhanced;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.id.IntegralDataTypeHolder;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.jboss.logging.Logger;

import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.hibernate.HibernateException;
import org.hibernate.id.IntegralDataTypeHolder;

import org.jboss.logging.Logger;

/**
* Slight variation from {@link HiLoOptimizer}, maintaining compatibility with the values generated by the
* legacy Hibernate hilo based generators.
Expand Down Expand Up @@ -150,4 +155,15 @@ public IntegralDataTypeHolder getLastValue() {
lock.unlock();
}
}

@Override
public Expression createLowValueExpression(Expression databaseValue, SessionFactoryImplementor sessionFactory) {
BasicValuedMapping integerType = sessionFactory.getTypeConfiguration().getBasicTypeForJavaType( Integer.class );
return new BinaryArithmeticExpression(
databaseValue,
BinaryArithmeticOperator.MULTIPLY,
new QueryLiteral<>( getIncrementSize() + 1, integerType ),
integerType
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
*/
package org.hibernate.id.enhanced;

import java.io.Serializable;

import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.id.IntegralDataTypeHolder;
import org.hibernate.sql.ast.tree.expression.Expression;

import java.io.Serializable;

/**
* An optimizer that performs no optimization. A round trip to
Expand Down Expand Up @@ -52,4 +54,9 @@ public boolean applyIncrementSizeToSourceValues() {
// We don't apply an increment size of 1, since it is already the default.
return getIncrementSize() != 0 && getIncrementSize() != 1;
}

@Override
public Expression createLowValueExpression(Expression databaseValue, SessionFactoryImplementor sessionFactory) {
return databaseValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
*/
package org.hibernate.id.enhanced;

import java.io.Serializable;

import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.id.IntegralDataTypeHolder;
import org.hibernate.sql.ast.tree.expression.Expression;

import java.io.Serializable;

/**
* Performs optimization on an optimizable identifier generator. Typically
Expand Down Expand Up @@ -59,4 +61,16 @@ public interface Optimizer {
* case the increment is totally an in memory construct.
*/
boolean applyIncrementSizeToSourceValues();

/**
* Creates an expression representing the low/base value for ID allocation in batch insert operations.
* <p>
* Each optimizer implementation should define its own
* strategy for calculating the starting value of a sequence range.
*
* @param databaseValue The expression representing the next value from database sequence
* @param sessionFactory
* @return An expression that calculates the low/base value according to the optimizer strategy
*/
Expression createLowValueExpression(Expression databaseValue, SessionFactoryImplementor sessionFactory);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@
*/
package org.hibernate.id.enhanced;

import org.hibernate.HibernateException;
import org.hibernat 9E81 e.engine.spi.SessionFactoryImplementor;
import org.hibernate.id.IntegralDataTypeHolder;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.jboss.logging.Logger;

import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;

import org.hibernate.HibernateException;
import org.hibernate.id.IntegralDataTypeHolder;
import org.hibernate.internal.CoreMessageLogger;
import org.jboss.logging.Logger;
import java.util.concurrent.locks.ReentrantLock;

/**
* Variation of {@link PooledOptimizer} which interprets the incoming database
Expand Down Expand Up @@ -125,4 +127,9 @@ public IntegralDataTypeHolder getLastSourceValue() {
public boolean applyIncrementSizeToSourceValues() {
return true;
}

@Override
public Expression createLowValueExpression(Expression databaseValue, SessionFactoryImplementor sessionFactory) {
return databaseValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
*/
package org.hibernate.id.enhanced;

import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Map;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.id.IntegralDataTypeHolder;
import org.hibernate.internal.CoreMessageLogger;

import org.hibernate.sql.ast.tree.expression.Expression;
import org.jboss.logging.Logger;

import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Map;

/**
* Variation of {@link PooledOptimizer} which interprets the incoming database
* value as the lo value, rather than the hi value, as well as using thread local
Expand Down Expand Up @@ -112,4 +113,9 @@ private Serializable generate(AccessCallback callback, int incrementSize) {
return value.makeValueThenIncrement();
}
}

@Override
public Expression createLowValueExpression(Expression databaseValue, SessionFactoryImplementor sessionFactory) {
return databaseValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@
*/
package org.hibernate.id.enhanced;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.id.IntegralDataTypeHolder;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.jboss.logging.Logger;

import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.hibernate.HibernateException;
import org.hibernate.id.IntegralDataTypeHolder;
import org.hibernate.internal.CoreMessageLogger;

import org.jboss.logging.Logger;

/**
* Optimizer which uses a pool of values, storing the next low value of the range
* in the database.
Expand Down Expand Up @@ -167,4 +172,15 @@ public IntegralDataTypeHolder getLastValue() {
public void injectInitialValue(long initialValue) {
this.initialValue = initialValue;
}

@Override
public Expression createLowValueExpression(Expression databaseValue, SessionFactoryImplementor sessionFactory) {
BasicValuedMapping integerType = sessionFactory.getTypeConfiguration().getBasicTypeForJavaType( Integer.class );
return new BinaryArithmeticExpression(
databaseValue,
BinaryArithmeticOperator.SUBTRACT,
new QueryLiteral<>( incrementSize - 1, integerType ),
integerType
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -384,12 +384,13 @@ public int execute(DomainQueryExecutionContext executionContext) {
generator.determineBulkInsertionIdentifierGenerationSelectFragment(
sessionFactory.getSqlStringGenerationContext()
);

Expression databaseValue = new SelfRenderingSqlFragmentExpression( fragment );
rowsWithSequenceQuery.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
1,
new SelfRenderingSqlFragmentExpression( fragment )
)
new SqlSelectionImpl( 1,
optimizer.createLowValueExpression( databaseValue, sessionFactory ) )
);

rowsWithSequenceQuery.applyPredicate(
new ComparisonPredicate(
rowNumberMinusOneModuloIncrement,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.id.cte;

import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.id.enhanced.HiLoOptimizer;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.RequiresDialects;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
* Tests sequence ID conflicts between entity persists and CTE batch inserts with {@link HiLoOptimizer},
* ensuring proper ID allocation and prevention of duplicates across both operations.
*
* @author Kowsar Atazadeh
*/
@JiraKey("HHH-18818")
@SessionFactory
@RequiresDialects({
@RequiresDialect(PostgreSQLDialect.class),
@RequiresDialect(DB2Dialect.class)
})
@ServiceRegistry(settings = @Setting(name = AvailableSettings.PREFERRED_POOLED_OPTIMIZER, value = "hilo"))
@DomainModel(annotatedClasses = Dummy.class)
public class CteInsertWithHiLoOptimizerTest {
@Test
void test(SessionFactoryScope scope) {
// 7 rows inserted with IDs 1-7
// Database sequence calls:
// - First returns 1 (allocates IDs 1-5)
// - Second returns 2 (allocates IDs 6-10)
// IDs 8-10 reserved from current allocation
scope.inTransaction( session -> {
for ( var id = 1; id <= 7; id++ ) {
Dummy d = new Dummy( "d" + id );
session.persist( d );
assertEquals( id, d.getId() );
}
} );

// 7 rows inserted with IDs 11-17
// Database sequence calls:
// - First returns 3 (allocates IDs 11-15)
// - Second returns 4 (allocates IDs 16-20)
// IDs 18-20 reserved from current allocation
scope.inTransaction( session -> {
session.createMutationQuery( "INSERT INTO Dummy (name) SELECT d.name FROM Dummy d" ).
executeUpdate();
var inserted = session.createSelectionQuery(
"SELECT d.id FROM Dummy d WHERE d.id > 7 ORDER BY d.id", Long.class )
.getResultList();
assertEquals( 7, inserted.size() );
for ( int i = 0; i < inserted.size(); i++ ) {
assertEquals( 11 + i, inserted.get( i ) );
}
} );

// 5 rows inserted with IDs 8-10, 21-22
// Database sequence call returns 5 (allocates IDs 21-25)
// Using previously reserved IDs 8-10 and new allocation IDs 21-22
// IDs 23-25 reserved from current allocation
scope.inTransaction( session -> {
for ( var id = 8; id <= 10; id++ ) {
Dummy d = new Dummy( "d" + id );
session.persist( d );
assertEquals( id, d.getId() );
}

for ( var id = 21; id <= 22; id++ ) {
Dummy d = new Dummy( "d" + id );
session.persist( d );
assertEquals( id, d.getId() );
}
} );
}
}
Loading
0