Skip to content

Commit 20467ee

Browse files
committed
HHH-18377 Test cases to check monofonicity of generated version 6 & version 7 UUID's
1 parent 5053fc7 commit 20467ee

File tree

5 files changed

+339
-0
lines changed

5 files changed

+339
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.orm.test.id.uuid.rfc9562;
8+
9+
import java.util.UUID;
10+
11+
import org.hibernate.annotations.UuidGenerator;
12+
import org.hibernate.id.uuid.UuidV7ValueGenerator;
13+
14+
import jakarta.persistence.Basic;
15+
import jakarta.persistence.Entity;
16+
import jakarta.persistence.Id;
17+
import jakarta.persistence.Table;
18+
19+
@Entity(name = "EntitySeven")
20+
@Table(name = "entity_seven")
21+
public class EntitySeven {
22+
@Id
23+
@UuidGenerator(algorithm = UuidV7ValueGenerator.class)
24+
public UUID id;
25+
@Basic
26+
public String name;
27+
28+
private EntitySeven() {
29+
// for Hibernate use
30+
}
31+
32+
public EntitySeven(String name) {
33+
this.name = name;
34+
}
35+
36+
public UUID getId() {
37+
return id;
38+
}
39+
40+
public String getName() {
41+
return name;
42+
}
43+
44+
public void setName(String name) {
45+
this.name = name;
46+
}
47+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
6+
*/
7+
package org.hibernate.orm.test.id.uuid.rfc9562;
8+
9+
import java.util.UUID;
10+
11+
import org.hibernate.annotations.UuidGenerator;
12+
import org.hibernate.id.uuid.UuidV6ValueGenerator;
13+
14+
import jakarta.persistence.Basic;
15+
import jakarta.persistence.Entity;
16+
import jakarta.persistence.GeneratedValue;
17+
import jakarta.persistence.Id;
18+
import jakarta.persistence.Table;
19+
20+
/**
21+
* @author Steve Ebersole
22+
*/
23+
@Table(name = "entity_six")
24+
@Entity
25+
public class EntitySix {
26+
@Id
27+
@GeneratedValue
28+
@UuidGenerator(algorithm = UuidV6ValueGenerator.class)
29+
private UUID id;
30+
@Basic
31+
private String name;
32+
33+
protected EntitySix() {
34+
// for Hibernate use
35+
}
36+
37+
public EntitySix(String name) {
38+
this.name = name;
39+
}
40+
41+
public UUID getId() {
42+
return id;
43+
}
44+
45+
public String getName() {
46+
return name;
47+
}
48+
49+
public void setName(String name) {
50+
this.name = name;
51+
}
52+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.orm.test.id.uuid.rfc9562;
8+
9+
import java.util.UUID;
10+
11+
import org.hibernate.annotations.UuidGenerator;
12+
import org.hibernate.id.uuid.UuidV7ValueGenerator;
13+
14+
import jakarta.persistence.Basic;
15+
import jakarta.persistence.Entity;
16+
import jakarta.persistence.GeneratedValue;
17+
import jakarta.persistence.Id;
18+
import jakarta.persistence.Table;
19+
20+
@Entity(name = "OtherEntitySeven")
21+
@Table(name = "other_entity_seven")
22+
public class OtherEntitySeven {
23+
@Id
24+
@GeneratedValue
25+
public Long pk;
26+
27+
@UuidGenerator(algorithm = UuidV7ValueGenerator.class)
28+
public UUID id;
29+
30+
@Basic
31+
public String name;
32+
33+
private OtherEntitySeven() {
34+
// for Hibernate use
35+
}
36+
37+
public OtherEntitySeven(String name) {
38+
this.name = name;
39+
}
40+
41+
public UUID getId() {
42+
return id;
43+
}
44+
45+
public String getName() {
46+
return name;
47+
}
48+
49+
public void setName(String name) {
50+
this.name = name;
51+
}
52+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.hibernate.orm.test.id.uuid.rfc9562;
2+
3+
import java.util.UUID;
4+
5+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
6+
import org.hibernate.id.uuid.UuidV6ValueGenerator;
7+
import org.hibernate.id.uuid.UuidV7ValueGenerator;
8+
import org.hibernate.id.uuid.UuidValueGenerator;
9+
10+
import org.junit.jupiter.api.Test;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
import static org.mockito.Mockito.mock;
14+
15+
public class UUidV6V7GenetartorTest {
16+
17+
private static final UUID NIL_UUID = new UUID( 0L, 0L );
18+
private static final int ITERATIONS = 1_000_000;
19+
20+
@Test
21+
void testMonotonicityUuid6() {
22+
testMonotonicity( new UuidV6ValueGenerator() );
23+
}
24+
25+
@Test
26+
void testMonotonicityUuid7() {
27+
testMonotonicity( new UuidV7ValueGenerator() );
28+
}
29+
30+
private static void testMonotonicity(UuidValueGenerator generator) {
31+
final SharedSessionContractImplementor session = mock( SharedSessionContractImplementor.class );
32+
final UUID[] uuids = new UUID[ITERATIONS + 1];
33+
uuids[0] = NIL_UUID;
34+
for ( int n = 1; n <= ITERATIONS; ++n ) {
35+
uuids[n] = generator.generateUuid( session );
36+
}
37+
38+
for ( var n = 0; n < ITERATIONS; ++n ) {
39+
assertThat( uuids[n + 1].toString() ).isGreaterThan( uuids[n].toString() );
40+
assertThat( uuids[n + 1] ).isGreaterThan( uuids[n] );
41+
}
42+
}
43+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.orm.test.id.uuid.rfc9562;
8+
9+
import java.time.Instant;
10+
import java.time.LocalDate;
11+
import java.time.ZoneId;
12+
import java.time.temporal.ChronoUnit;
13+
import java.util.UUID;
14+
15+
import org.hibernate.dialect.SybaseDialect;
16+
import org.hibernate.generator.Generator;
17+
import org.hibernate.id.uuid.UuidGenerator;
18+
import org.hibernate.id.uuid.UuidV6ValueGenerator;
19+
import org.hibernate.id.uuid.UuidV7ValueGenerator;
20+
import org.hibernate.mapping.BasicValue;
21+
import org.hibernate.mapping.Property;
22+
23+
import org.hibernate.testing.orm.junit.DomainModel;
24+
import org.hibernate.testing.orm.junit.DomainModelScope;
25+
import org.hibernate.testing.orm.junit.SessionFactory;
26+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
27+
import org.hibernate.testing.orm.junit.SkipForDialect;
28+
import org.hibernate.testing.util.uuid.IdGeneratorCreationContext;
29+
import org.junit.jupiter.api.AfterEach;
30+
import org.junit.jupiter.api.Test;
31+
32+
import static org.assertj.core.api.Assertions.assertThat;
33+
34+
@SuppressWarnings("JUnitMalformedDeclaration")
35+
@DomainModel(annotatedClasses = {
36+
EntitySeven.class, OtherEntitySeven.class, EntitySix.class
37+
})
38+
@SessionFactory
39+
@SkipForDialect(dialectClass = SybaseDialect.class, matchSubTypes = true,
40+
reason = "Skipped for Sybase to avoid problems with UUIDs potentially ending with a trailing 0 byte")
41+
public class UuidGeneratorAnnotationTests {
42+
@Test
43+
public void verifyUuidV7IdGeneratorModel(final DomainModelScope scope) {
44+
scope.withHierarchy( EntitySeven.class, descriptor -> {
45+
final Property idProperty = descriptor.getIdentifierProperty();
46+
final BasicValue value = (BasicValue) idProperty.getValue();
47+
48+
assertThat( value.getCustomIdGeneratorCreator() ).isNotNull();
49+
final Generator generator = value.getCustomIdGeneratorCreator()
50+
.createGenerator( new IdGeneratorCreationContext(
51+
scope.getDomainModel(),
52+
descriptor
53+
) );
54+
55+
assertThat( generator ).isInstanceOf( UuidGenerator.class );
56+
final UuidGenerator uuidGenerator = (UuidGenerator) generator;
57+
assertThat( uuidGenerator.getValueGenerator() ).isInstanceOf( UuidV7ValueGenerator.class );
58+
} );
59+
}
60+
61+
@Test
62+
public void verifyUuidV6IdGeneratorModel(final DomainModelScope scope) {
63+
scope.withHierarchy( EntitySix.class, descriptor -> {
64+
final Property idProperty = descriptor.getIdentifierProperty();
65+
final BasicValue value = (BasicValue) idProperty.getValue();
66+
67+
assertThat( value.getCustomIdGeneratorCreator() ).isNotNull();
68+
final Generator generator = value.getCustomIdGeneratorCreator()
69+
.createGenerator( new IdGeneratorCreationContext(
70+
scope.getDomainModel(),
71+
descriptor
72+
) );
73+
74+
assertThat( generator ).isInstanceOf( UuidGenerator.class );
75+
final UuidGenerator uuidGenerator = (UuidGenerator) generator;
76+
assertThat( uuidGenerator.getValueGenerator() ).isInstanceOf( UuidV6ValueGenerator.class );
77+
} );
78+
}
79+
80+
@Test
81+
public void basicUseTest(final SessionFactoryScope scope) {
82+
scope.inTransaction( session -> {
83+
final EntitySeven seven = new EntitySeven( "John Doe" );
84+
session.persist( seven );
85+
session.flush();
86+
assertThat( seven.id ).isNotNull();
87+
assertThat( seven.id.version() ).isEqualTo( 7 );
88+
} );
89+
}
90+
91+
@Test
92+
public void nonPkUseTest(final SessionFactoryScope scope) {
93+
scope.inTransaction( session -> {
94+
final Instant startTime = Instant.now();
95+
96+
final OtherEntitySeven seven = new OtherEntitySeven( "Dave Default" );
97+
session.persist( seven );
98+
session.flush();
99+
100+
final Instant endTime = Instant.now();
101+
assertThat( seven.id ).isNotNull();
102+
assertThat( seven.id.version() ).isEqualTo( 7 );
103+
104+
assertThat( Instant.ofEpochMilli( seven.id.getMostSignificantBits() >> 16 & 0xFFFF_FFFF_FFFFL ) )
105+
.isBetween( startTime.truncatedTo( ChronoUnit.MILLIS ), endTime.truncatedTo( ChronoUnit.MILLIS ) );
106+
} );
107+
}
108+
109+
@Test
110+
void testUuidV6IdGenerator(final SessionFactoryScope sessionFactoryScope) {
111+
sessionFactoryScope.inTransaction( session -> {
112+
final Instant startTime = Instant.now();
113+
114+
final EntitySix six = new EntitySix( "Jane Doe" );
115+
session.persist( six );
116+
assertThat( six.getId() ).isNotNull();
117+
assertThat( six.getId().version() ).isEqualTo( 6 );
118+
119+
session.flush();
120+
final Instant endTime = Instant.now();
121+
assertThat( six.getId() ).isNotNull();
122+
assertThat( six.getId().version() ).isEqualTo( 6 );
123+
assertThat( uuid6Instant( six.getId() ) ).isBetween( startTime, endTime );
124+
} );
125+
}
126+
127+
@AfterEach
128+
void dropTestData(final SessionFactoryScope sessionFactoryScope) {
129+
sessionFactoryScope.inTransaction( session -> {
130+
session.createMutationQuery( "delete EntitySeven" ).executeUpdate();
131+
session.createMutationQuery( "delete OtherEntitySeven" ).executeUpdate();
132+
session.createMutationQuery( "delete EntitySix" ).executeUpdate();
133+
} );
134+
}
135+
136+
public static Instant uuid6Instant(final UUID uuid) {
137+
assert uuid.version() == 6;
138+
139+
final var msb = uuid.getMostSignificantBits();
140+
final var ts = msb >> 4 & 0x0FFF_FFFF_FFFF_F000L | msb & 0x0FFFL;
141+
return LocalDate.of( 1582, 10, 15 ).atStartOfDay( ZoneId.of( "UTC" ) ).toInstant()
142+
.plusSeconds( ts / 10_000_000 ).plusNanos( ts % 10_000_000 * 100 );
143+
}
144+
145+
}

0 commit comments

Comments
 (0)