HHH-15528 Add Cockroach to Jenkins nightly test matrix and fix issues

This commit is contained in:
Christian Beikov 2022-09-23 17:43:11 +02:00
parent 495849437f
commit 14d1c65802
62 changed files with 1140 additions and 304 deletions

View File

@ -47,6 +47,8 @@ jobs:
- rdbms: db2
- rdbms: mssql
- rdbms: sybase
# Running with CockroachDB requires at least 2-4 vCPUs, which we don't have on GH Actions runners
# - rdbms: cockroachdb
# Running with HANA requires at least 8GB memory just for the database, which we don't have on GH Actions runners
# - rdbms: hana
steps:

106
Jenkinsfile vendored
View File

@ -24,7 +24,6 @@ import org.hibernate.jenkins.pipeline.helpers.job.JobHelper
this.helper = new JobHelper(this)
helper.runWithNotification {
def defaultJdk = '11'
stage('Configure') {
this.environments = [
// new BuildEnvironment( dbName: 'h2' ),
@ -39,9 +38,11 @@ stage('Configure') {
// new BuildEnvironment( dbName: 'db2' ),
// new BuildEnvironment( dbName: 'mssql' ),
// new BuildEnvironment( dbName: 'sybase' ),
new BuildEnvironment( dbName: 'hana', node: 'HANA' ),
new BuildEnvironment( dbName: 's390x', node: 's390x' ),
new BuildEnvironment( dbName: 'tidb', node: 'tidb', notificationRecipients: 'tidb_hibernate@pingcap.com' ),
new BuildEnvironment( dbName: 'hana_jenkins', node: 'HANA', dbLockableResource: 'HANA' ),
new BuildEnvironment( node: 's390x' ),
new BuildEnvironment( dbName: 'tidb', node: 'tidb', dbLockableResource: 'TIDB',
additionalOptions: '-DdbHost=localhost:4000',
notificationRecipients: 'tidb_hibernate@pingcap.com' ),
// Disable EDB for now as the image is not available anymore
// new BuildEnvironment( dbName: 'edb' ),
new BuildEnvironment( testJdkVersion: '17' ),
@ -53,11 +54,17 @@ stage('Configure') {
new BuildEnvironment( testJdkVersion: '20', testJdkLauncherArgs: '--enable-preview' )
];
if ( env.CHANGE_ID ) {
if ( pullRequest.labels.contains( 'cockroachdb' ) ) {
this.environments.add( new BuildEnvironment( dbName: 'cockroachdb', node: 'LongDuration', longRunning: true ) )
}
}
helper.configure {
file 'job-configuration.yaml'
// We don't require the following, but the build helper plugin apparently does
jdk {
defaultTool "OpenJDK ${defaultJdk} Latest"
defaultTool DEFAULT_JDK_TOOL
}
maven {
defaultTool 'Apache Maven 3.8'
@ -96,7 +103,7 @@ stage('Build') {
if ( buildEnv.testJdkVersion ) {
testJavaHome = tool(name: "OpenJDK ${buildEnv.testJdkVersion} Latest", type: 'jdk')
}
def javaHome = tool(name: "OpenJDK ${DEFAULT_JDK_VERSION} Latest", type: 'jdk')
def javaHome = tool(name: DEFAULT_JDK_TOOL, type: 'jdk')
// Use withEnv instead of setting env directly, as that is global!
// See https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md
withEnv(["JAVA_HOME=${javaHome}", "PATH+JAVA=${javaHome}/bin"]) {
@ -116,6 +123,13 @@ stage('Build') {
try {
stage('Start database') {
switch (buildEnv.dbName) {
case "cockroachdb":
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
docker.image('cockroachdb/cockroach:v21.1.21').pull()
}
sh "./docker_db.sh cockroachdb"
state[buildEnv.tag]['containerName'] = "cockroach"
break;
case "mysql":
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
docker.image('mysql:5.7').pull()
@ -155,7 +169,7 @@ stage('Build') {
break;
case "oracle":
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
docker.image('quillbuilduser/oracle-18-xe').pull()
docker.image('gvenzl/oracle-xe:18.4.0-full').pull()
}
sh "./docker_db.sh oracle_18"
state[buildEnv.tag]['containerName'] = "oracle"
@ -192,38 +206,25 @@ stage('Build') {
}
}
stage('Test') {
switch (buildEnv.dbName) {
case "h2":
case "derby":
case "hsqldb":
runTest("-Pdb=${buildEnv.dbName}${state[buildEnv.tag]['additionalOptions']}")
break;
case "mysql":
case "mysql8":
runTest("-Pdb=mysql_ci${state[buildEnv.tag]['additionalOptions']}")
break;
case "tidb":
runTest("-Pdb=tidb -DdbHost=localhost:4000${state[buildEnv.tag]['additionalOptions']}", 'TIDB')
break;
case "postgresql":
case "postgresql_13":
runTest("-Pdb=pgsql_ci${state[buildEnv.tag]['additionalOptions']}")
break;
case "oracle":
runTest("-Pdb=oracle_ci -PexcludeTests=**.LockTest.testQueryTimeout*${state[buildEnv.tag]['additionalOptions']}")
break;
case "hana":
runTest("-Pdb=hana_jenkins${state[buildEnv.tag]['additionalOptions']}", 'HANA')
break;
case "edb":
runTest("-Pdb=edb_ci -DdbHost=localhost:5433${state[buildEnv.tag]['additionalOptions']}")
break;
case "s390x":
runTest("-Pdb=h2${state[buildEnv.tag]['additionalOptions']}")
break;
default:
runTest("-Pdb=${buildEnv.dbName}_ci${state[buildEnv.tag]['additionalOptions']}")
break;
String cmd = "./ci/build.sh ${buildEnv.additionalOptions ?: ''} ${state[buildEnv.tag]['additionalOptions'] ?: ''}"
withEnv(["RDBMS=${buildEnv.dbName}"]) {
try {
if (buildEnv.dbLockableResource == null) {
timeout( [time: buildEnv.longRunning ? 240 : 120, unit: 'MINUTES'] ) {
sh cmd
}
}
else {
lock(buildEnv.dbLockableResource) {
timeout( [time: buildEnv.longRunning ? 240 : 120, unit: 'MINUTES'] ) {
sh cmd
}
}
}
}
finally {
junit '**/target/test-results/test/*.xml,**/target/test-results/testKitTest/*.xml'
}
}
}
}
@ -252,10 +253,13 @@ class BuildEnvironment {
String testJdkLauncherArgs
String dbName = 'h2'
String node
String dbLockableResource
String additionalOptions
String notificationRecipients
boolean longRunning
String toString() { getTag() }
String getTag() { "${testJdkVersion ? 'jdk_' + testJdkVersion + '_' : '' }${dbName}" }
String getTag() { "${node ? node + "_" : ''}${testJdkVersion ? 'jdk_' + testJdkVersion + '_' : '' }${dbName}" }
}
void runBuildOnNode(String label, Closure body) {
@ -281,28 +285,6 @@ void pruneDockerContainers() {
sh 'docker volume prune -f || true'
}
}
// Clean by default otherwise the PackagedEntityManager tests fail on a node that previously ran a different DB
void runTest(String goal, String lockableResource = null, boolean clean = true) {
String cmd = "./gradlew" + (clean ? " clean" : "") + " check ${goal} -Plog-test-progress=true --stacktrace";
try {
if (lockableResource == null) {
timeout( [time: 120, unit: 'MINUTES'] ) {
sh cmd
}
}
else {
lock(lockableResource) {
timeout( [time: 120, unit: 'MINUTES'] ) {
sh cmd
}
}
}
}
finally {
junit '**/target/test-results/test/*.xml,**/target/test-results/testKitTest/*.xml'
}
}
void handleNotifications(currentBuild, buildEnv) {
def currentResult = getParallelResult(currentBuild, buildEnv.tag)

View File

@ -1,8 +1,13 @@
#! /bin/bash
goal=
if [ "$RDBMS" == "derby" ]; then
if [ "$RDBMS" == "h2" ]; then
# This is the default.
goal=""
elif [ "$RDBMS" == "derby" ]; then
goal="-Pdb=derby"
elif [ "$RDBMS" == "edb" ]; then
goal="-Pdb=edb_ci"
elif [ "$RDBMS" == "hsqldb" ]; then
goal="-Pdb=hsqldb"
elif [ "$RDBMS" == "mysql8" ]; then
@ -17,6 +22,7 @@ elif [ "$RDBMS" == "postgresql_13" ]; then
goal="-Pdb=pgsql_ci"
elif [ "$RDBMS" == "oracle" ]; then
# I have no idea why, but these tests don't work on GH Actions
# yrodiere: Apparently those have been disabled on Jenkins as well...
goal="-Pdb=oracle_ci -PexcludeTests=**.LockTest.testQueryTimeout*"
elif [ "$RDBMS" == "db2" ]; then
goal="-Pdb=db2_ci"
@ -24,8 +30,27 @@ elif [ "$RDBMS" == "mssql" ]; then
goal="-Pdb=mssql_ci"
elif [ "$RDBMS" == "hana" ]; then
goal="-Pdb=hana_ci"
elif [ "$RDBMS" == "hana_jenkins" ]; then
goal="-Pdb=hana_jenkins"
elif [ "$RDBMS" == "sybase" ]; then
goal="-Pdb=sybase_ci"
elif [ "$RDBMS" == "tidb" ]; then
goal="-Pdb=tidb"
elif [ "$RDBMS" == "cockroachdb" ]; then
goal="-Pdb=cockroachdb"
fi
exec ./gradlew check ${goal} -Plog-test-progress=true --stacktrace
# Only run checkstyle in the H2 build,
# so that CI jobs give a more complete report
# and developers can fix code style and non-H2 DB tests in parallel.
if [ -n "$goal" ]; then
goal="$goal -x checkstyleMain"
fi
function logAndExec() {
echo 1>&2 "Executing:" "${@}"
exec "${@}"
}
# Clean by default otherwise the PackagedEntityManager tests fail on a node that previously ran a different DB
logAndExec ./gradlew clean check ${goal} "${@}" -Plog-test-progress=true --stacktrace

View File

@ -22,4 +22,6 @@ elif [ "$RDBMS" == 'hana' ]; then
bash $DIR/../docker_db.sh hana
elif [ "$RDBMS" == 'sybase' ]; then
bash $DIR/../docker_db.sh sybase
elif [ "$RDBMS" == 'cockroachdb' ]; then
bash $DIR/../docker_db.sh cockroachdb
fi

View File

@ -485,8 +485,16 @@ hana() {
cockroachdb() {
$CONTAINER_CLI rm -f cockroach || true
$CONTAINER_CLI run -d --name=cockroach -p 26257:26257 -p 8080:8080 docker.io/cockroachdb/cockroach:v21.2.10 start-single-node \
--insecure --store=type=mem,size=0.25 --advertise-addr=localhost
LOG_CONFIG="
sinks:
stderr:
channels: all
filter: ERROR
redact: false
exit-on-error: true
"
$CONTAINER_CLI run -d --name=cockroach -m 3g -p 26257:26257 -p 8080:8080 docker.io/cockroachdb/cockroach:v21.2.16 start-single-node \
--insecure --store=type=mem,size=640MiB --advertise-addr=localhost --log="$LOG_CONFIG"
OUTPUT=
while [[ $OUTPUT != *"CockroachDB node starting"* ]]; do
echo "Waiting for CockroachDB to start..."
@ -494,9 +502,9 @@ cockroachdb() {
# Note we need to redirect stderr to stdout to capture the logs
OUTPUT=$($CONTAINER_CLI logs cockroach 2>&1)
done
echo "Enabling experimental box2d operators and some ptimized settings for running the tests"
echo "Enabling experimental box2d operators and some optimized settings for running the tests"
#settings documented in https://www.cockroachlabs.com/docs/v21.2/local-testing.html#use-a-local-single-node-cluster-with-in-memory-storage
$CONTAINER_CLI exec -it cockroach bash -c "cat <<EOF | ./cockroach sql --insecure
$CONTAINER_CLI exec cockroach bash -c "cat <<EOF | ./cockroach sql --insecure
SET CLUSTER SETTING sql.spatial.experimental_box2d_comparison_operators.enabled = on;
SET CLUSTER SETTING kv.raft_log.disable_synchronization_unsafe = true;
SET CLUSTER SETTING kv.range_merge.queue_interval = '50ms';
@ -506,8 +514,12 @@ SET CLUSTER SETTING jobs.retention_time = '15s';
SET CLUSTER SETTING schemachanger.backfiller.buffer_increment = '128 KiB';
SET CLUSTER SETTING sql.stats.automatic_collection.enabled = false;
SET CLUSTER SETTING kv.range_split.by_load_merge_delay = '5s';
ALTER RANGE default CONFIGURE ZONE USING "gc.ttlseconds" = 5;
ALTER DATABASE system CONFIGURE ZONE USING "gc.ttlseconds" = 5;
SET CLUSTER SETTING timeseries.storage.enabled = false;
SET CLUSTER SETTING timeseries.storage.resolution_10s.ttl = '0s';
SET CLUSTER SETTING timeseries.storage.resolution_30m.ttl = '0s';
ALTER RANGE default CONFIGURE ZONE USING \"gc.ttlseconds\" = 10;
ALTER DATABASE system CONFIGURE ZONE USING \"gc.ttlseconds\" = 10;
ALTER DATABASE defaultdb CONFIGURE ZONE USING \"gc.ttlseconds\" = 10;
quit
EOF
"

View File

@ -17,6 +17,7 @@
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
@ -39,34 +40,35 @@ public void verifyMappings(SessionFactoryScope scope) {
final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final JdbcTypeRegistry jdbcTypeRegistry = mappingMetamodel.getTypeConfiguration().getJdbcTypeRegistry();
final EntityPersister entityDescriptor = mappingMetamodel.getEntityDescriptor(EntityOfByteArrays.class);
{
final BasicAttributeMapping primitive = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("primitive");
final JdbcMapping jdbcMapping = primitive.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(byte[].class));
assertThat( jdbcMapping.getJdbcType().getJdbcTypeCode(), equalTo( Types.VARBINARY));
assertThat( jdbcMapping.getJdbcType(), equalTo( jdbcTypeRegistry.getDescriptor( Types.VARBINARY ) ) );
}
{
final BasicAttributeMapping primitive = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapper");
final JdbcMapping jdbcMapping = primitive.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(Byte[].class));
assertThat( jdbcMapping.getJdbcType().getJdbcTypeCode(), equalTo( Types.VARBINARY));
assertThat( jdbcMapping.getJdbcType(), equalTo( jdbcTypeRegistry.getDescriptor( Types.VARBINARY ) ) );
}
{
final BasicAttributeMapping primitive = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("primitiveLob");
final JdbcMapping jdbcMapping = primitive.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(byte[].class));
assertThat( jdbcMapping.getJdbcType().getJdbcTypeCode(), equalTo( Types.BLOB));
assertThat( jdbcMapping.getJdbcType(), equalTo( jdbcTypeRegistry.getDescriptor( Types.BLOB ) ) );
}
{
final BasicAttributeMapping primitive = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapperLob");
final JdbcMapping jdbcMapping = primitive.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(Byte[].class));
assertThat( jdbcMapping.getJdbcType().getJdbcTypeCode(), equalTo( Types.BLOB));
assertThat( jdbcMapping.getJdbcType(), equalTo( jdbcTypeRegistry.getDescriptor( Types.BLOB ) ) );
}
scope.inTransaction(

View File

@ -144,36 +144,43 @@ protected SessionFactory sessionFactory(Map<String, Object> settings) {
HibernateSchemaManagementTool tool = new HibernateSchemaManagementTool();
tool.injectServices(serviceRegistry);
final GenerationTargetToDatabase frontEndSchemaGenerator = new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
connectionProviderMap.get(FRONT_END_TENANT)
)
);
final GenerationTargetToDatabase backEndSchemaGenerator = new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
connectionProviderMap.get(BACK_END_TENANT)
)
);
new SchemaDropperImpl(serviceRegistry).doDrop(
new SchemaDropperImpl( serviceRegistry ).doDrop(
metadata,
serviceRegistry,
settings,
true,
frontEndSchemaGenerator,
backEndSchemaGenerator
);
new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
connectionProviderMap.get( FRONT_END_TENANT )
)
),
new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
connectionProviderMap.get( BACK_END_TENANT )
)
)
);
new SchemaCreatorImpl(serviceRegistry).doCreation(
new SchemaCreatorImpl( serviceRegistry ).doCreation(
metadata,
serviceRegistry,
settings,
true,
frontEndSchemaGenerator,
backEndSchemaGenerator
);
new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
connectionProviderMap.get( FRONT_END_TENANT )
)
),
new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
connectionProviderMap.get( BACK_END_TENANT )
)
)
);
final SessionFactoryBuilder sessionFactoryBuilder = metadata.getSessionFactoryBuilder();
return sessionFactoryBuilder.build();

View File

@ -232,36 +232,43 @@ protected SessionFactory sessionFactory(Map<String, Object> settings) {
HibernateSchemaManagementTool tool = new HibernateSchemaManagementTool();
tool.injectServices(serviceRegistry);
final GenerationTargetToDatabase frontEndSchemaGenerator = new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
connectionProviderMap.get(FRONT_END_TENANT)
)
);
final GenerationTargetToDatabase backEndSchemaGenerator = new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
connectionProviderMap.get(BACK_END_TENANT)
)
);
new SchemaDropperImpl(serviceRegistry).doDrop(
new SchemaDropperImpl( serviceRegistry ).doDrop(
metadata,
serviceRegistry,
settings,
true,
frontEndSchemaGenerator,
backEndSchemaGenerator
);
new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
connectionProviderMap.get( FRONT_END_TENANT )
)
),
new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
connectionProviderMap.get( BACK_END_TENANT )
)
)
);
new SchemaCreatorImpl(serviceRegistry).doCreation(
new SchemaCreatorImpl( serviceRegistry ).doCreation(
metadata,
serviceRegistry,
settings,
true,
frontEndSchemaGenerator,
backEndSchemaGenerator
);
new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
connectionProviderMap.get( FRONT_END_TENANT )
)
),
new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
connectionProviderMap.get( BACK_END_TENANT )
)
)
);
final SessionFactoryBuilder sessionFactoryBuilder = metadata.getSessionFactoryBuilder();
return sessionFactoryBuilder.build();

