Skip to content

Commit

Permalink
FMWK-461 Update tests and documentation to have version field as inte…
Browse files Browse the repository at this point in the history
…ger (#756)
  • Loading branch information
agrgr authored Jun 23, 2024
1 parent 2d2af38 commit 2d988d7
Show file tree
Hide file tree
Showing 13 changed files with 89 additions and 46 deletions.
15 changes: 13 additions & 2 deletions src/main/asciidoc/reference/aerospike-object-mapping.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,18 @@ NOTE: AbstractAerospikeConfiguration will create an AerospikeTemplate instance a
[[mapping-usage-annotations]]
=== Mapping Annotation Overview

The MappingAerospikeConverter can use metadata to drive the mapping of objects to documents. An overview of the annotations is provided below
The MappingAerospikeConverter can use metadata to drive the mapping of objects to documents using annotations. An overview of the annotations is provided below

* `@Id` - applied at the field level to mark the field used for identity purposes.
* `@Field` - applied at the field level, describes the name of the field as it will be represented in the AerospikeDB BSON document thus allowing the name to be different from the field name of the class.
* `@Version` - applied at the field level to mark record modification count. The value must be effectively integer.
In Spring Data Aerospike, documents come in two forms – non-versioned and versioned.
Documents with an `@Version` annotation have a version field populated by the corresponding record’s generation count.
Version can be passed to a constructor or not (in that case it stays equal to zero).
* `@Expiration` - applied at the field level to mark a property to be used as expiration field.
Expiration can be specified in two flavors: as an offset in seconds from the current time (then field value must be
effectively integer) or as an absolute Unix timestamp. Client system time must be synchronized
with Aerospike server system time, otherwise expiration behaviour will be unpredictable.

The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology-agnostic. Specific subclasses are used in the AerospikeDB support to support annotation-based metadata. Other strategies are also possible to put in place if there is demand.

Expand Down Expand Up @@ -89,17 +97,20 @@ public class Person<T extends Address> {
private T address;
@Version
private int id; // must be integer
public Person(Integer ssn) {
this.ssn = ssn;
}
public Person(Integer ssn, String firstName, String lastName, Integer age, T address) {
public Person(Integer ssn, String firstName, String lastName, Integer age, T address, int version) {
this.ssn = ssn;
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.address = address;
this.version = version;
}
public String getId() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@

/**
* Demarcates a property to be used as expiration field. Expiration can be specified in two flavors: as an offset in
* seconds from the current time or as an absolute Unix time stamp. <br/><br/> Client system time must be synchronized
* with aerospike server system time, otherwise expiration behaviour will be unpredictable.
* seconds from the current time or as an absolute Unix timestamp. <br/><br/> Client system time must be synchronized
* with Aerospike server system time, otherwise expiration behaviour will be unpredictable.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public void write(Object source, final AerospikeWriteData data) {

AerospikePersistentProperty versionProperty = entity.getVersionProperty();
if (versionProperty != null) {
// version is read as Integer because Java client's Record.generation is integer
Integer version = accessor.getProperty(versionProperty, Integer.class);
data.setVersion(version);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,15 +376,15 @@ public void shouldReadExpirationForDocumentWithPersistenceConstructor(int conver

DocumentWithExpirationAnnotationAndPersistenceConstructor document =
aerospikeConverter.read(DocumentWithExpirationAnnotationAndPersistenceConstructor.class, forRead);
assertThat(document.getExpiration()).isCloseTo(TimeUnit.MINUTES.toSeconds(1), Offset.offset(100L));
assertThat(document.getExpiration()).isCloseTo((int) TimeUnit.MINUTES.toSeconds(1), Offset.offset(100));
}

@ParameterizedTest()
@ValueSource(ints = {0, 1})
public void shouldNotWriteVersionToBins(int converterOption) {
MappingAerospikeConverter aerospikeConverter = getAerospikeMappingConverterByOption(converterOption);
AerospikeWriteData forWrite = AerospikeWriteData.forWrite(NAMESPACE);
aerospikeConverter.write(new VersionedClass("id", "data", 42L), forWrite);
aerospikeConverter.write(new VersionedClass("id", "data", 42), forWrite);

assertThat(forWrite.getBins()).containsOnly(
new Bin("@_class", VersionedClass.class.getName()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,7 @@ void shouldWriteAndReadNestedPOJOs() {
new Address(new Street("Street1", 1), 1),
new Address(new Street("Street2", 2), 2)
);
SampleClasses.idAndAddressesList testObj = new idAndAddressesList("testId", addressesList);
IdAndAddressesList testObj = new IdAndAddressesList("testId", addressesList);
converter.write(testObj, forWrite);

assertThat(forWrite.getBins()).containsOnly(
Expand All @@ -774,9 +774,9 @@ void shouldWriteAndReadNestedPOJOs() {
);

AerospikeReadData forRead = AerospikeReadData.forRead(forWrite.getKey(), aeroRecord(forWrite.getBins()));
SampleClasses.idAndAddressesList actual = converter.read(idAndAddressesList.class, forRead);
IdAndAddressesList actual = converter.read(IdAndAddressesList.class, forRead);

assertThat(actual).isEqualTo(new idAndAddressesList("testId", addressesList));
assertThat(actual).isEqualTo(new IdAndAddressesList("testId", addressesList));
}

private <T> void assertWriteAndRead(int converterOption,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,12 @@ public void shouldExpire() {

@Test
public void shouldSaveAndGetDocumentWithImmutableExpiration() {
template.insert(new DocumentWithExpirationAnnotationAndPersistenceConstructor(id, 60L));
template.insert(new DocumentWithExpirationAnnotationAndPersistenceConstructor(id, 60));

DocumentWithExpirationAnnotationAndPersistenceConstructor doc = template.findById(id,
DocumentWithExpirationAnnotationAndPersistenceConstructor.class);
assertThat(doc).isNotNull();
assertThat(doc.getExpiration()).isCloseTo(60L, Offset.offset(10L));
assertThat(doc.getExpiration()).isCloseTo(60, Offset.offset(10));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class AerospikeTemplateFindByIdTests extends BaseBlockingIntegrationTests

@Test
public void findById_shouldReadVersionedClassWithAllArgsConstructor() {
VersionedClassWithAllArgsConstructor inserted = new VersionedClassWithAllArgsConstructor(id, "foobar", 0L);
VersionedClassWithAllArgsConstructor inserted = new VersionedClassWithAllArgsConstructor(id, "foobar", 0);
template.insert(inserted);
assertThat(template.findById(id, VersionedClassWithAllArgsConstructor.class).getVersion()).isEqualTo(1L);
template.update(new VersionedClassWithAllArgsConstructor(id, "foobar1", inserted.getVersion()));
Expand All @@ -54,7 +54,7 @@ public void findById_shouldReadVersionedClassWithAllArgsConstructor() {

@Test
public void findById_shouldReadVersionedClassWithAllArgsConstructorAndSetName() {
VersionedClassWithAllArgsConstructor inserted = new VersionedClassWithAllArgsConstructor(id, "foobar", 0L);
VersionedClassWithAllArgsConstructor inserted = new VersionedClassWithAllArgsConstructor(id, "foobar", 0);
template.insert(inserted, OVERRIDE_SET_NAME);
assertThat(template.findById(id, VersionedClassWithAllArgsConstructor.class, OVERRIDE_SET_NAME)
.getVersion()).isEqualTo(1L);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,23 @@
import com.aerospike.client.Key;
import com.aerospike.client.Record;
import com.aerospike.client.policy.Policy;
import lombok.Data;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.aerospike.BaseBlockingIntegrationTests;
import org.springframework.data.aerospike.mapping.Document;
import org.springframework.data.aerospike.sample.Person;
import org.springframework.data.aerospike.sample.SampleClasses.CustomCollectionClass;
import org.springframework.data.aerospike.sample.SampleClasses.DocumentWithByteArray;
import org.springframework.data.aerospike.util.AsyncUtils;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;

import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -142,14 +147,40 @@ public void insertsDocumentWithZeroVersionIfThereIsNoDocumentWithSameKey() {

@Test
public void insertsDocumentWithVersionGreaterThanZeroIfThereIsNoDocumentWithSameKey() {
VersionedClass document = new VersionedClass(id, "any", 5L);
VersionedClass document = new VersionedClass(id, "any", 5);
// initially given versions are ignored
// RecordExistsAction.CREATE_ONLY is used
template.insert(document);

assertThat(document.getVersion()).isEqualTo(1);
}


@Data
@Document(collection = "versioned-set")
public static class ClassWithLongVersion {

@Version
private long version; // must be integer
private String field;
@Id
private String id;

public ClassWithLongVersion(String id, String field, long version) {
this.id = id;
this.field = field;
this.version = version;
}
}

@Test
public void mustUseIntegerVersion() {
ClassWithLongVersion document = new ClassWithLongVersion(id, "any", Long.MAX_VALUE);
assertThatThrownBy(() -> template.insert(document))
.isInstanceOf(ConversionFailedException.class)
.hasMessageContaining("Failed to convert from type [java.lang.Long] to type [java.lang.Integer]");
}

@Test
public void throwsExceptionForDuplicateId() {
Person person = new Person(id, "Amol", 28);
Expand All @@ -161,7 +192,7 @@ public void throwsExceptionForDuplicateId() {

@Test
public void throwsExceptionForDuplicateIdForVersionedDocument() {
VersionedClass document = new VersionedClass(id, "any", 5L);
VersionedClass document = new VersionedClass(id, "any", 5);

template.insert(document);
assertThatThrownBy(() -> template.insert(document))
Expand Down Expand Up @@ -275,8 +306,8 @@ public void shouldInsertAllVersionedDocuments() {
// batch write operations are supported starting with Server version 6.0+
if (serverVersionSupport.isBatchWriteSupported()) {
VersionedClass first = new VersionedClass(id, "foo");
VersionedClass second = new VersionedClass(nextId(), "foo", 1L);
VersionedClass third = new VersionedClass(nextId(), "foo", 2L);
VersionedClass second = new VersionedClass(nextId(), "foo", 1);
VersionedClass third = new VersionedClass(nextId(), "foo", 2);

// initially given versions are ignored
// RecordExistsAction.CREATE_ONLY is used
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public void shouldReplaceAllBinsPresentInAerospikeWhenSavingDocument() {
Key key = new Key(getNameSpace(), template.getSetName(VersionedClass.class), id);
additionalAerospikeTestOperations.addNewFieldToSavedDataInAerospike(key);

template.save(new VersionedClass(id, "foo2", 2L));
template.save(new VersionedClass(id, "foo2", 2));

Record aeroRecord = client.get(new Policy(), key);
assertThat(aeroRecord.bins.get("notPresent")).isNull();
Expand Down Expand Up @@ -126,9 +126,9 @@ public void shouldUpdateNotVersionedDocumentIfItAlreadyExists() {
@Test
public void shouldSaveDocumentWithEqualVersion() {
// if an object has version property, GenerationPolicy.EXPECT_GEN_EQUAL is used
VersionedClass first = new VersionedClass(id, "foo", 0L);
VersionedClass second = new VersionedClass(id, "foo", 1L);
VersionedClass third = new VersionedClass(id, "foo", 2L);
VersionedClass first = new VersionedClass(id, "foo", 0);
VersionedClass second = new VersionedClass(id, "foo", 1);
VersionedClass third = new VersionedClass(id, "foo", 2);

template.save(first);
template.save(second);
Expand All @@ -141,7 +141,7 @@ public void shouldSaveDocumentWithEqualVersion() {

@Test
public void shouldFailSaveNewDocumentWithVersionGreaterThanZero() {
assertThatThrownBy(() -> template.save(new VersionedClass(nextId(), "foo", 5L)))
assertThatThrownBy(() -> template.save(new VersionedClass(nextId(), "foo", 5)))
.isInstanceOf(OptimisticLockingFailureException.class);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,8 +496,8 @@ public void updateAllIfDocumentsChanged() {
VersionedClass first = new VersionedClass("id1", "foo");

// In this case non-zero versions get explicitly passed to the constructor
VersionedClass second = new VersionedClass("id2", "foo", 1L);
VersionedClass third = new VersionedClass("id3", "foo", 2L);
VersionedClass second = new VersionedClass("id2", "foo", 1);
VersionedClass third = new VersionedClass("id3", "foo", 2);

// Insert multiple versioned documents to create new DB records
template.insertAll(List.of(first, second, third));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,15 @@ public void insertsDocumentWithZeroVersionIfThereIsNoDocumentWithSameKey() {

@Test
public void insertsDocumentWithVersionGreaterThanZeroIfThereIsNoDocumentWithSameKey() {
VersionedClass document = new VersionedClass(id, "any", 5L);
VersionedClass document = new VersionedClass(id, "any", 5);
reactiveTemplate.insert(document).subscribeOn(Schedulers.parallel()).block();

assertThat(document.getVersion()).isEqualTo(1);
}

@Test
public void insertsDocumentWithVersionGreaterThanZeroIfThereIsNoDocumentWithSameKeyAndSetName() {
VersionedClass document = new VersionedClass(id, "any", 5L);
VersionedClass document = new VersionedClass(id, "any", 5);
reactiveTemplate.insert(document, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()).block();

assertThat(document.getVersion()).isEqualTo(1);
Expand Down Expand Up @@ -177,7 +177,7 @@ public void throwsExceptionForDuplicateIdAndSetName() {

@Test
public void throwsExceptionForDuplicateIdForVersionedDocument() {
VersionedClass document = new VersionedClass(id, "any", 5L);
VersionedClass document = new VersionedClass(id, "any", 5);

reactiveTemplate.insert(document).subscribeOn(Schedulers.parallel()).block();
StepVerifier.create(reactiveTemplate.insert(document).subscribeOn(Schedulers.parallel()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ public void saveWithSetName_shouldSaveAndSetVersion() {

@Test
public void save_shouldNotSaveDocumentIfItAlreadyExistsWithZeroVersion() {
reactiveTemplate.save(new VersionedClass(id, "foo", 0L))
reactiveTemplate.save(new VersionedClass(id, "foo", 0))
.subscribeOn(Schedulers.parallel()).block();

StepVerifier.create(reactiveTemplate.save(new VersionedClass(id, "foo", 0L))
StepVerifier.create(reactiveTemplate.save(new VersionedClass(id, "foo", 0))
.subscribeOn(Schedulers.parallel()))
.expectError(OptimisticLockingFailureException.class)
.verify();
Expand All @@ -61,13 +61,13 @@ public void save_shouldNotSaveDocumentIfItAlreadyExistsWithZeroVersion() {
public void save_shouldSaveDocumentWithEqualVersion() {
reactiveTemplate.save(new VersionedClass(id, "foo")).subscribeOn(Schedulers.parallel()).block();

reactiveTemplate.save(new VersionedClass(id, "foo", 1L)).subscribeOn(Schedulers.parallel()).block();
reactiveTemplate.save(new VersionedClass(id, "foo", 2L)).subscribeOn(Schedulers.parallel()).block();
reactiveTemplate.save(new VersionedClass(id, "foo", 1)).subscribeOn(Schedulers.parallel()).block();
reactiveTemplate.save(new VersionedClass(id, "foo", 2)).subscribeOn(Schedulers.parallel()).block();
}

@Test
public void save_shouldFailSaveNewDocumentWithVersionGreaterThanZero() {
StepVerifier.create(reactiveTemplate.save(new VersionedClass(id, "foo", 5L))
StepVerifier.create(reactiveTemplate.save(new VersionedClass(id, "foo", 5))
.subscribeOn(Schedulers.parallel()))
.expectError(OptimisticLockingFailureException.class)
.verify();
Expand Down Expand Up @@ -234,7 +234,7 @@ public void save_shouldReplaceAllBinsPresentInAerospikeWhenSavingDocument() {
reactiveTemplate.save(first).subscribeOn(Schedulers.parallel()).block();
additionalAerospikeTestOperations.addNewFieldToSavedDataInAerospike(key);

reactiveTemplate.save(new VersionedClass(id, "foo2", 2L))
reactiveTemplate.save(new VersionedClass(id, "foo2", 2))
.subscribeOn(Schedulers.parallel()).block();

StepVerifier.create(reactorClient.get(new Policy(), key))
Expand Down
Loading

0 comments on commit 2d988d7

Please sign in to comment.