Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.projectnessie.versioned.impl.AbstractTestStore Maven / Gradle / Ivy
/*
* Copyright (C) 2020 Dremio
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.projectnessie.versioned.impl;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.projectnessie.versioned.impl.condition.ConditionExpression;
import org.projectnessie.versioned.impl.condition.ExpressionFunction;
import org.projectnessie.versioned.impl.condition.ExpressionPath;
import org.projectnessie.versioned.impl.condition.RemoveClause;
import org.projectnessie.versioned.impl.condition.SetClause;
import org.projectnessie.versioned.impl.condition.UpdateExpression;
import org.projectnessie.versioned.store.ConditionFailedException;
import org.projectnessie.versioned.store.Entity;
import org.projectnessie.versioned.store.HasId;
import org.projectnessie.versioned.store.LoadStep;
import org.projectnessie.versioned.store.NotFoundException;
import org.projectnessie.versioned.store.SaveOp;
import org.projectnessie.versioned.store.Store;
import org.projectnessie.versioned.store.ValueType;
import org.projectnessie.versioned.tiered.BaseValue;
/**
* Common class for testing public APIs of a Store. This class should be moved to the
* versioned/tests project when it will not introduce a circular dependency.
*
* @param The type of the Store being tested.
*/
public abstract class AbstractTestStore {
static class CreatorPair {
final ValueType> type;
final Supplier> supplier;
CreatorPair(ValueType> type, Supplier> supplier) {
this.type = type;
this.supplier = supplier;
}
}
static class EntitySaveOp> {
final ValueType type;
final PersistentBase entity;
final SaveOp saveOp;
EntitySaveOp(ValueType type, PersistentBase entity) {
this.type = type;
this.entity = entity;
this.saveOp = EntityType.forType(type).createSaveOpForEntity(entity);
}
}
protected static final ExpressionPath COMMITS = ExpressionPath.builder("commits").build();
protected static final Entity ONE = Entity.ofNumber(1);
protected static final Entity TWO = Entity.ofNumber(2);
protected Random random;
protected S store;
/** Create and start the store, if not already done. */
@BeforeEach
void setup() {
if (store == null) {
this.store = createStore();
this.store.start();
random = new Random(getRandomSeed());
}
}
/** Reset the state of the store. */
@AfterEach
void reset() {
resetStoreState();
}
/**
* Create the store that will be tested.
*
* @return the newly created (unstarted) store.
*/
protected abstract S createStore();
protected abstract S createRawStore();
protected abstract long getRandomSeed();
protected abstract void resetStoreState();
protected abstract int loadSize();
protected boolean supportsDelete() {
return true;
}
protected boolean supportsUpdate() {
return true;
}
protected boolean supportsConditionExpression() {
return true;
}
// Tests for close()
@Test
void closeWithoutStart() {
final Store localStore = createRawStore();
localStore.close(); // This should be a no-op.
}
@Test
void closeTwice() {
final Store localStore = createRawStore();
localStore.start();
localStore.close();
localStore.close(); // This should be a no-op.
}
@Nested
@DisplayName("load() tests")
class LoadTests {
@Test
void load() {
final ImmutableList creators =
ImmutableList.builder()
.add(new CreatorPair(ValueType.REF, () -> SampleEntities.createTag(random)))
.add(new CreatorPair(ValueType.REF, () -> SampleEntities.createBranch(random)))
.add(
new CreatorPair(
ValueType.COMMIT_METADATA, () -> SampleEntities.createCommitMetadata(random)))
.add(new CreatorPair(ValueType.VALUE, () -> SampleEntities.createValue(random)))
.add(new CreatorPair(ValueType.L1, () -> SampleEntities.createL1(random)))
.add(new CreatorPair(ValueType.L2, () -> SampleEntities.createL2(random)))
.add(new CreatorPair(ValueType.L3, () -> SampleEntities.createL3(random)))
.add(
new CreatorPair(
ValueType.KEY_FRAGMENT, () -> SampleEntities.createFragment(random)))
.build();
final ImmutableMultimap.Builder, HasId> builder = ImmutableMultimap.builder();
for (int i = 0; i < 100; ++i) {
final int index = i % creators.size();
final HasId obj = creators.get(index).supplier.get();
builder.put(creators.get(index).type, obj);
}
final Multimap, HasId> objs = builder.build();
objs.forEach(AbstractTestStore.this::putThenLoad);
store.load(createTestLoadStep(objs));
}
@Test
void loadSteps() {
final Multimap, HasId> objs =
ImmutableMultimap., HasId>builder()
.put(ValueType.REF, SampleEntities.createBranch(random))
.put(ValueType.REF, SampleEntities.createBranch(random))
.put(ValueType.COMMIT_METADATA, SampleEntities.createCommitMetadata(random))
.build();
final Multimap, HasId> objs2 =
ImmutableMultimap., HasId>builder()
.put(ValueType.L3, SampleEntities.createL3(random))
.put(ValueType.VALUE, SampleEntities.createValue(random))
.put(ValueType.VALUE, SampleEntities.createValue(random))
.put(ValueType.VALUE, SampleEntities.createValue(random))
.put(ValueType.REF, SampleEntities.createTag(random))
.build();
objs.forEach(AbstractTestStore.this::putThenLoad);
objs2.forEach(AbstractTestStore.this::putThenLoad);
final LoadStep step2 = createTestLoadStep(objs2);
final LoadStep step1 = createTestLoadStep(objs, Optional.of(step2));
store.load(step1);
}
@Test
void loadNone() {
store.load(createTestLoadStep(ImmutableMultimap.of()));
}
@Test
void loadInvalid() {
putThenLoad(ValueType.REF, SampleEntities.createBranch(random));
final Multimap, HasId> objs =
ImmutableMultimap.of(ValueType.REF, SampleEntities.createBranch(random));
Assertions.assertThrows(NotFoundException.class, () -> store.load(createTestLoadStep(objs)));
}
@Test
void loadPagination() {
final ImmutableMultimap.Builder, HasId> builder = ImmutableMultimap.builder();
for (int i = 0; i < (10 + loadSize()); ++i) {
// Only create a single type as this is meant to test the pagination within the store, not
// the variety. Variety is
// taken care of by another test.
builder.put(ValueType.REF, SampleEntities.createTag(random));
}
final Multimap, HasId> objs = builder.build();
objs.forEach(AbstractTestStore.this::putThenLoad);
store.load(createTestLoadStep(objs));
}
private LoadStep createTestLoadStep(Multimap, HasId> objs) {
return createTestLoadStep(objs, Optional.empty());
}
@SuppressWarnings({"unchecked", "rawtypes"})
private LoadStep createTestLoadStep(
Multimap, HasId> objs, Optional next) {
final EntityLoadOps loadOps = new EntityLoadOps();
objs.forEach(
(type, val) ->
loadOps.load(
((EntityType) EntityType.forType(type)), val.getId(), r -> assertEquals(val, r)));
return loadOps.build(() -> next);
}
}
@Nested
@DisplayName("loadSingle() tests")
class LoadSingleTests {
@Test
void loadSingleInvalid() {
Assertions.assertThrows(
NotFoundException.class,
() -> EntityType.REF.loadSingle(store, SampleEntities.createId(random)));
}
@Test
void loadSingleL1() {
putThenLoad(ValueType.L1, SampleEntities.createL1(random));
}
@Test
void loadSingleL2() {
putThenLoad(ValueType.L2, SampleEntities.createL2(random));
}
@Test
void loadSingleL3() {
putThenLoad(ValueType.L3, SampleEntities.createL3(random));
}
@Test
void loadFragment() {
putThenLoad(ValueType.KEY_FRAGMENT, SampleEntities.createFragment(random));
}
@Test
void loadBranch() {
putThenLoad(ValueType.REF, SampleEntities.createBranch(random));
}
@Test
void loadTag() {
putThenLoad(ValueType.REF, SampleEntities.createTag(random));
}
@Test
void loadCommitMetadata() {
putThenLoad(ValueType.COMMIT_METADATA, SampleEntities.createCommitMetadata(random));
}
@Test
void loadValue() {
putThenLoad(ValueType.VALUE, SampleEntities.createValue(random));
}
}
@Nested
@DisplayName("putIfAbsent() tests")
class PutIfAbsentTests {
@Test
void putIfAbsentL1() {
testPutIfAbsent(ValueType.L1, SampleEntities.createL1(random));
}
@Test
void putIfAbsentL2() {
testPutIfAbsent(ValueType.L2, SampleEntities.createL2(random));
}
@Test
void putIfAbsentL3() {
testPutIfAbsent(ValueType.L3, SampleEntities.createL3(random));
}
@Test
void putIfAbsentFragment() {
testPutIfAbsent(ValueType.KEY_FRAGMENT, SampleEntities.createFragment(random));
}
@Test
void putIfAbsentBranch() {
testPutIfAbsent(ValueType.REF, SampleEntities.createBranch(random));
}
@Test
void putIfAbsentTag() {
testPutIfAbsent(ValueType.REF, SampleEntities.createTag(random));
}
@Test
void putIfAbsentCommitMetadata() {
testPutIfAbsent(ValueType.COMMIT_METADATA, SampleEntities.createCommitMetadata(random));
}
@Test
void putIfAbsentValue() {
testPutIfAbsent(ValueType.VALUE, SampleEntities.createValue(random));
}
protected , T extends PersistentBase> void testPutIfAbsent(
ValueType type, T sample) {
Assertions.assertTrue(store.putIfAbsent(new EntitySaveOp<>(type, sample).saveOp));
testLoadSingle(type, sample);
Assertions.assertFalse(store.putIfAbsent(new EntitySaveOp<>(type, sample).saveOp));
testLoadSingle(type, sample);
}
}
@Nested
@DisplayName("save() tests")
class SaveTests {
@Test
void save() {
final List> entities =
Arrays.asList(
new EntitySaveOp<>(ValueType.L1, SampleEntities.createL1(random)),
new EntitySaveOp<>(ValueType.L2, SampleEntities.createL2(random)),
new EntitySaveOp<>(ValueType.L3, SampleEntities.createL3(random)),
new EntitySaveOp<>(ValueType.KEY_FRAGMENT, SampleEntities.createFragment(random)),
new EntitySaveOp<>(ValueType.REF, SampleEntities.createBranch(random)),
new EntitySaveOp<>(ValueType.REF, SampleEntities.createTag(random)),
new EntitySaveOp<>(
ValueType.COMMIT_METADATA, SampleEntities.createCommitMetadata(random)),
new EntitySaveOp<>(ValueType.VALUE, SampleEntities.createValue(random)));
store.save(entities.stream().map(e -> e.saveOp).collect(Collectors.toList()));
Assertions.assertAll(
entities.stream()
.map(
s ->
() -> {
try {
final HasId saveOpValue = s.entity;
HasId loadedValue =
EntityType.forType(s.type).loadSingle(store, saveOpValue.getId());
Assertions.assertEquals(saveOpValue, loadedValue, "type " + s.type);
try {
loadedValue =
EntityType.forType(s.type)
.buildEntity(
producer -> {
@SuppressWarnings("rawtypes")
ValueType t = s.type;
@SuppressWarnings("rawtypes")
BaseValue p = producer;
store.loadSingle(t, saveOpValue.getId(), p);
});
Assertions.assertEquals(saveOpValue, loadedValue, "type " + s.type);
} catch (UnsupportedOperationException e) {
// TODO ignore this for now
}
} catch (NotFoundException e) {
Assertions.fail("type " + s.type, e);
}
}));
}
}
@Nested
@DisplayName("put() tests")
class PutWithoutConditionExpressionTests {
@Test
void putWithConditionValue() {
putWithCondition(ValueType.VALUE, SampleEntities.createValue(random));
}
@Test
void putWithConditionBranch() {
putWithCondition(ValueType.REF, SampleEntities.createBranch(random));
}
@Test
void putWithConditionTag() {
putWithCondition(ValueType.REF, SampleEntities.createTag(random));
}
@Test
void putWithConditionCommitMetadata() {
putWithCondition(ValueType.COMMIT_METADATA, SampleEntities.createCommitMetadata(random));
}
@Test
void putWithConditionKeyFragment() {
putWithCondition(ValueType.KEY_FRAGMENT, SampleEntities.createFragment(random));
}
@Test
void putWithConditionL1() {
putWithCondition(ValueType.L1, SampleEntities.createL1(random));
}
@Test
void putWithConditionL2() {
putWithCondition(ValueType.L2, SampleEntities.createL2(random));
}
@Test
void putWithConditionL3() {
putWithCondition(ValueType.L3, SampleEntities.createL3(random));
}
@Test
void putTwice() {
final HasId l3 = SampleEntities.createL3(random);
putThenLoad(ValueType.L3, l3);
putThenLoad(ValueType.L3, l3);
}
@Test
void putWithCompoundConditionTag() {
final InternalRef sample = SampleEntities.createTag(random);
putThenLoad(ValueType.REF, sample);
final InternalRef.Type type = InternalRef.Type.TAG;
final ConditionExpression condition =
ConditionExpression.of(
ExpressionFunction.equals(
ExpressionPath.builder(InternalRef.TYPE).build(), type.toEntity()),
ExpressionFunction.equals(
ExpressionPath.builder("name").build(), Entity.ofString("tagName")));
putConditional(ValueType.REF, sample, true, Optional.of(condition));
}
@Test
void putWithFailingConditionExpression() {
final InternalRef sample = SampleEntities.createBranch(random);
final InternalRef.Type type = InternalRef.Type.BRANCH;
final ConditionExpression condition =
ConditionExpression.of(
ExpressionFunction.equals(
ExpressionPath.builder(InternalRef.TYPE).build(), type.toEntity()),
ExpressionFunction.equals(
ExpressionPath.builder("commit").build(), Entity.ofString("notEqual")));
putConditional(ValueType.REF, sample, false, Optional.of(condition));
}
private void putWithCondition(ValueType> type, T sample) {
// Tests that attempt to put (update) an existing entry should only occur when the condition
// expression is met.
putThenLoad(type, sample);
final ExpressionPath keyName = ExpressionPath.builder(Store.KEY_NAME).build();
final ConditionExpression conditionExpression =
ConditionExpression.of(ExpressionFunction.equals(keyName, sample.getId().toEntity()));
putConditional(type, sample, true, Optional.of(conditionExpression));
}
@SuppressWarnings("unchecked")
private > void putConditional(
ValueType type,
HasId sample,
boolean shouldSucceed,
Optional conditionExpression) {
if (!supportsConditionExpression()) {
return;
}
try {
store.put(new EntitySaveOp<>(type, (PersistentBase) sample).saveOp, conditionExpression);
if (!shouldSucceed) {
Assertions.fail();
}
testLoadSingle(type, sample);
} catch (ConditionFailedException cfe) {
if (shouldSucceed) {
Assertions.fail(cfe);
}
Assertions.assertThrows(
NotFoundException.class,
() -> EntityType.forType(type).loadSingle(store, sample.getId()));
}
}
}
@Nested
@DisplayName("delete() tests")
class DeleteTests {
@Test
void deleteNoConditionValue() {
if (!supportsDelete()) {
return;
}
deleteWithCondition(
ValueType.VALUE, SampleEntities.createValue(random), true, Optional.empty());
}
@Test
void deleteNoConditionL1() {
if (!supportsDelete()) {
return;
}
deleteWithCondition(ValueType.L1, SampleEntities.createL1(random), true, Optional.empty());
}
@Test
void deleteNoConditionL2() {
if (!supportsDelete()) {
return;
}
deleteWithCondition(ValueType.L2, SampleEntities.createL2(random), true, Optional.empty());
}
@Test
void deleteNoConditionL3() {
if (!supportsDelete()) {
return;
}
deleteWithCondition(ValueType.L3, SampleEntities.createL3(random), true, Optional.empty());
}
@Test
void deleteNoConditionFragment() {
if (!supportsDelete()) {
return;
}
deleteWithCondition(
ValueType.KEY_FRAGMENT, SampleEntities.createFragment(random), true, Optional.empty());
}
@Test
void deleteNoConditionBranch() {
if (!supportsDelete()) {
return;
}
deleteWithCondition(
ValueType.REF, SampleEntities.createBranch(random), true, Optional.empty());
}
@Test
void deleteNoConditionTag() {
if (!supportsDelete()) {
return;
}
deleteWithCondition(ValueType.REF, SampleEntities.createTag(random), true, Optional.empty());
}
@Test
void deleteNoConditionCommitMetadata() {
if (!supportsDelete()) {
return;
}
deleteWithCondition(
ValueType.COMMIT_METADATA,
SampleEntities.createCommitMetadata(random),
true,
Optional.empty());
}
@Test
void deleteConditionMismatchAttributeValue() {
if (!supportsDelete() || !supportsConditionExpression()) {
return;
}
final ExpressionFunction expressionFunction =
ExpressionFunction.equals(
ExpressionPath.builder("value").build(),
SampleEntities.createStringEntity(random, random.nextInt(10) + 1));
final ConditionExpression ex = ConditionExpression.of(expressionFunction);
deleteWithCondition(
ValueType.VALUE, SampleEntities.createValue(random), false, Optional.of(ex));
}
@Test
void deleteConditionMismatchAttributeBranch() {
if (!supportsDelete() || !supportsConditionExpression()) {
return;
}
final ExpressionFunction expressionFunction =
ExpressionFunction.equals(
ExpressionPath.builder("commit").build(),
SampleEntities.createStringEntity(random, random.nextInt(10) + 1));
final ConditionExpression ex = ConditionExpression.of(expressionFunction);
deleteWithCondition(
ValueType.REF, SampleEntities.createBranch(random), false, Optional.of(ex));
}
@Test
void deleteBranchSizeFail() {
if (!supportsDelete() || !supportsConditionExpression()) {
return;
}
final ConditionExpression expression =
ConditionExpression.of(ExpressionFunction.equals(ExpressionFunction.size(COMMITS), ONE));
deleteWithCondition(
ValueType.REF, SampleEntities.createBranch(random), false, Optional.of(expression));
}
@Test
void deleteBranchSizeSucceed() {
if (!supportsDelete() || !supportsConditionExpression()) {
return;
}
final ConditionExpression expression =
ConditionExpression.of(ExpressionFunction.equals(ExpressionFunction.size(COMMITS), TWO));
deleteWithCondition(
ValueType.REF, SampleEntities.createBranch(random), true, Optional.of(expression));
}
protected void deleteWithCondition(
ValueType> type,
T sample,
boolean shouldSucceed,
Optional conditionExpression) {
putThenLoad(type, sample);
if (shouldSucceed) {
Assertions.assertTrue(store.delete(type, sample.getId(), conditionExpression));
Assertions.assertThrows(
NotFoundException.class,
() -> EntityType.forType(type).loadSingle(store, sample.getId()));
} else {
Assertions.assertFalse(store.delete(type, sample.getId(), conditionExpression));
testLoadSingle(type, sample);
}
}
}
@Nested
@DisplayName("update() tests")
class UpdateTests {
@Test
void updateWithFailedCondition() {
if (!supportsUpdate() || !supportsConditionExpression()) {
return;
}
final InternalRef tag = SampleEntities.createTag(random);
putThenLoad(ValueType.REF, tag);
final ConditionExpression expression =
ConditionExpression.of(
ExpressionFunction.equals(
ExpressionPath.builder("name").build(), Entity.ofString("badTagName")));
Assertions.assertFalse(
store.update(
ValueType.REF,
tag.getId(),
UpdateExpression.of(RemoveClause.of(ExpressionPath.builder("metadata").build())),
Optional.of(expression),
Optional.empty()));
}
@Test
void updateWithSuccessfulCondition() {
if (!supportsUpdate() || !supportsConditionExpression()) {
return;
}
final String tagName = "myTag";
final InternalTag tag = SampleEntities.createTag(random).getTag();
putThenLoad(ValueType.REF, tag);
final ConditionExpression expression =
ConditionExpression.of(
ExpressionFunction.equals(
ExpressionPath.builder("name").build(), Entity.ofString("tagName")));
final InternalRef.Builder> builder = EntityType.REF.newEntityProducer();
final boolean result =
store.update(
ValueType.REF,
tag.getId(),
UpdateExpression.of(
SetClause.equals(
ExpressionPath.builder("name").build(), Entity.ofString(tagName))),
Optional.of(expression),
Optional.of(builder));
Assertions.assertTrue(result);
final InternalTag updated = builder.build().getTag();
Assertions.assertEquals(tag.getId(), updated.getId());
Assertions.assertEquals(tag.getCommit(), updated.getCommit());
Assertions.assertEquals(tag.getDt(), updated.getDt());
Assertions.assertNotEquals(tag.getName(), updated.getName());
Assertions.assertEquals(tagName, updated.getName());
testLoadSingle(ValueType.REF, updated);
}
@Test
protected void updateRemoveOneArray() {
updateRemoveArray(
UpdateExpression.of(RemoveClause.of(ExpressionPath.builder("keys").position(0).build())),
1,
10);
}
@Test
protected void updateRemoveOneArrayEnd() {
updateRemoveArray(
UpdateExpression.of(RemoveClause.of(ExpressionPath.builder("keys").position(9).build())),
0,
9);
}
@Test
protected void updateRemoveMultipleArrayAscending() {
UpdateExpression update = UpdateExpression.of();
for (int i = 0; i < 5; ++i) {
update = update.and(RemoveClause.of(ExpressionPath.builder("keys").position(i).build()));
}
updateRemoveArray(update, 5, 10);
}
@Test
protected void updateRemoveMultipleArrayDescending() {
UpdateExpression update = UpdateExpression.of();
for (int i = 4; i >= 0; --i) {
update = update.and(RemoveClause.of(ExpressionPath.builder("keys").position(i).build()));
}
updateRemoveArray(update, 5, 10);
}
@Test
void updateSetEquals() {
if (!supportsUpdate()) {
return;
}
final String tagName = "myTag";
final InternalTag tag = SampleEntities.createTag(random).getTag();
putThenLoad(ValueType.REF, tag);
final InternalRef.Builder> builder = EntityType.REF.newEntityProducer();
final boolean result =
store.update(
ValueType.REF,
tag.getId(),
UpdateExpression.of(
SetClause.equals(
ExpressionPath.builder("name").build(), Entity.ofString(tagName))),
Optional.empty(),
Optional.of(builder));
Assertions.assertTrue(result);
final InternalTag updated = builder.build().getTag();
Assertions.assertEquals(tag.getId(), updated.getId());
Assertions.assertEquals(tag.getCommit(), updated.getCommit());
Assertions.assertEquals(tag.getDt(), updated.getDt());
Assertions.assertNotEquals(tag.getName(), updated.getName());
Assertions.assertEquals(tagName, updated.getName());
testLoadSingle(ValueType.REF, updated);
}
@Test
void updateSetListAppend() {
if (!supportsUpdate()) {
return;
}
final String key = "newKey";
final InternalFragment fragment = SampleEntities.createFragment(random);
putThenLoad(ValueType.KEY_FRAGMENT, fragment);
final InternalFragment.Builder builder = EntityType.KEY_FRAGMENT.newEntityProducer();
final boolean result =
store.update(
ValueType.KEY_FRAGMENT,
fragment.getId(),
UpdateExpression.of(
SetClause.appendToList(
ExpressionPath.builder("keys").build(),
Entity.ofList(Entity.ofList(Entity.ofString("24"), Entity.ofString(key))))),
Optional.empty(),
Optional.of(builder));
Assertions.assertTrue(result);
final InternalFragment updated = builder.build();
Assertions.assertEquals(fragment.getId(), updated.getId());
final List oldKeys = new ArrayList<>(fragment.getKeys());
oldKeys.add(
InternalKeyWithPayload.of(
(byte) 24, InternalKey.fromEntity(Entity.ofList(Entity.ofString(key)))));
Assertions.assertEquals(oldKeys, updated.getKeys());
testLoadSingle(ValueType.KEY_FRAGMENT, updated);
}
private void updateRemoveArray(
UpdateExpression update, int beginArrayIndex, int endArrayIndex) {
if (!supportsUpdate()) {
return;
}
final InternalFragment fragment = SampleEntities.createFragment(random);
putThenLoad(ValueType.KEY_FRAGMENT, fragment);
final InternalFragment.Builder builder = EntityType.KEY_FRAGMENT.newEntityProducer();
final boolean result =
store.update(
ValueType.KEY_FRAGMENT,
fragment.getId(),
update,
Optional.empty(),
Optional.of(builder));
Assertions.assertTrue(result);
final InternalFragment updated = builder.build();
Assertions.assertEquals(fragment.getId(), updated.getId());
final List oldKeys =
fragment.getKeys().subList(beginArrayIndex, endArrayIndex);
Assertions.assertEquals(oldKeys, updated.getKeys());
testLoadSingle(ValueType.KEY_FRAGMENT, updated);
}
}
// Utility functions for the tests
@SuppressWarnings("unchecked")
private > void putThenLoad(ValueType type, HasId sample) {
store.put(new EntitySaveOp<>(type, (PersistentBase) sample).saveOp, Optional.empty());
testLoadSingle(type, sample);
}
@SuppressWarnings("unchecked")
protected void testLoadSingle(ValueType> type, T sample) {
final T read = (T) EntityType.forType(type).loadSingle(store, sample.getId());
assertEquals(sample, read);
}
protected static void assertEquals(HasId expected, HasId actual) {
Assertions.assertEquals(expected, actual);
Assertions.assertEquals(expected.getId(), actual.getId());
}
}