View File

@ -132,15 +132,25 @@ xjc {
}
}
task copyBundleResources (type: Copy) {
task copyBundleResourcesXml (type: Copy) {
inputs.property( "db", db )
ext {
bundlesTargetDir = file( "${buildDir}/bundles" )
bundleTokens = dbBundle[db]
// Escape
bundleTokens = [
'db.dialect' : dbBundle[db]['db.dialect'].replace("&", "&amp;"),
'jdbc.driver' : dbBundle[db]['jdbc.driver'].replace("&", "&amp;"),
'jdbc.user' : dbBundle[db]['jdbc.user'].replace("&", "&amp;"),
'jdbc.pass' : dbBundle[db]['jdbc.pass'].replace("&", "&amp;"),
'jdbc.url' : dbBundle[db]['jdbc.url'].replace("&", "&amp;"),
'connection.init_sql' : dbBundle[db]['connection.init_sql'].replace("&", "&amp;")
]
ext.bundleTokens['buildDirName'] = project.relativePath( buildDir )
}
from file('src/test/bundles/templates')
from('src/test/bundles/templates') {
include '**/*.xml'
}
into ext.bundlesTargetDir
filter( ReplaceTokens, tokens: ext.bundleTokens)
@ -149,6 +159,32 @@ task copyBundleResources (type: Copy) {
}
}
task copyBundleResourcesNonXml (type: Copy) {
inputs.property( "db", db )
ext {
bundlesTargetDir = file( "${buildDir}/bundles" )
// Escape
bundleTokens = dbBundle[db]
ext.bundleTokens['buildDirName'] = project.relativePath( buildDir )
}
from('src/test/bundles/templates') {
exclude '**/*.xml'
}
into ext.bundlesTargetDir
filter( ReplaceTokens, tokens: ext.bundleTokens)
doFirst {
ext.bundlesTargetDir.mkdirs()
}
}
task copyBundleResources (type: Copy) {
inputs.property( "db", db )
dependsOn tasks.copyBundleResourcesXml
dependsOn tasks.copyBundleResourcesNonXml
}
processTestResources {
dependsOn copyBundleResources
duplicatesStrategy = DuplicatesStrategy.WARN

View File

@ -24,6 +24,8 @@
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.PessimisticLockException;
import org.hibernate.QueryTimeoutException;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.function.FormatFunction;
@ -39,17 +41,16 @@
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.exception.LockAcquisitionException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.IntervalType;
import org.hibernate.query.sqm.NullOrdering;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.query.sqm.mutation.internal.cte.CteInsertStrategy;
import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
@ -58,6 +59,7 @@
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.type.JavaObjectType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantAsTimestampWithTimeZoneJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType;
@ -68,11 +70,14 @@
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
import org.hibernate.type.descriptor.sql.internal.Scale6IntervalSecondDdlType;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;
import org.jboss.logging.Logger;
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
import static org.hibernate.query.sqm.TemporalUnit.DAY;
import static org.hibernate.query.sqm.TemporalUnit.NATIVE;
import static org.hibernate.type.SqlTypes.ARRAY;
import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.BLOB;
import static org.hibernate.type.SqlTypes.CHAR;
@ -80,6 +85,7 @@
import static org.hibernate.type.SqlTypes.GEOGRAPHY;
import static org.hibernate.type.SqlTypes.GEOMETRY;
import static org.hibernate.type.SqlTypes.INET;
import static org.hibernate.type.SqlTypes.INTEGER;
import static org.hibernate.type.SqlTypes.JSON;
import static org.hibernate.type.SqlTypes.LONG32NVARCHAR;
import static org.hibernate.type.SqlTypes.LONG32VARBINARY;
@ -183,12 +189,13 @@ protected String columnType(int sqlTypeCode) {
switch ( sqlTypeCode ) {
case TINYINT:
return "smallint"; //no tinyint
case INTEGER:
return "int4";
case CHAR:
case NCHAR:
case VARCHAR:
return columnType( CHAR );
case NVARCHAR:
return "string($l)";
return columnType( VARCHAR );
case NCLOB:
case CLOB:
@ -280,10 +287,46 @@ public JdbcType resolveSqlTypeDescriptor(
jdbcTypeCode = TIMESTAMP_UTC;
}
break;
case ARRAY:
final JdbcType jdbcType = jdbcTypeRegistry.getDescriptor( jdbcTypeCode );
// PostgreSQL names array types by prepending an underscore to the base name
if ( jdbcType instanceof ArrayJdbcType && columnTypeName.charAt( 0 ) == '_' ) {
final String componentTypeName = columnTypeName.substring( 1 );
final Integer sqlTypeCode = resolveSqlTypeCode( componentTypeName, jdbcTypeRegistry.getTypeConfiguration() );
if ( sqlTypeCode != null ) {
return ( (ArrayJdbcType) jdbcType ).resolveType(
jdbcTypeRegistry.getTypeConfiguration(),
this,
jdbcTypeRegistry.getDescriptor( sqlTypeCode ),
null
);
}
}
return jdbcType;
}
return jdbcTypeRegistry.getDescriptor( jdbcTypeCode );
}
@Override
protected Integer resolveSqlTypeCode(String columnTypeName, TypeConfiguration typeConfiguration) {
switch ( columnTypeName ) {
case "bool":
return Types.BOOLEAN;
case "float4":
// Use REAL instead of FLOAT to get Float as recommended Java type
return Types.REAL;
case "float8":
return Types.DOUBLE;
case "int2":
return Types.SMALLINT;
case "int4":
return Types.INTEGER;
case "int8":
return Types.BIGINT;
}
return super.resolveSqlTypeCode( columnTypeName, typeConfiguration );
}
@Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes( typeContributions, serviceRegistry );
@ -309,6 +352,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
// Force Blob binding to byte[] for CockroachDB
jdbcTypeRegistry.addDescriptor( Types.BLOB, VarbinaryJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( Types.CLOB, VarcharJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( Types.NCLOB, VarcharJdbcType.INSTANCE );
// The next two contributions are the same as for Postgresql
typeContributions.contributeJdbcType( ObjectNullAsBinaryTypeJdbcType.INSTANCE );
@ -335,6 +379,7 @@ public void initializeFunctionRegistry(QueryEngine queryEngine) {
functionFactory.position();
functionFactory.substringFromFor();
functionFactory.locate_positionSubstring();
functionFactory.concat_pipeOperator();
functionFactory.trim2();
functionFactory.substr();
functionFactory.reverse();
@ -349,6 +394,20 @@ public void initializeFunctionRegistry(QueryEngine queryEngine) {
functionFactory.radians();
functionFactory.pi();
functionFactory.trunc(); //TODO: emulate second arg
functionFactory.log();
functionFactory.log10_log();
functionFactory.bitandorxornot_operator();
functionFactory.bitAndOr();
functionFactory.everyAny_boolAndOr();
functionFactory.median_percentileCont_castDouble();
functionFactory.stddev();
functionFactory.stddevPopSamp();
functionFactory.variance();
functionFactory.varPopSamp();
functionFactory.covarPopSamp();
functionFactory.corr();
functionFactory.regrLinearRegressionAggregates();
queryEngine.getSqmFunctionRegistry().register(
"format",
@ -357,7 +416,7 @@ public void initializeFunctionRegistry(QueryEngine queryEngine) {
functionFactory.windowFunctions();
functionFactory.listagg_stringAgg( "string" );
functionFactory.inverseDistributionOrderedSetAggregates();
functionFactory.hypotheticalOrderedSetAggregates();
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
}
@Override
@ -597,8 +656,6 @@ public String extractPattern(TemporalUnit unit) {
switch ( unit ) {
case DAY_OF_WEEK:
return "(" + super.extractPattern(unit) + "+1)";
case SECOND:
return "(extract(second from ?2)+extract(microsecond from ?2)/1e6)";
default:
return super.extractPattern(unit);
}
@ -788,28 +845,28 @@ public String getReadLockString(String aliases, int timeout) {
@Override
public String getForUpdateNowaitString() {
return supportsNoWait()
? " for update nowait"
? getForUpdateString() + " nowait"
: getForUpdateString();
}
@Override
public String getForUpdateNowaitString(String aliases) {
return supportsNoWait()
? " for update of " + aliases + " nowait"
: getForUpdateString(aliases);
? getForUpdateString( aliases ) + " nowait"
: getForUpdateString( aliases );
}
@Override
public String getForUpdateSkipLockedString() {
return supportsSkipLocked()
? " for update skip locked"
? getForUpdateString() + " skip locked"
: getForUpdateString();
}
@Override
public String getForUpdateSkipLockedString(String aliases) {
return supportsSkipLocked()
? " for update of " + aliases + " skip locked"
? getForUpdateString( aliases ) + " skip locked"
: getForUpdateString( aliases );
}
@ -850,7 +907,8 @@ public boolean supportsWait() {
@Override
public boolean supportsSkipLocked() {
return getVersion().isSameOrAfter( 20, 1 );
// See https://www.cockroachlabs.com/docs/stable/select-for-update.html#wait-policies
return false;
}
@Override
@ -878,16 +936,78 @@ public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, D
}
@Override
public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy(
EntityMappingType rootEntityDescriptor,
RuntimeModelCreationContext runtimeModelCreationContext) {
return new CteMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext );
public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
return EXTRACTOR;
}
/**
* Constraint-name extractor for Postgres constraint violation exceptions.
* Originally contributed by Denny Bartelt.
*/
private static final ViolatedConstraintNameExtractor EXTRACTOR =
new TemplatedViolatedConstraintNameExtractor( sqle -> {
final String sqlState = JdbcExceptionHelper.extractSqlState( sqle );
if ( sqlState == null ) {
return null;
}
switch ( Integer.parseInt( sqlState ) ) {
// CHECK VIOLATION
case 23514:
return extractUsingTemplate( "violates check constraint \"","\"", sqle.getMessage() );
// UNIQUE VIOLATION
case 23505:
return extractUsingTemplate( "violates unique constraint \"","\"", sqle.getMessage() );
// FOREIGN KEY VIOLATION
case 23503:
return extractUsingTemplate( "violates foreign key constraint \"","\"", sqle.getMessage() );
// NOT NULL VIOLATION
case 23502:
return extractUsingTemplate( "null value in column \"","\" violates not-null constraint", sqle.getMessage() );
// TODO: RESTRICT VIOLATION
case 23001:
return null;
// ALL OTHER
default:
return null;
}
} );
@Override
public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy(
EntityMappingType rootEntityDescriptor,
RuntimeModelCreationContext runtimeModelCreationContext) {
return new CteInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext );
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return (sqlException, message, sql) -> {
final String sqlState = JdbcExceptionHelper.extractSqlState( sqlException );
if ( sqlState == null ) {
return null;
}
switch ( sqlState ) {
case "40P01":
// DEADLOCK DETECTED
return new LockAcquisitionException( message, sqlException, sql);
case "55P03":
// LOCK NOT AVAILABLE
return new PessimisticLockException( message, sqlException, sql);
case "57014":
return new QueryTimeoutException( message, sqlException, sql );
default:
// returning null allows other delegates to operate
return null;
}
};
}
// CockroachDB doesn't support this by default. See sql.multiple_modifications_of_table.enabled
//
// @Override
// public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy(
// EntityMappingType rootEntityDescriptor,
// RuntimeModelCreationContext runtimeModelCreationContext) {
// return new CteMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext );
// }
//
// @Override
// public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy(
// EntityMappingType rootEntityDescriptor,
// RuntimeModelCreationContext runtimeModelCreationContext) {
// return new CteInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext );
// }
}

View File

@ -576,9 +576,7 @@ public JdbcType resolveSqlTypeDescriptor(
if ( sqlTypeCode != null ) {
return ( (ArrayJdbcType) jdbcType ).resolveType(
jdbcTypeRegistry.getTypeConfiguration(),
jdbcTypeRegistry.getTypeConfiguration().getServiceRegistry()
.getService( JdbcServices.class )
.getDialect(),
this,
jdbcTypeRegistry.getDescriptor( sqlTypeCode ),
null
);

View File

@ -41,7 +41,6 @@
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.exception.LockAcquisitionException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
@ -310,9 +309,7 @@ public JdbcType resolveSqlTypeDescriptor(
if ( sqlTypeCode != null ) {
return ( (ArrayJdbcType) jdbcType ).resolveType(
jdbcTypeRegistry.getTypeConfiguration(),
jdbcTypeRegistry.getTypeConfiguration().getServiceRegistry()
.getService( JdbcServices.class )
.getDialect(),
this,
jdbcTypeRegistry.getDescriptor( sqlTypeCode ),
null
);

View File

@ -239,6 +239,20 @@ public void median_percentileCont(boolean over) {
.register();
}
/**
* CockroachDB lacks implicit casting: https://github.com/cockroachdb/cockroach/issues/89965
*/
public void median_percentileCont_castDouble() {
functionRegistry.patternDescriptorBuilder(
"median",
"percentile_cont(0.5) within group (order by cast(?1 as double precision))"
)
.setInvariantType(doubleType)
.setExactArgumentCount( 1 )
.setParameterTypes(NUMERIC)
.register();
}
/**
* Warning: the semantics of this function are inconsistent between DBs.
*

View File

@ -8,10 +8,13 @@
import java.sql.Types;
import org.hibernate.MappingException;
public class CockroachDBIdentityColumnSupport extends IdentityColumnSupportImpl {
@Override
public boolean supportsIdentityColumns() {
// Full support requires setting the sql.defaults.serial_normalization=sql_sequence in CockroachDB.
// Also, support for serial4 is not enabled by default: https://github.com/cockroachdb/cockroach/issues/26925#issuecomment-1255293916
return false;
}
@ -23,9 +26,17 @@ public String getIdentitySelectString(String table, String column, int type) {
@Override
public String getIdentityColumnString(int type) {
return type == Types.SMALLINT ?
"serial4 not null" :
"serial8 not null";
switch ( type ) {
case Types.TINYINT:
case Types.SMALLINT:
return "serial2 not null";
case Types.INTEGER:
return "serial4 not null";
case Types.BIGINT:
return "serial8 not null";
default:
throw new MappingException( "illegal identity column type");
}
}
@Override

View File

@ -101,10 +101,8 @@ public String getSqlTruncateCommand(
Function<SharedSessionContractImplementor, String> sessionUidAccess,
SharedSessionContractImplementor session) {
if ( idTable.getSessionUidColumn() != null ) {
assert sessionUidAccess != null;
final String uid = sessionUidAccess.apply( session );
return getTruncateTableCommand() + " " + idTable.getQualifiedTableName()
+ " where " + idTable.getSessionUidColumn().getColumnName() + " = " + uid;
+ " where " + idTable.getSessionUidColumn().getColumnName() + " = ?";
}
else {
return getTruncateTableCommand() + " " + idTable.getQualifiedTableName();

View File

@ -7,6 +7,7 @@
package org.hibernate.dialect.temptable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@ -24,6 +25,7 @@
import org.hibernate.id.enhanced.Optimizer;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Contributable;
@ -39,7 +41,10 @@
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.SingleTableEntityPersister;
import org.hibernate.type.BasicType;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.spi.TypeConfiguration;
/**
@ -70,12 +75,25 @@ private TemporaryTable(
SqlStringGenerationContext sqlStringGenerationContext,
Function<TemporaryTable, List<TemporaryTableColumn>> columnInitializer) {
this.entityDescriptor = entityDescriptor;
final EntityPersister entityPersister = entityDescriptor.getEntityPersister();
final EntityPersister rootEntityPersister = entityDescriptor.getRootEntityDescriptor().getEntityPersister();
final String persisterQuerySpace = entityPersister.getSynchronizedQuerySpaces()[0];
final QualifiedNameParser.NameParts nameParts = QualifiedNameParser.INSTANCE.parse( persisterQuerySpace );
// The table name might be a sub-query, which is inappropriate for a temporary table name
final String originalTableName = entityDescriptor.getEntityPersister().getSynchronizedQuerySpaces()[0];
final QualifiedNameParser.NameParts nameParts = QualifiedNameParser.INSTANCE.parse( originalTableName );
final String tableBaseName;
if ( rootEntityPersister != entityPersister && rootEntityPersister instanceof SingleTableEntityPersister ) {
// In this case, the descriptor is a subclass of a single table inheritance.
// To avoid name collisions, we suffix the table name with the subclass number
tableBaseName = nameParts.getObjectName().getText() + ArrayHelper.indexOf(
( (SingleTableEntityPersister) rootEntityPersister ).getSubclassClosure(),
entityPersister.getEntityName()
);
}
else {
tableBaseName = nameParts.getObjectName().getText();
}
final QualifiedNameParser.NameParts adjustedNameParts = QualifiedNameParser.INSTANCE.parse(
temporaryTableNameAdjuster.apply( nameParts.getObjectName().getText() )
temporaryTableNameAdjuster.apply( tableBaseName )
);
final String temporaryTableName = adjustedNameParts.getObjectName().getText();
final Identifier tableNameIdentifier;
@ -101,11 +119,12 @@ private TemporaryTable(
);
this.dialect = dialect;
if ( dialect.getSupportedTemporaryTableKind() == TemporaryTableKind.PERSISTENT ) {
final TypeConfiguration typeConfiguration = entityDescriptor.getEntityPersister()
final TypeConfiguration typeConfiguration = entityPersister
.getFactory()
.getTypeConfiguration();
final BasicType<UUID> uuidType = typeConfiguration
.getBasicTypeForJavaType( UUID.class );
final BasicType<UUID> uuidType = typeConfiguration.getBasicTypeRegistry().resolve(
StandardBasicTypes.UUID_CHAR
);
this.sessionUidColumn = new TemporaryTableSessionUidColumn(
this,
uuidType,

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.query.sqm.mutation.internal.temptable;
import java.util.UUID;
import java.util.function.Function;
import org.hibernate.LockMode;
@ -127,7 +128,7 @@ public static int saveMatchingIdsIntoIdTable(
jdbcPosition,
jdbcPosition + 1,
new QueryLiteral<>(
sessionUidAccess.apply( executionContext.getSession() ),
UUID.fromString( sessionUidAccess.apply( executionContext.getSession() ) ),
(BasicValuedMapping) idTable.getSessionUidColumn().getJdbcMapping()
)
)
@ -293,7 +294,7 @@ private static void applyIdTableRestrictions(
),
ComparisonOperator.EQUAL,
new QueryLiteral<>(
sessionUidAccess.apply( executionContext.getSession() ),
UUID.fromString( sessionUidAccess.apply( executionContext.getSession() ) ),
(BasicValuedMapping) idTable.getSessionUidColumn().getJdbcMapping()
)
)

View File

@ -13,6 +13,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
import org.hibernate.dialect.temptable.TemporaryTable;
@ -55,6 +56,7 @@
import org.hibernate.sql.ast.tree.from.UnionTableReference;
import org.hibernate.sql.ast.tree.insert.InsertStatement;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.ast.tree.select.SortSpecification;
@ -90,6 +92,7 @@ public class InsertExecutionDelegate implements TableBasedInsertHandler.Executio
private final EntityMappingType entityDescriptor;
private final JdbcParameterBindings jdbcParameterBindings;
private final JdbcParameter sessionUidParameter;
private final Map<TableReference, List<Assignment>> assignmentsByTable;
private final Map<SqmParameter<?>, MappingModelExpressible<?>> paramTypeResolutions;
@ -107,6 +110,7 @@ public InsertExecutionDelegate(
List<Assignment> assignments,
InsertStatement insertStatement,
Map<SqmParameter<?>, List<List<JdbcParameter>>> parameterResolutions,
JdbcParameter sessionUidParameter,
Map<SqmParameter<?>, MappingModelExpressible<?>> paramTypeResolutions,
DomainQueryExecutionContext executionContext) {
this.sqmInsert = sqmInsert;
@ -116,6 +120,7 @@ public InsertExecutionDelegate(
this.sessionUidAccess = sessionUidAccess;
this.domainParameterXref = domainParameterXref;
this.updatingTableGroup = insertingTableGroup;
this.sessionUidParameter = sessionUidParameter;
this.paramTypeResolutions = paramTypeResolutions;
this.insertStatement = insertStatement;
@ -189,6 +194,15 @@ public int execute(ExecutionContext executionContext) {
);
try {
if ( sessionUidParameter != null ) {
jdbcParameterBindings.addBinding(
sessionUidParameter,
new JdbcParameterBindingImpl(
entityTable.getSessionUidColumn().getJdbcMapping(),
UUID.fromString( sessionUidAccess.apply( executionContext.getSession() ) )
)
);
}
final int rows = ExecuteWithTemporaryTableHelper.saveIntoTemporaryTable(
insertStatement,
jdbcParameterBindings,
@ -422,24 +436,53 @@ private void insertRootTable(
rootIdentity
)
);
final TemporaryTableColumn rowNumberColumn = entityTable.getColumns().get(
entityTable.getColumns().size() - 1
);
final TemporaryTableColumn rowNumberColumn;
final TemporaryTableColumn sessionUidColumn;
final Predicate sessionUidPredicate;
if ( entityTable.getSessionUidColumn() == null ) {
rowNumberColumn = entityTable.getColumns().get(
entityTable.getColumns().size() - 1
);
sessionUidColumn = null;
sessionUidPredicate = null;
}
else {
rowNumberColumn = entityTable.getColumns().get(
entityTable.getColumns().size() - 2
);
sessionUidColumn = entityTable.getSessionUidColumn();
sessionUidPredicate = new ComparisonPredicate(
new ColumnReference(
(String) null,
sessionUidColumn.getColumnName(),
false,
null,
null,
sessionUidColumn.getJdbcMapping(),
sessionFactory
),
ComparisonOperator.EQUAL,
sessionUidParameter
);
}
final UpdateStatement updateStatement = new UpdateStatement(
temporaryTableReference,
temporaryTableAssignments,
new ComparisonPredicate(
new ColumnReference(
(String) null,
rowNumberColumn.getColumnName(),
false,
null,
null,
rowNumberColumn.getJdbcMapping(),
sessionFactory
),
ComparisonOperator.EQUAL,
rowNumber
Predicate.combinePredicates(
new ComparisonPredicate(
new ColumnReference(
(String) null,
rowNumberColumn.getColumnName(),
false,
null,
null,
rowNumberColumn.getJdbcMapping(),
sessionFactory
),
ComparisonOperator.EQUAL,
rowNumber
),
sessionUidPredicate
)
);
@ -448,6 +491,15 @@ private void insertRootTable(
.buildUpdateTranslator( sessionFactory, updateStatement )
.translate( null, executionContext.getQueryOptions() );
final JdbcParameterBindings updateBindings = new JdbcParameterBindingsImpl( 2 );
if ( sessionUidColumn != null ) {
updateBindings.addBinding(
sessionUidParameter,
new JdbcParameterBindingImpl(
sessionUidColumn.getJdbcMapping(),
UUID.fromString( sessionUidAccess.apply( executionContext.getSession() ) )
)
);
}
for ( int i = 0; i < rows; i++ ) {
updateBindings.addBinding(

View File

@ -108,7 +108,7 @@ public void release(
released = true;
if ( created ) {
if ( !created ) {
return;
}

View File

@ -17,6 +17,7 @@
import org.hibernate.dialect.temptable.TemporaryTable;
import org.hibernate.dialect.temptable.TemporaryTableColumn;
import org.hibernate.dialect.temptable.TemporaryTableSessionUidColumn;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
@ -51,6 +52,7 @@
import org.hibernate.sql.ast.tree.insert.Values;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.update.Assignment;
import org.hibernate.sql.exec.internal.JdbcParameterImpl;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.type.BasicType;
@ -74,6 +76,7 @@ public interface ExecutionDelegate {
private final AfterUseAction afterUseAction;
private final Function<SharedSessionContractImplementor,String> sessionUidAccess;
private final DomainParameterXref domainParameterXref;
private final JdbcParameter sessionUidParameter;
private final EntityPersister entityDescriptor;
@ -93,6 +96,14 @@ public interface ExecutionDelegate {
final String targetEntityName = sqmInsert.getTarget().getEntityName();
this.entityDescriptor = sessionFactory.getRuntimeMetamodels().getMappingMetamodel().getEntityDescriptor( targetEntityName );
final TemporaryTableSessionUidColumn sessionUidColumn = entityTable.getSessionUidColumn();
if ( sessionUidColumn == null ) {
this.sessionUidParameter = null;
}
else {
this.sessionUidParameter = new JdbcParameterImpl( sessionUidColumn.getJdbcMapping() );
}
}
public SqmInsertStatement<?> getSqmInsertStatement() {
@ -173,13 +184,14 @@ private ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionC
// visit the where-clause using our special converter, collecting information
// about the restrictions
final TemporaryTableSessionUidColumn sessionUidColumn = entityTable.getSessionUidColumn();
if ( sqmInsertStatement instanceof SqmInsertSelectStatement ) {
final QueryPart queryPart = converterDelegate.visitQueryPart( ( (SqmInsertSelectStatement<?>) sqmInsertStatement ).getSelectQueryPart() );
queryPart.visitQuerySpecs(
querySpec -> {
if ( additionalInsertValues.applySelections( querySpec, sessionFactory ) ) {
final TemporaryTableColumn rowNumberColumn = entityTable.getColumns()
.get( entityTable.getColumns().size() - 1 );
.get( entityTable.getColumns().size() - ( sessionUidColumn == null ? 1 : 2 ) );
final ColumnReference columnReference = new ColumnReference(
(String) null,
rowNumberColumn.getColumnName(),
@ -205,7 +217,7 @@ else if ( entityDescriptor.getIdentifierGenerator() instanceof OptimizableGenera
return;
}
final TemporaryTableColumn rowNumberColumn = entityTable.getColumns()
.get( entityTable.getColumns().size() - 1 );
.get( entityTable.getColumns().size() - ( sessionUidColumn == null ? 1 : 2 ) );
final ColumnReference columnReference = new ColumnReference(
(String) null,
rowNumberColumn.getColumnName(),
@ -229,6 +241,30 @@ else if ( entityDescriptor.getIdentifierGenerator() instanceof OptimizableGenera
);
}
}
if ( sessionUidColumn != null ) {
final ColumnReference sessionUidColumnReference = new ColumnReference(
(String) null,
sessionUidColumn.getColumnName(),
false,
null,
null,
sessionUidColumn.getJdbcMapping(),
sessionFactory
);
insertStatement.getTargetColumnReferences().add(
sessionUidColumnReference
);
targetPathColumns.add(
new Assignment( sessionUidColumnReference, sessionUidParameter )
);
querySpec.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
insertStatement.getTargetColumnReferences().size(),
insertStatement.getTargetColumnReferences().size() - 1,
sessionUidParameter
)
);
}
}
);
insertStatement.setSourceSelectStatement( queryPart );
@ -262,6 +298,23 @@ else if ( entityDescriptor.getIdentifierGenerator() instanceof OptimizableGenera
else {
rowNumberType = null;
}
if ( sessionUidColumn != null ) {
final ColumnReference sessionUidColumnReference = new ColumnReference(
(String) null,
sessionUidColumn.getColumnName(),
false,
null,
null,
sessionUidColumn.getJdbcMapping(),
sessionFactory
);
insertStatement.getTargetColumnReferences().add(
sessionUidColumnReference
);
targetPathColumns.add(
new Assignment( sessionUidColumnReference, sessionUidParameter )
);
}
final List<SqmValues> sqmValuesList = ( (SqmInsertValuesStatement<?>) sqmInsertStatement ).getValuesList();
final List<Values> valuesList = new ArrayList<>( sqmValuesList.size() );
for ( int i = 0; i < sqmValuesList.size(); i++ ) {
@ -275,6 +328,9 @@ else if ( entityDescriptor.getIdentifierGenerator() instanceof OptimizableGenera
)
);
}
if ( sessionUidParameter != null ) {
values.getExpressions().add( sessionUidParameter );
}
valuesList.add( values );
}
insertStatement.setValuesList( valuesList );
@ -303,6 +359,7 @@ else if ( entityDescriptor.getIdentifierGenerator() instanceof OptimizableGenera
targetPathColumns,
insertStatement,
parameterResolutions,
sessionUidParameter,
paramTypeResolutions,
executionContext
);

View File

@ -107,6 +107,7 @@ public void release() {
catch (Throwable t2) {
suppressed = t2;
}
jdbcConnection = null;
if ( suppressed != null ) {
if ( originalException == null ) {
originalException = suppressed;

View File

@ -19,6 +19,7 @@
import org.hibernate.id.SequenceMismatchStrategy;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
@ -27,6 +28,7 @@
* Verifies that setting {@code AvailableSettings.SEQUENCE_INCREMENT_SIZE_MISMATCH_STRATEGY} to {@code none}
* is going to skip loading the sequence information from the database.
*/
@RequiresDialect( H2Dialect.class )
@TestForIssue( jiraKey = "HHH-14667")
public class SkipLoadingSequenceInformationTest extends BaseCoreFunctionalTestCase {

View File

@ -76,17 +76,17 @@ private void tryBuildingSessionFactory(Class... annotatedClasses) {
}
}
@Entity
@Entity(name = "Building1")
@DiscriminatorValue(DISCRIMINATOR_VALUE) // Duplicated discriminator value in single hierarchy.
public static class Building1 extends Building {
}
@Entity
@Entity(name = "Building2")
@DiscriminatorValue(DISCRIMINATOR_VALUE) // Duplicated discriminator value in single hierarchy.
public static class Building2 extends Building {
}
@Entity
@Entity(name = "Furniture")
@DiscriminatorColumn(name = "entity_type")
@DiscriminatorValue("F")
public static class Furniture {
@ -95,7 +95,7 @@ public static class Furniture {
private Integer id;
}
@Entity
@Entity(name = "Chair")
@DiscriminatorValue(DISCRIMINATOR_VALUE) // Duplicated discriminator value in different hierarchy.
public static class Chair extends Furniture {
}

View File

@ -43,6 +43,7 @@
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableStrategy;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.schema.TargetType;
@ -267,6 +268,7 @@ private StandardServiceRegistry createStandardServiceRegistry(String defaultCata
final BootstrapServiceRegistry bsr = bsrb.build();
final Map<String, Object> settings = new HashMap<>();
settings.put( PersistentTableStrategy.DROP_ID_TABLES, "true" );
settings.put( GlobalTemporaryTableStrategy.DROP_ID_TABLES, "true" );
settings.put( LocalTemporaryTableStrategy.DROP_ID_TABLES, "true" );
if ( !Environment.getProperties().containsKey( Environment.CONNECTION_PROVIDER ) ) {

View File

@ -26,6 +26,8 @@
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -34,6 +36,7 @@
*/
@RunWith(BytecodeEnhancerRunner.class)
@CustomEnhancementContext({ DirtyCheckEnhancementContext.class })
@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class)
public class DirtyCheckPrivateUnMappedCollectionTest extends BaseNonConfigCoreFunctionalTestCase {
boolean skipTest;

View File

@ -92,7 +92,7 @@ protected void addSettings(Map<String,Object> settings) {
settings.put( Environment.USE_SCROLLABLE_RESULTSET, "" + supportsScroll );
}
finally {
connection.close();
cp.closeConnection( connection );
}
}
catch (SQLException ignore) {

View File

@ -529,7 +529,7 @@ public static class Company extends AbstractCompany {
public CompanyInfo info;
}
@Entity
@Entity(name = "PlanItem")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class PlanItem {
@ -546,7 +546,7 @@ public void setId(Integer id) {
}
}
@Entity
@Entity(name = "Task")
@SecondaryTable( name = "Task" )
public class Task extends PlanItem {
@Id

View File

@ -19,6 +19,7 @@
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.annotations.ParamDef;
import org.hibernate.dialect.AbstractHANADialect;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.H2Dialect;
@ -79,6 +80,7 @@ public void testYesNo(SessionFactoryScope scope) {
@SkipForDialect(dialectClass = TiDBDialect.class, reason = "TiDB silently converts a boolean to string types")
@SkipForDialect(dialectClass = SybaseDialect.class, matchSubTypes = true, reason = "Sybase silently converts a boolean to string types")
@SkipForDialect(dialectClass = AbstractHANADialect.class, matchSubTypes = true, reason = "HANA silently converts a boolean to string types")
@SkipForDialect(dialectClass = CockroachDialect.class, matchSubTypes = true, reason = "Cockroach silently converts a boolean to string types")
public void testYesNoMismatch(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final EntityOne loaded = session.byId( EntityOne.class ).load( 1 );

View File

@ -19,6 +19,7 @@
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.id.enhanced.HiLoOptimizer;
import org.hibernate.id.enhanced.Optimizer;
import org.hibernate.id.enhanced.SequenceStyleGenerator;
@ -65,30 +66,48 @@ public void dropDatabaseSequence(SessionFactoryScope scope) {
final ConnectionProvider connectionProvider = scope.getSessionFactory()
.getServiceRegistry()
.getService( ConnectionProvider.class );
scope.inSession(
session -> {
Connection connection = null;
final JdbcConnectionAccess jdbcConnectionAccess = session.getJdbcConnectionAccess();
try {
connection = jdbcConnectionAccess.obtainConnection();
try ( Statement statement = connection.createStatement() ) {
try ( Connection connection = connectionProvider.getConnection();
Statement statement = connection.createStatement() ) {
for ( String dropSequenceStatement : dropSequenceStatements ) {
try {
statement.execute( dropSequenceStatement );
}
catch (SQLException e) {
System.out.printf( "TEST DEBUG : dropping sequence failed [`%s`] - %s", dropSequenceStatement, e.getMessage() );
System.out.println();
e.printStackTrace( System.out );
// ignore
}
}
// Commit in between because CockroachDB fails to drop and commit schema objects in the same transaction
connection.commit();
for ( String dropSequenceStatement : dropSequenceStatements ) {
try {
statement.execute( dropSequenceStatement );
for ( String createSequenceStatement : createSequenceStatements ) {
statement.execute( createSequenceStatement );
}
connection.commit();
}
}
catch (SQLException e) {
fail( e.getMessage() );
}
finally {
if ( connection != null ) {
try {
jdbcConnectionAccess.releaseConnection( connection );
}
catch (SQLException ignore) {
}
}
}
}
catch (SQLException e) {
System.out.printf( "TEST DEBUG : dropping sequence failed [`%s`] - %s", dropSequenceStatement, e.getMessage() );
System.out.println();
e.printStackTrace( System.out );
// ignore
}
}
for ( String createSequenceStatement : createSequenceStatements ) {
statement.execute( createSequenceStatement );
}
connection.commit();
}
catch (SQLException e) {
fail( e.getMessage() );
}
);
}
@Test

View File

@ -31,6 +31,7 @@
import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableStrategy;
import org.hibernate.testing.jdbc.SharedDriverManagerConnectionProviderImpl;
import org.hibernate.testing.junit4.BaseUnitTestCase;
@ -227,6 +228,7 @@ protected Map getConfig() {
config.put( AvailableSettings.ORM_XML_FILES, dds );
}
config.put( PersistentTableStrategy.DROP_ID_TABLES, "true" );
config.put( GlobalTemporaryTableMutationStrategy.DROP_ID_TABLES, "true" );
config.put( LocalTemporaryTableMutationStrategy.DROP_ID_TABLES, "true" );
if ( !config.containsKey( Environment.CONNECTION_PROVIDER ) ) {

View File

@ -6,8 +6,11 @@
*/
package org.hibernate.orm.test.jpa.compliance;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -44,6 +47,7 @@ public void tearDown(EntityManagerFactoryScope scope) {
}
@Test
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "https://github.com/cockroachdb/cockroach/issues/82478")
public void testCriteriaMod(EntityManagerFactoryScope scope) {
scope.inEntityManager(
entityManager -> {

View File

@ -19,8 +19,10 @@
import org.hibernate.graph.GraphParser;
import org.hibernate.graph.GraphSemantic;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -44,6 +46,7 @@
FetchGraphTest.Trigger.class,
FetchGraphTest.FinanceEntity.class
})
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsIdentityColumns.class)
public class FetchGraphTest {
@BeforeEach

View File

@ -11,12 +11,14 @@
import org.hibernate.Transaction;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.orm.test.jpa.model.AbstractJPATest;
import org.hibernate.orm.test.jpa.model.Item;
import org.hibernate.orm.test.jpa.model.MyEntity;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.hibernate.testing.jdbc.SQLServerSnapshotIsolationConnectionProvider;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
@ -82,6 +84,7 @@ protected void tearDown() {
* EJB3 LockModeType.READ actually maps to the Hibernate LockMode.OPTIMISTIC
*/
@Test
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "Cockroach uses SERIALIZABLE by default and fails to acquire a write lock after a TX in between committed changes to a row")
public void testLockModeTypeRead() {
if ( !readCommittedIsolationMaintained( "ejb3 lock tests" ) ) {
return;
@ -176,6 +179,7 @@ public void testLockModeTypeRead() {
* lock(entity, LockModeType.WRITE) on non-versioned objects will not be portable.
*/
@Test
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "Cockroach uses SERIALIZABLE by default and fails to acquire a write lock after a TX in between committed changes to a row")
public void testLockModeTypeWrite() {
if ( !readCommittedIsolationMaintained( "ejb3 lock tests" ) ) {
return;

View File

@ -11,6 +11,7 @@
import org.hibernate.LockOptions;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.orm.test.jpa.model.AbstractJPATest;
@ -20,6 +21,7 @@
import org.hibernate.testing.jdbc.SQLServerSnapshotIsolationConnectionProvider;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.hibernate.testing.transaction.TransactionUtil2;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
@ -55,6 +57,7 @@ protected void tearDown() {
@Test
@TestForIssue( jiraKey = "HHH-8786" )
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "for update clause does not imply locking. See https://github.com/cockroachdb/cockroach/issues/88995")
public void testLockTimeoutFind() {
final Item item = new Item( "find" );
@ -95,6 +98,7 @@ public void testLockTimeoutFind() {
}
@Test
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "Cockroach uses SERIALIZABLE by default and seems to fail reading a row that is exclusively locked by a different TX")
public void testLockTimeoutRefresh() {
final Item item = new Item( "refresh" );
@ -136,6 +140,7 @@ public void testLockTimeoutRefresh() {
}
@Test
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "Cockroach uses SERIALIZABLE by default and seems to fail reading a row that is exclusively locked by a different TX")
public void testLockTimeoutLock() {
final Item item = new Item( "lock" );

View File

@ -99,6 +99,7 @@ public void testFindWithTimeoutHint() {
@RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class,
comment = "Test verifies proper exception throwing when a lock timeout is specified.",
jiraKey = "HHH-7252" )
@SkipForDialect(value = CockroachDialect.class, comment = "for update clause does not imply locking. See https://github.com/cockroachdb/cockroach/issues/88995")
public void testFindWithPessimisticWriteLockTimeoutException() {
Lock lock = new Lock();
lock.setName( "name" );
@ -146,6 +147,7 @@ public void testFindWithPessimisticWriteLockTimeoutException() {
@RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class,
comment = "Test verifies proper exception throwing when a lock timeout is specified for Query#getSingleResult.",
jiraKey = "HHH-13364" )
@SkipForDialect(value = CockroachDialect.class, comment = "for update clause does not imply locking. See https://github.com/cockroachdb/cockroach/issues/88995")
public void testQuerySingleResultPessimisticWriteLockTimeoutException() {
Lock lock = new Lock();
lock.setName( "name" );
@ -190,6 +192,7 @@ public void testQuerySingleResultPessimisticWriteLockTimeoutException() {
@RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class,
comment = "Test verifies proper exception throwing when a lock timeout is specified for Query#getResultList.",
jiraKey = "HHH-13364" )
@SkipForDialect(value = CockroachDialect.class, comment = "for update clause does not imply locking. See https://github.com/cockroachdb/cockroach/issues/88995")
public void testQueryResultListPessimisticWriteLockTimeoutException() {
Lock lock = new Lock();
lock.setName( "name" );
@ -237,6 +240,7 @@ public void testQueryResultListPessimisticWriteLockTimeoutException() {
@RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class,
comment = "Test verifies proper exception throwing when a lock timeout is specified for NamedQuery#getResultList.",
jiraKey = "HHH-13364" )
@SkipForDialect(value = CockroachDialect.class, comment = "for update clause does not imply locking. See https://github.com/cockroachdb/cockroach/issues/88995")
public void testNamedQueryResultListPessimisticWriteLockTimeoutException() {
Lock lock = new Lock();
lock.setName( "name" );
@ -278,6 +282,7 @@ public void testNamedQueryResultListPessimisticWriteLockTimeoutException() {
@Test
@RequiresDialectFeature( value = DialectChecks.SupportSkipLocked.class )
@SkipForDialect(value = CockroachDialect.class, comment = "for update clause does not imply locking. See https://github.com/cockroachdb/cockroach/issues/88995")
public void testUpdateWithPessimisticReadLockSkipLocked() {
Lock lock = new Lock();
lock.setName( "name" );
@ -323,6 +328,7 @@ public void testUpdateWithPessimisticReadLockSkipLocked() {
@Test
@RequiresDialectFeature(value = DialectChecks.SupportsLockTimeouts.class)
@SkipForDialect(value = CockroachDialect.class, comment = "for update clause does not imply locking. See https://github.com/cockroachdb/cockroach/issues/88995")
public void testUpdateWithPessimisticReadLockWithoutNoWait() {
Lock lock = new Lock();
lock.setName( "name" );
@ -1168,6 +1174,7 @@ public void testLockTimeoutEMProps() throws Exception {
}, strictMatching = true, comment = "With InnoDB, a FK constraint check acquires a shared lock that isn't compatible with an exclusive lock")
@SkipForDialect(value = HSQLDialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks")
@SkipForDialect(value = AbstractHANADialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks")
@SkipForDialect(value = CockroachDialect.class, comment = "Cockroach supports the 'for no key update' syntax but it doesn't work")
public void testLockInsertFkTarget() {
Lock lock = new Lock();
lock.setName( "name" );
@ -1204,6 +1211,7 @@ public void testLockInsertFkTarget() {
}, strictMatching = true, comment = "With InnoDB, a FK constraint check acquires a shared lock that isn't compatible with an exclusive lock")
@SkipForDialect(value = HSQLDialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks")
@SkipForDialect(value = AbstractHANADialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks")
@SkipForDialect(value = CockroachDialect.class, comment = "Cockroach supports the 'for no key update' syntax but it doesn't work")
public void testLockUpdateFkTarget() {
Lock lock1 = new Lock();
lock1.setName( "l1" );

View File

@ -12,6 +12,7 @@
import org.hibernate.StaleObjectStateException;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.exception.SQLGrammarException;
@ -19,6 +20,7 @@
import org.hibernate.orm.test.jpa.model.Item;
import org.hibernate.orm.test.jpa.model.Part;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.hibernate.testing.jdbc.SQLServerSnapshotIsolationConnectionProvider;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
@ -103,6 +105,7 @@ public void testStaleVersionedInstanceFoundInQueryResult() {
}
@Test
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "Cockroach uses SERIALIZABLE by default and fails to acquire a write lock after a TX in between committed changes to a row")
public void testStaleVersionedInstanceFoundOnLock() {
if ( !readCommittedIsolationMaintained( "repeatable read tests" ) ) {
return;
@ -224,6 +227,7 @@ public void testStaleNonVersionedInstanceFoundInQueryResult() {
}
@Test
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "Cockroach uses SERIALIZABLE by default and fails to acquire a write lock after a TX in between committed changes to a row")
public void testStaleNonVersionedInstanceFoundOnLock() {
if ( !readCommittedIsolationMaintained( "repeatable read tests" ) ) {
return;

View File

@ -33,6 +33,7 @@
* @author Andrea Boriero
*/
@RequiresDialectFeature({DialectChecks.SupportsJdbcDriverProxying.class, DialectChecks.SupportsLockTimeouts.class})
@SkipForDialect(value = CockroachDialect.class, comment = "for update clause does not imply locking. See https://github.com/cockroachdb/cockroach/issues/88995")
public class StatementIsClosedAfterALockExceptionTest extends BaseEntityManagerFunctionalTestCase {
private static final PreparedStatementSpyConnectionProvider CONNECTION_PROVIDER = new PreparedStatementSpyConnectionProvider( false, false );
@ -68,7 +69,6 @@ public void releaseResources() {
@Test(timeout = 1000 * 30) //30 seconds
@TestForIssue(jiraKey = "HHH-11617")
@SkipForDialect( value = CockroachDialect.class )
public void testStatementIsClosed() {
TransactionUtil.doInJPA( this::entityManagerFactory, em1 -> {

View File

@ -26,6 +26,7 @@
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.AbstractHANADialect;
import org.hibernate.dialect.AbstractTransactSQLDialect;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.Dialect;
@ -125,6 +126,7 @@ public void bitType() {
@Test
@SkipForDialect(value = PostgreSQLDialect.class, comment = "Turns tinyints into shorts in result sets and advertises the type as short in the metadata")
@SkipForDialect(value = CockroachDialect.class, comment = "Turns tinyints into shorts in result sets and advertises the type as short in the metadata")
@SkipForDialect(value = DerbyDialect.class, comment = "No support for the tinyint datatype so we use smallint")
@SkipForDialect(value = DB2Dialect.class, comment = "No support for the tinyint datatype so we use smallint")
@SkipForDialect(value = AbstractTransactSQLDialect.class, comment = "No support for the tinyint datatype so we use smallint")

View File

@ -35,6 +35,7 @@
import org.hibernate.testing.orm.junit.Setting;
import org.hibernate.testing.orm.junit.SettingProvider;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@ -66,6 +67,14 @@
)
public class CloseEntityManagerWithActiveTransactionTest {
@BeforeAll
public void beforeAll(EntityManagerFactoryScope scope) throws Exception {
// This makes sure that hbm2ddl runs before we start a transaction for a test
// This is important for database that only support SNAPSHOT/SERIALIZABLE isolation,
// because a test transaction still sees the state before the DDL executed
scope.getEntityManagerFactory();
}
@AfterEach
public void tearDown(EntityManagerFactoryScope scope) throws Exception {
TransactionManager transactionManager = TestingJtaPlatformImpl.INSTANCE.getTransactionManager();

View File

@ -41,7 +41,8 @@
* @author Andrea Boriero
*/
@TestForIssue(jiraKey = "HHH-11477")
@RequiresDialects({ @RequiresDialect(PostgreSQLDialect.class), @RequiresDialect(CockroachDialect.class) })
// Note that Cockroach doesn't support LOB functions. See https://github.com/cockroachdb/cockroach/issues/26725
@RequiresDialect(PostgreSQLDialect.class)
@DomainModel(
annotatedClasses = LobStringTest.TestEntity.class
)

View File

@ -81,6 +81,7 @@ public void prepareTest() throws Exception {
@Test
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsLockTimeouts.class )
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "for update clause does not imply locking. See https://github.com/cockroachdb/cockroach/issues/88995")
@SuppressWarnings( {"deprecation"})
public void testLoading() {
// open a session, begin a transaction and lock row
@ -97,6 +98,7 @@ public void testLoading() {
@Test
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsLockTimeouts.class )
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "for update clause does not imply locking. See https://github.com/cockroachdb/cockroach/issues/88995")
public void testCriteria() {
// open a session, begin a transaction and lock row
doInHibernate( this::sessionFactory, session -> {
@ -119,6 +121,7 @@ public void testCriteria() {
@Test
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsLockTimeouts.class )
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "for update clause does not imply locking. See https://github.com/cockroachdb/cockroach/issues/88995")
public void testCriteriaAliasSpecific() {
// open a session, begin a transaction and lock row
doInHibernate( this::sessionFactory, session -> {
@ -143,6 +146,7 @@ public void testCriteriaAliasSpecific() {
@Test
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsLockTimeouts.class )
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "for update clause does not imply locking. See https://github.com/cockroachdb/cockroach/issues/88995")
public void testQuery() {
// open a session, begin a transaction and lock row
doInHibernate( this::sessionFactory, session -> {
@ -278,19 +282,12 @@ private void nowAttemptToUpdateRow() {
executeSync( () -> doInHibernate( this::sessionFactory, _session -> {
TransactionUtil.setJdbcTimeout( _session );
try {
// We used to load with write lock here to deal with databases that block (wait indefinitely)
// direct attempts to write a locked row.
// At some point, due to a bug, the lock mode was lost when applied via lock options, leading
// this code to not apply the pessimistic write lock.
// See HHH-12847 + https://github.com/hibernate/hibernate-orm/commit/719e5d0c12a6ef709bee907b8b651d27b8b08a6a.
// At least Sybase waits indefinitely when really applying a PESSIMISTIC_WRITE lock here (and
// the NO_WAIT part is not applied by the Sybase dialect so it doesn't help).
// For now going back to LockMode.NONE as it's the lock mode that has been applied for quite
// some time and it seems our supported databases don't have a problem with it.
// load with write lock to deal with databases that block (wait indefinitely) direct attempts
// to write a locked row
A it = _session.get(
A.class,
id,
new LockOptions( LockMode.NONE ).setTimeOut( LockOptions.NO_WAIT )
new LockOptions( LockMode.PESSIMISTIC_WRITE ).setTimeOut( LockOptions.NO_WAIT )
);
_session.createNativeQuery( updateStatement() )
.setParameter( "value", "changed" )

View File

@ -81,10 +81,7 @@ public void setUp() {
connectionProvider = ConnectionProviderBuilder.buildConnectionProvider();
final GenerationTargetToDatabase target = new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
connectionProvider
)
new DdlTransactionIsolatorTestingImpl( tool.resolveJdbcContext( settings ) )
);
@ -104,8 +101,6 @@ public void setUp() {
target
);
target.release();
final SessionFactoryBuilder sfb = metadata.getSessionFactoryBuilder();
sessionFactory = (SessionFactoryImplementor) sfb.build();
currentTenantResolver.setHibernateBooted();

View File

@ -77,26 +77,23 @@ public void setUp() {
HibernateSchemaManagementTool tool = new HibernateSchemaManagementTool();
tool.injectServices( serviceRegistry );
final GenerationTargetToDatabase acmeTarget = new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
acmeProvider
)
);
final GenerationTargetToDatabase jbossTarget = new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
jbossProvider
)
);
new SchemaDropperImpl( serviceRegistry ).doDrop(
metadata,
serviceRegistry,
settings,
true,
acmeTarget,
jbossTarget
new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
acmeProvider
)
),
new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
jbossProvider
)
)
);
new SchemaCreatorImpl( serviceRegistry ).doCreation(
@ -104,8 +101,18 @@ public void setUp() {
serviceRegistry,
settings,
true,
acmeTarget,
jbossTarget
new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
acmeProvider
)
),
new GenerationTargetToDatabase(
new DdlTransactionIsolatorTestingImpl(
serviceRegistry,
jbossProvider
)
)
);
final SessionFactoryBuilder sfb = metadata.getSessionFactoryBuilder();

View File

@ -7,6 +7,7 @@
package org.hibernate.orm.test.query.hql;
import org.hibernate.QueryException;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.MariaDBDialect;
@ -983,6 +984,7 @@ public void testTimestampAddDiffFunctions(SessionFactoryScope scope) {
@SkipForDialect(dialectClass = MySQLDialect.class)
@SkipForDialect(dialectClass = MariaDBDialect.class)
@SkipForDialect(dialectClass = TiDBDialect.class)
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "unsupported binary operator: <timestamptz> - <date>")
public void testDateAddDiffFunctions(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
@ -1148,20 +1150,6 @@ public void testIntervalDiffExpressions(SessionFactoryScope scope) {
session.createQuery("select (e.theDate - e.theDate) by day from EntityOfBasics e")
.list();
session.createQuery("select (e.theDate - e.theTimestamp) by year from EntityOfBasics e")
.list();
session.createQuery("select (e.theDate - e.theTimestamp) by month from EntityOfBasics e")
.list();
session.createQuery("select (e.theDate - e.theTimestamp) by day from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theDate) by year from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theDate) by month from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theDate) by day from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theTimestamp) by hour from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theTimestamp) by minute from EntityOfBasics e")
@ -1195,6 +1183,28 @@ public void testIntervalDiffExpressions(SessionFactoryScope scope) {
);
}
@Test
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "unsupported binary operator: <date> - <timestamp(6)>")
public void testIntervalDiffExpressionsDifferentTypes(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery("select (e.theDate - e.theTimestamp) by year from EntityOfBasics e")
.list();
session.createQuery("select (e.theDate - e.theTimestamp) by month from EntityOfBasics e")
.list();
session.createQuery("select (e.theDate - e.theTimestamp) by day from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theDate) by year from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theDate) by month from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theDate) by day from EntityOfBasics e")
.list();
}
);
}
@Test
public void testExtractFunction(SessionFactoryScope scope) {
scope.inTransaction(
@ -1401,8 +1411,6 @@ public void testExtractFunctionWithAssertions(SessionFactoryScope scope) {
public void testFormat(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery("select format(e.theTime as 'hh:mm:ss a') from EntityOfBasics e")
.list();
session.createQuery("select format(e.theDate as 'dd/MM/yy'), format(e.theDate as 'EEEE, MMMM dd, yyyy') from EntityOfBasics e")
.list();
session.createQuery("select format(e.theTimestamp as 'dd/MM/yyyy ''at'' HH:mm:ss') from EntityOfBasics e")
@ -1412,6 +1420,18 @@ public void testFormat(SessionFactoryScope scope) {
session.createQuery("select format(theDate as 'EEEE, dd/MM/yyyy') from EntityOfBasics where id=123").getResultList().get(0),
is("Monday, 25/03/1974")
);
}
);
}
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsFormat.class)
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "unknown signature: experimental_strftime(time, string)") // could cast the first argument to timestamp to workaround this
public void testFormatTime(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery("select format(e.theTime as 'hh:mm:ss a') from EntityOfBasics e")
.list();
assertThat(
session.createQuery("select format(theTime as '''Hello'', hh:mm:ss a') from EntityOfBasics where id=123").getResultList().get(0),
is("Hello, 08:10:08 PM")

View File

@ -12,6 +12,8 @@
import java.time.LocalDate;
import java.time.LocalTime;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.gambit.EntityOfBasics;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
@ -20,6 +22,7 @@
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.SkipForDialect;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@ -510,20 +513,6 @@ public void testIntervalDiffExpressions(SessionFactoryScope scope) {
session.createQuery("select (e.theDate - e.theDate) by day from EntityOfBasics e")
.list();
session.createQuery("select (e.theDate - e.theTimestamp) by year from EntityOfBasics e")
.list();
session.createQuery("select (e.theDate - e.theTimestamp) by month from EntityOfBasics e")
.list();
session.createQuery("select (e.theDate - e.theTimestamp) by day from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theDate) by year from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theDate) by month from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theDate) by day from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theTimestamp) by hour from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theTimestamp) by minute from EntityOfBasics e")
@ -557,6 +546,28 @@ public void testIntervalDiffExpressions(SessionFactoryScope scope) {
);
}
@Test
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "unsupported binary operator: <date> - <timestamp(6)>")
public void testIntervalDiffExpressionsDifferentTypes(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery("select (e.theDate - e.theTimestamp) by year from EntityOfBasics e")
.list();
session.createQuery("select (e.theDate - e.theTimestamp) by month from EntityOfBasics e")
.list();
session.createQuery("select (e.theDate - e.theTimestamp) by day from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theDate) by year from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theDate) by month from EntityOfBasics e")
.list();
session.createQuery("select (e.theTimestamp - e.theDate) by day from EntityOfBasics e")
.list();
}
);
}
@Test
public void testExtractFunction(SessionFactoryScope scope) {
scope.inTransaction(
@ -881,8 +892,6 @@ public void testAggregateFunctions(SessionFactoryScope scope) {
public void testFormat(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "select format(e.theTime as 'hh:mm:ss a') from EntityOfBasics e" )
.list();
session.createQuery(
"select format(e.theDate as 'dd/MM/yy'), format(e.theDate as 'EEEE, MMMM dd, yyyy') from EntityOfBasics e" )
.list();
@ -897,9 +906,21 @@ public void testFormat(SessionFactoryScope scope) {
.get( 0 ),
is( "Monday, 25/03/1974" )
);
}
);
}
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsFormat.class)
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "unknown signature: experimental_strftime(time, string)") // could cast the first argument to timestamp to workaround this
public void testFormatTime(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "select format(e.theTime as 'hh:mm:ss a') from EntityOfBasics e" )
.list();
assertThat(
session.createQuery(
"select format(e.theTime as '''Hello'', hh:mm:ss a') from EntityOfBasics e" )
"select format(e.theTime as '''Hello'', hh:mm:ss a') from EntityOfBasics e" )
.getResultList()
.get( 0 ),
is( "Hello, 08:10:08 PM" )

View File

@ -87,8 +87,12 @@ public void getPrimaryKey() throws Exception {
JdbcMetadaAccessStrategy.GROUPED
)
.build();
DdlTransactionIsolator ddlTransactionIsolator = null;
ExtractionContextImpl extractionContext = null;
try {
TableInformation table = buildInformationExtractor( ssr ).getTable(
ddlTransactionIsolator = buildDdlTransactionIsolator( ssr );
extractionContext = buildContext( ssr, ddlTransactionIsolator );
TableInformation table = buildInformationExtractor( extractionContext ).getTable(
null,
null,
new Identifier( "TEST_ENTITY", false )
@ -108,24 +112,35 @@ public void getPrimaryKey() throws Exception {
assertTrue( pkColumnNames.contains( "b" ) );
}
finally {
if ( extractionContext != null ) {
extractionContext.cleanup();
}
if ( ddlTransactionIsolator != null ) {
ddlTransactionIsolator.release();
}
StandardServiceRegistryBuilder.destroy( ssr );
}
}
private InformationExtractor buildInformationExtractor(StandardServiceRegistry ssr) throws Exception {
ExtractionContextImpl extractionContext = buildContext( ssr );
private InformationExtractor buildInformationExtractor(ExtractionContextImpl extractionContext) throws Exception {
ExtractionTool extractionTool = new HibernateSchemaManagementTool().getExtractionTool();
return extractionTool.createInformationExtractor( extractionContext );
}
private static ExtractionContextImpl buildContext(StandardServiceRegistry ssr) throws Exception {
private static ExtractionContextImpl buildContext(
StandardServiceRegistry ssr,
DdlTransactionIsolator ddlTransactionIsolator) throws Exception {
Database database = new MetadataSources( ssr ).buildMetadata().getDatabase();
SqlStringGenerationContext sqlStringGenerationContext = SqlStringGenerationContextImpl.forTests( database.getJdbcEnvironment() );
DatabaseInformation dbInfo = buildDatabaseInformation( ssr, database, sqlStringGenerationContext );
DatabaseInformation dbInfo = buildDatabaseInformation(
ssr,
database,
sqlStringGenerationContext,
ddlTransactionIsolator
);
return new ExtractionContextImpl(
ssr,
@ -139,12 +154,13 @@ private static ExtractionContextImpl buildContext(StandardServiceRegistry ssr) t
private static DatabaseInformationImpl buildDatabaseInformation(
StandardServiceRegistry ssr,
Database database,
SqlStringGenerationContext sqlStringGenerationContext) throws Exception {
SqlStringGenerationContext sqlStringGenerationContext,
DdlTransactionIsolator ddlTransactionIsolator) throws Exception {
return new DatabaseInformationImpl(
ssr,
database.getJdbcEnvironment(),
sqlStringGenerationContext,
buildDdlTransactionIsolator( ssr ),
ddlTransactionIsolator,
database.getServiceRegistry().getService( SchemaManagementTool.class )
);
}

View File

@ -22,7 +22,7 @@
<property name="name"/>
<property name="company"/>
<property name="region"/>
<property name="region" column="p_region"/>
<subclass name="Employee" discriminator-value="1">
<property name="title" column="`title`"/>
@ -31,7 +31,7 @@
<set name="minions" inverse="true" lazy="true" cascade="all">
<key column="mgr_id"/>
<one-to-many class="Employee"/>
<filter name="region" condition="region = :userRegion"/>
<filter name="region" condition="p_region = :userRegion"/>
</set>
</subclass>
@ -39,7 +39,7 @@
<many-to-one name="contactOwner" class="Employee"/>
</subclass>
<filter name="region" condition="region = :userRegion"/>
<filter name="region" condition="p_region = :userRegion"/>
</class>

View File

@ -19,7 +19,7 @@
<property name="name"/>
<property name="company"/>
<property name="region"/>
<property name="region" column="p_region"/>
<joined-subclass name="Employee" table="JEmployee">
<key column="person_id"/>
@ -39,7 +39,7 @@
<many-to-one name="contactOwner" class="Employee"/>
</joined-subclass>
<filter name="region" condition="region = :userRegion"/>
<filter name="region" condition="p_region = :userRegion"/>
</class>
<filter-def name="region">

View File

@ -19,7 +19,7 @@
<property name="name" unique="true" not-null="true"/>
<property name="company"/>
<property name="region"/>
<property name="region" column="p_region"/>
<union-subclass name="Employee" table="UEmployee">
<property name="title" column="emp_title"/>
@ -28,7 +28,7 @@
<set name="minions" inverse="true" lazy="true" cascade="all">
<key column="mgr_id"/>
<one-to-many class="Employee"/>
<filter name="region" condition="region = :userRegion"/>
<filter name="region" condition="p_region = :userRegion"/>
</set>
</union-subclass>
@ -36,7 +36,7 @@
<many-to-one name="contactOwner" class="Employee"/>
</union-subclass>
<filter name="region" condition="region = :userRegion"/>
<filter name="region" condition="p_region = :userRegion"/>
</class>

View File

@ -13,6 +13,7 @@
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper;
@ -281,11 +282,12 @@ public void testConcurrentCachedQueries(SessionFactoryScope scope) throws Except
feature = DialectFeatureChecks.DoesReadCommittedNotCauseWritersToBlockReadersCheck.class,
comment = "write locks block readers"
)
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "Cockroach uses SERIALIZABLE by default and seems to fail reading a row that is exclusively locked by a different TX")
public void testConcurrentCachedDirtyQueries(SessionFactoryScope scope) throws Exception {
final TransactionManager transactionManager = TestingJtaPlatformImpl.INSTANCE.getTransactionManager();
try {
transactionManager.begin();
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
transactionManager.begin();
Session s = sessionFactory.openSession();
Map foo = new HashMap();
foo.put( "name", "Foo" );

View File

@ -20,6 +20,7 @@
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.ActionQueue;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
@ -54,6 +55,11 @@ protected void applySettings(StandardServiceRegistryBuilder builder) {
@Test
@TestForIssue(jiraKey = "HHH-12448")
public void testAfterCompletionCallbackExecutedAfterTransactionTimeout() throws Exception {
// This makes sure that hbm2ddl runs before we start a transaction for a test
// This is important for database that only support SNAPSHOT/SERIALIZABLE isolation,
// because a test transaction still sees the state before the DDL executed
final SessionFactoryImplementor sessionFactory = sessionFactory();
// Set timeout to 5 seconds
// Allows the reaper thread to abort our running thread for us
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().setTransactionTimeout( 5 );
@ -63,7 +69,7 @@ public void testAfterCompletionCallbackExecutedAfterTransactionTimeout() throws
Session session = null;
try {
session = sessionFactory().openSession();
session = sessionFactory.openSession();
SimpleEntity entity = new SimpleEntity( "Hello World" );
session.save( entity );

View File

@ -123,7 +123,7 @@ public SchemaFilter getSchemaFilter() {
@After
public void tearsDown() {
try {
connection.close();
connectionProvider.closeConnection( connection );
}
catch (SQLException e) {
log.error( e.getMessage() );

View File

@ -30,6 +30,10 @@ public DdlTransactionIsolatorTestingImpl(ServiceRegistry serviceRegistry, JdbcCo
super( createJdbcContext( jdbcConnectionAccess, serviceRegistry ) );
}
public DdlTransactionIsolatorTestingImpl(JdbcContext jdbcContext) {
super( jdbcContext );
}
public static JdbcContext createJdbcContext(
JdbcConnectionAccess jdbcConnectionAccess,
ServiceRegistry serviceRegistry) {

View File

@ -40,6 +40,7 @@
import org.hibernate.testing.orm.jpa.PersistenceUnitDescriptorAdapter;
import org.hibernate.testing.orm.junit.DialectContext;
import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableStrategy;
import org.hibernate.service.ServiceRegistry;
import org.junit.After;
@ -147,6 +148,7 @@ protected Map getConfig() {
config.put( AvailableSettings.ORM_XML_FILES, dds );
}
config.put( PersistentTableStrategy.DROP_ID_TABLES, "true" );
config.put( GlobalTemporaryTableMutationStrategy.DROP_ID_TABLES, "true" );
config.put( LocalTemporaryTableMutationStrategy.DROP_ID_TABLES, "true" );
if ( !Environment.getProperties().containsKey( AvailableSettings.CONNECTION_PROVIDER ) ) {

View File

@ -12,6 +12,7 @@
import java.util.Set;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.spatial.CommonSpatialFunction;
import org.hibernate.spatial.integration.SpatialTestDataProvider;
@ -48,13 +49,21 @@ public boolean isSupported(CommonSpatialFunction function) {
protected void initH2GISExtensionsForInMemDb() {
this.scope.inSession( session -> {
final JdbcConnectionAccess jdbcConnectionAccess = session.getJdbcConnectionAccess();
Connection connection = null;
try {
Connection cn = session.getJdbcConnectionAccess().obtainConnection();
H2GISFunctions.load( cn );
H2GISFunctions.load( connection = jdbcConnectionAccess.obtainConnection() );
}
catch (SQLException e) {
throw new RuntimeException( e );
}
finally {
try {
jdbcConnectionAccess.releaseConnection( connection );
}
catch (SQLException ignore) {
}
}
} );
}
}

View File

@ -44,6 +44,7 @@
import org.hibernate.jdbc.Work;
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableStrategy;
import org.hibernate.resource.transaction.spi.TransactionCoordinator;
import org.hibernate.testing.AfterClassOnce;
@ -193,6 +194,7 @@ protected Configuration constructConfiguration(BootstrapServiceRegistry bootstra
}
configuration.setImplicitNamingStrategy( ImplicitNamingStrategyLegacyJpaImpl.INSTANCE );
configuration.setProperty( Environment.DIALECT, getDialect().getClass().getName() );
configuration.getProperties().put( PersistentTableStrategy.DROP_ID_TABLES, "true" );
configuration.getProperties().put( GlobalTemporaryTableMutationStrategy.DROP_ID_TABLES, "true" );
configuration.getProperties().put( LocalTemporaryTableMutationStrategy.DROP_ID_TABLES, "true" );
if ( !Environment.getProperties().containsKey( Environment.CONNECTION_PROVIDER ) ) {

View File

@ -49,6 +49,7 @@
import org.hibernate.mapping.SimpleValue;
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableStrategy;
import org.hibernate.resource.transaction.spi.TransactionCoordinator;
import org.hibernate.testing.AfterClassOnce;
@ -174,6 +175,7 @@ protected final StandardServiceRegistryBuilder constructStandardServiceRegistryB
afterBootstrapServiceRegistryBuilt( bsr );
final Map<String,Object> settings = new HashMap<>();
settings.put( PersistentTableStrategy.DROP_ID_TABLES, "true" );
settings.put( GlobalTemporaryTableMutationStrategy.DROP_ID_TABLES, "true" );
settings.put( LocalTemporaryTableMutationStrategy.DROP_ID_TABLES, "true" );
if ( !Environment.getProperties().containsKey( Environment.CONNECTION_PROVIDER ) ) {

View File

@ -24,6 +24,7 @@
import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableStrategy;
import org.hibernate.testing.jdbc.SharedDriverManagerConnectionProviderImpl;
import org.junit.jupiter.api.AfterEach;
@ -139,6 +140,7 @@ protected Map<Object, Object> getConfig() {
config.put( AvailableSettings.ORM_XML_FILES, dds );
}
config.put( PersistentTableStrategy.DROP_ID_TABLES, "true" );
config.put( GlobalTemporaryTableMutationStrategy.DROP_ID_TABLES, "true" );
config.put( LocalTemporaryTableMutationStrategy.DROP_ID_TABLES, "true" );
if ( !config.containsKey( Environment.CONNECTION_PROVIDER ) ) {

View File

@ -26,6 +26,7 @@
import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder;
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableStrategy;
import org.hibernate.tool.schema.Action;
import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator;
import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.ActionGrouping;
@ -164,7 +165,7 @@ public static EntityManagerFactoryScope findEntityManagerFactoryScope(
final Map<String, Object> integrationSettings = new HashMap<>();
integrationSettings.put( PersistentTableStrategy.DROP_ID_TABLES, "true" );
integrationSettings.put( GlobalTemporaryTableMutationStrategy.DROP_ID_TABLES, "true" );
integrationSettings.put( LocalTemporaryTableMutationStrategy.DROP_ID_TABLES, "true" );
if ( !integrationSettings.containsKey( Environment.CONNECTION_PROVIDER ) ) {

View File

@ -17,11 +17,16 @@
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableStrategy;
import org.hibernate.service.spi.ServiceContributor;
import org.hibernate.testing.boot.ExtraJavaServicesClassLoaderService;
import org.hibernate.testing.boot.ExtraJavaServicesClassLoaderService.JavaServiceDescriptor;
import org.hibernate.testing.jdbc.SharedDriverManagerConnectionProviderImpl;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
@ -186,6 +191,15 @@ public ServiceRegistryProducerImpl(Optional<ServiceRegistry> ssrAnnRef) {
public StandardServiceRegistry produceServiceRegistry(StandardServiceRegistryBuilder ssrb) {
// set some baseline test settings
ssrb.applySetting( AvailableSettings.STATEMENT_INSPECTOR, org.hibernate.testing.jdbc.SQLStatementInspector.class );
ssrb.applySetting( PersistentTableStrategy.DROP_ID_TABLES, "true" );
ssrb.applySetting( GlobalTemporaryTableMutationStrategy.DROP_ID_TABLES, "true" );
ssrb.applySetting( LocalTemporaryTableMutationStrategy.DROP_ID_TABLES, "true" );
if ( !ssrb.getSettings().containsKey( Environment.CONNECTION_PROVIDER ) ) {
ssrb.applySetting(
AvailableSettings.CONNECTION_PROVIDER,
SharedDriverManagerConnectionProviderImpl.getInstance()
);
}
if ( ssrAnnRef.isPresent() ) {
final ServiceRegistry serviceRegistryAnn = ssrAnnRef.get();

View File

@ -27,6 +27,7 @@
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.dialect.AbstractHANADialect;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.MySQLDialect;
@ -588,7 +589,7 @@ public static void setJdbcTimeout(Session session) {
public static void setJdbcTimeout(Session session, long millis) {
final Dialect dialect = session.getSessionFactory().unwrap( SessionFactoryImplementor.class ).getJdbcServices().getDialect();
session.doWork( connection -> {
if ( dialect instanceof PostgreSQLDialect ) {
if ( dialect instanceof PostgreSQLDialect || dialect instanceof CockroachDialect ) {
try (Statement st = connection.createStatement()) {
//Prepared Statements fail for SET commands
st.execute(String.format( "SET statement_timeout TO %d", millis / 10));

322
nightly.Jenkinsfile Normal file
View File

@ -0,0 +1,322 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
import groovy.transform.Field
import io.jenkins.blueocean.rest.impl.pipeline.PipelineNodeGraphVisitor
import io.jenkins.blueocean.rest.impl.pipeline.FlowNodeWrapper
import org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper
/*
* See https://github.com/hibernate/hibernate-jenkins-pipeline-helpers
*/
@Library('hibernate-jenkins-pipeline-helpers@1.5') _
import org.hibernate.jenkins.pipeline.helpers.job.JobHelper
@Field final String DEFAULT_JDK_VERSION = '11'
@Field final String DEFAULT_JDK_TOOL = "OpenJDK ${DEFAULT_JDK_VERSION} Latest"
@Field final String NODE_PATTERN_BASE = 'Worker&&Containers'
@Field List<BuildEnvironment> environments
this.helper = new JobHelper(this)
helper.runWithNotification {
stage('Configure') {
this.environments = [
new BuildEnvironment( dbName: 'cockroachdb', node: 'LongDuration', longRunning: true ))
];
helper.configure {
file 'job-configuration.yaml'
// We don't require the following, but the build helper plugin apparently does
jdk {
defaultTool DEFAULT_JDK_TOOL
}
maven {
defaultTool 'Apache Maven 3.8'
}
}
properties([
buildDiscarder(
logRotator(daysToKeepStr: '30', numToKeepStr: '10')
),
rateLimitBuilds(throttle: [count: 1, durationName: 'day', userBoost: true]),
// If two builds are about the same branch or pull request,
// the older one will be aborted when the newer one starts.
disableConcurrentBuilds(abortPrevious: true),
helper.generateNotificationProperty()
])
}
// Avoid running the pipeline on branch indexing
if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) {
print "INFO: Build skipped due to trigger being Branch Indexing"
currentBuild.result = 'ABORTED'
return
}
stage('Build') {
Map<String, Closure> executions = [:]
Map<String, Map<String, String>> state = [:]
environments.each { BuildEnvironment buildEnv ->
// Don't build environments for newer JDKs when this is a PR
if ( helper.scmSource.pullRequest && buildEnv.testJdkVersion ) {
return
}
state[buildEnv.tag] = [:]
executions.put(buildEnv.tag, {
runBuildOnNode(buildEnv.node ?: NODE_PATTERN_BASE) {
def testJavaHome
if ( buildEnv.testJdkVersion ) {
testJavaHome = tool(name: "OpenJDK ${buildEnv.testJdkVersion} Latest", type: 'jdk')
}
def javaHome = tool(name: DEFAULT_JDK_TOOL, type: 'jdk')
// Use withEnv instead of setting env directly, as that is global!
// See https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md
withEnv(["JAVA_HOME=${javaHome}", "PATH+JAVA=${javaHome}/bin"]) {
state[buildEnv.tag]['additionalOptions'] = ''
if ( testJavaHome ) {
state[buildEnv.tag]['additionalOptions'] = state[buildEnv.tag]['additionalOptions'] +
" -Ptest.jdk.version=${buildEnv.testJdkVersion} -Porg.gradle.java.installations.paths=${javaHome},${testJavaHome}"
}
if ( buildEnv.testJdkLauncherArgs ) {
state[buildEnv.tag]['additionalOptions'] = state[buildEnv.tag]['additionalOptions'] +
" -Ptest.jdk.launcher.args=${buildEnv.testJdkLauncherArgs}"
}
state[buildEnv.tag]['containerName'] = null;
stage('Checkout') {
checkout scm
}
try {
stage('Start database') {
switch (buildEnv.dbName) {
case "cockroachdb":
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
docker.image('cockroachdb/cockroach:v21.1.21').pull()
}
sh "./docker_db.sh cockroachdb"
state[buildEnv.tag]['containerName'] = "cockroach"
break;
case "mysql":
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
docker.image('mysql:5.7').pull()
}
sh "./docker_db.sh mysql"
state[buildEnv.tag]['containerName'] = "mysql"
break;
case "mysql8":
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
docker.image('mysql:8.0.21').pull()
}
sh "./docker_db.sh mysql_8_0"
state[buildEnv.tag]['containerName'] = "mysql"
break;
case "mariadb":
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
docker.image('mariadb:10.7.5').pull()
}
sh "./docker_db.sh mariadb"
state[buildEnv.tag]['containerName'] = "mariadb"
break;
case "postgresql":
// use the postgis image to enable the PGSQL GIS (spatial) extension
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
docker.image('postgis/postgis:9.5-2.5').pull()
}
sh "./docker_db.sh postgresql"
state[buildEnv.tag]['containerName'] = "postgres"
break;
case "postgresql_14":
// use the postgis image to enable the PGSQL GIS (spatial) extension
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
docker.image('postgis/postgis:14-3.3').pull()
}
sh "./docker_db.sh postgresql_14"
state[buildEnv.tag]['containerName'] = "postgres"
break;
case "oracle":
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
docker.image('gvenzl/oracle-xe:18.4.0-full').pull()
}
sh "./docker_db.sh oracle_18"
state[buildEnv.tag]['containerName'] = "oracle"
break;
case "db2":
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
docker.image('ibmcom/db2:11.5.7.0').pull()
}
sh "./docker_db.sh db2"
state[buildEnv.tag]['containerName'] = "db2"
break;
case "mssql":
docker.image('mcr.microsoft.com/mssql/server@sha256:f54a84b8a802afdfa91a954e8ddfcec9973447ce8efec519adf593b54d49bedf').pull()
sh "./docker_db.sh mssql"
state[buildEnv.tag]['containerName'] = "mssql"
break;
case "sybase":
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
docker.image('nguoianphu/docker-sybase').pull()
}
sh "./docker_db.sh sybase"
state[buildEnv.tag]['containerName'] = "sybase"
break;
case "edb":
docker.withRegistry('https://containers.enterprisedb.com', 'hibernateci.containers.enterprisedb.com') {
// withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'hibernateci.containers.enterprisedb.com',
// usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD']]) {
// sh 'docker login -u "$USERNAME" -p "$PASSWORD" https://containers.enterprisedb.com'
docker.image('containers.enterprisedb.com/edb/edb-as-lite:v11').pull()
}
sh "./docker_db.sh edb"
state[buildEnv.tag]['containerName'] = "edb"
break;
}
}
stage('Test') {
String cmd = "./ci/build.sh ${buildEnv.additionalOptions ?: ''} ${state[buildEnv.tag]['additionalOptions'] ?: ''}"
withEnv(["RDBMS=${buildEnv.dbName}"]) {
try {
if (buildEnv.dbLockableResource == null) {
timeout( [time: buildEnv.longRunning ? 240 : 120, unit: 'MINUTES'] ) {
sh cmd
}
}
else {
lock(buildEnv.dbLockableResource) {
timeout( [time: buildEnv.longRunning ? 240 : 120, unit: 'MINUTES'] ) {
sh cmd
}
}
}
}
finally {
junit '**/target/test-results/test/*.xml,**/target/test-results/testKitTest/*.xml'
}
}
}
}
finally {
if ( state[buildEnv.tag]['containerName'] != null ) {
sh "docker rm -f ${state[buildEnv.tag]['containerName']}"
}
// Skip this for PRs
if ( !env.CHANGE_ID && buildEnv.notificationRecipients != null ) {
handleNotifications(currentBuild, buildEnv)
}
}
}
}
})
}
parallel(executions)
}
} // End of helper.runWithNotification
// Job-specific helpers
class BuildEnvironment {
String testJdkVersion
String testJdkLauncherArgs
String dbName = 'h2'
String node
String dbLockableResource
String additionalOptions
String notificationRecipients
boolean longRunning
String toString() { getTag() }
String getTag() { "${node ? node + "_" : ''}${testJdkVersion ? 'jdk_' + testJdkVersion + '_' : '' }${dbName}" }
}
void runBuildOnNode(String label, Closure body) {
node( label ) {
pruneDockerContainers()
try {
body()
}
finally {
// If this is a PR, we clean the workspace at the end
if ( env.CHANGE_BRANCH != null ) {
cleanWs()
}
pruneDockerContainers()
}
}
}
void pruneDockerContainers() {
if ( !sh( script: 'command -v docker || true', returnStdout: true ).trim().isEmpty() ) {
sh 'docker container prune -f || true'
sh 'docker image prune -f || true'
sh 'docker network prune -f || true'
sh 'docker volume prune -f || true'
}
}
void handleNotifications(currentBuild, buildEnv) {
def currentResult = getParallelResult(currentBuild, buildEnv.tag)
boolean success = currentResult == 'SUCCESS' || currentResult == 'UNKNOWN'
def previousResult = currentBuild.previousBuild == null ? null : getParallelResult(currentBuild.previousBuild, buildEnv.tag)
// Ignore success after success
if ( !( success && previousResult == 'SUCCESS' ) ) {
def subject
def body
if ( success ) {
if ( previousResult != 'SUCCESS' && previousResult != null ) {
subject = "${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Fixed"
body = """<p>${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Fixed:</p>
<p>Check console output at <a href='${env.BUILD_URL}'>${env.BUILD_URL}</a> to view the results.</p>"""
}
else {
subject = "${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Success"
body = """<p>${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Success:</p>
<p>Check console output at <a href='${env.BUILD_URL}'>${env.BUILD_URL}</a> to view the results.</p>"""
}
}
else if (currentBuild.rawBuild.getActions(jenkins.model.InterruptedBuildAction.class).isEmpty()) {
// If there are interrupted build actions, this means the build was cancelled, probably superseded
// Thanks to https://issues.jenkins.io/browse/JENKINS-43339 for the "hack" to determine this
if ( currentResult == 'FAILURE' ) {
if ( previousResult != null && previousResult == "FAILURE" ) {
subject = "${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Still failing"
body = """<p>${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Still failing:</p>
<p>Check console output at <a href='${env.BUILD_URL}'>${env.BUILD_URL}</a> to view the results.</p>"""
}
else {
subject = "${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Failure"
body = """<p>${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - Failure:</p>
<p>Check console output at <a href='${env.BUILD_URL}'>${env.BUILD_URL}</a> to view the results.</p>"""
}
}
else {
subject = "${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - ${currentResult}"
body = """<p>${env.JOB_NAME} - Build ${env.BUILD_NUMBER} - ${currentResult}:</p>
<p>Check console output at <a href='${env.BUILD_URL}'>${env.BUILD_URL}</a> to view the results.</p>"""
}
}
emailext(
subject: subject,
body: body,
to: buildEnv.notificationRecipients
)
}
}
@NonCPS
String getParallelResult( RunWrapper build, String parallelBranchName ) {
def visitor = new PipelineNodeGraphVisitor( build.rawBuild )
def branch = visitor.pipelineNodes.find{ it.type == FlowNodeWrapper.NodeType.PARALLEL && parallelBranchName == it.displayName }
if ( branch == null ) {
echo "Couldn't find parallel branch name '$parallelBranchName'. Available parallel branch names:"
visitor.pipelineNodes.findAll{ it.type == FlowNodeWrapper.NodeType.PARALLEL }.each{
echo " - ${it.displayName}"
}
return null;
}
return branch.status.result
}