Skip to content

Commit 228aa11

Browse files
authored
Merge pull request #787 from FgForrest/786-chainindex-corrupted-in-consolidation
fix: ChainIndex corrupted in consolidation
2 parents c9d608b + db3d675 commit 228aa11

File tree

1 file changed

+39
-24
lines changed

1 file changed

+39
-24
lines changed

evita_engine/src/main/java/io/evitadb/index/attribute/ChainIndex.java

+39-24
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* | __/\ V /| | || (_| | |_| | |_) |
77
* \___| \_/ |_|\__\__,_|____/|____/
88
*
9-
* Copyright (c) 2023-2024
9+
* Copyright (c) 2023-2025
1010
*
1111
* Licensed under the Business Source License, Version 1.1 (the "License");
1212
* you may not use this file except in compliance with the License.
@@ -303,7 +303,7 @@ public ConsistencyReport getConsistencyReport() {
303303
int previousElementId = headElementId;
304304
for (int i = 0; i < unorderedList.length; i++) {
305305
int elementId = unorderedList[i];
306-
final ChainElementState state = elementStates.get(elementId);
306+
final ChainElementState state = this.elementStates.get(elementId);
307307
if (state == null) {
308308
errors.append("\nThe element `")
309309
.append(elementId)
@@ -594,7 +594,7 @@ private OptionalInt removeSuccessorElement(int primaryKey, int chainHeadPk) {
594594
reclassifyChain(subChainWithoutRemovedElement[0], subChainWithoutRemovedElement);
595595

596596
// verify whether the head of chain was not in circular conflict and if so
597-
verifyIfCircularDependencyExistsAndIsBroken(existingStateHeadState);
597+
verifyIfCircularDependencyExistsAndIsBroken(chainHeadPk, existingStateHeadState);
598598
} else {
599599
// just remove it from the chain
600600
chain.removeRange(index, chain.getLength());
@@ -677,20 +677,21 @@ private void introduceNewSuccessorChain(int primaryKey, @Nonnull ChainableType p
677677
*/
678678
private void updatePredecessor(int primaryKey, @Nonnull ChainableType predecessor, @Nonnull ChainElementState existingState) {
679679
// the primary key was already present in the index - we need to relocate it
680-
final TransactionalUnorderedIntArray existingChain = this.chains.get(existingState.inChainOfHeadWithPrimaryKey());
680+
final int primaryKeyOfExistingHeadState = existingState.inChainOfHeadWithPrimaryKey();
681+
final TransactionalUnorderedIntArray existingChain = this.chains.get(primaryKeyOfExistingHeadState);
681682
final int index = existingChain.indexOf(primaryKey);
682683
// sanity check - the primary key must be present in the chain according to the state information
683684
Assert.isPremiseValid(
684685
index >= 0,
685686
"Index damaged! The primary key `" + primaryKey + "` must be present in the chain according to the state information!"
686687
);
687688
// we need to remember original state of the chain head before changes in order to resolve possible circular dependency
688-
final ChainElementState existingStateHeadState = this.elementStates.get(existingState.inChainOfHeadWithPrimaryKey());
689+
final ChainElementState existingStateHeadState = this.elementStates.get(primaryKeyOfExistingHeadState);
689690
// if newly created element is a head of its chain
690691
if (predecessor.isHead()) {
691-
updateElementToBecomeHeadOfTheChain(primaryKey, index, predecessor, existingChain, existingStateHeadState);
692+
updateElementToBecomeHeadOfTheChain(primaryKey, index, predecessor, existingChain, primaryKeyOfExistingHeadState, existingStateHeadState);
692693
} else {
693-
updateElementWithinExistingChain(primaryKey, index, predecessor, existingChain, existingStateHeadState, existingState);
694+
updateElementWithinExistingChain(primaryKey, index, predecessor, existingChain, primaryKeyOfExistingHeadState, existingStateHeadState, existingState);
694695
}
695696
}
696697

@@ -701,13 +702,15 @@ private void updatePredecessor(int primaryKey, @Nonnull ChainableType predecesso
701702
* @param index index of the element in the chain
702703
* @param predecessor pointer record to a predecessor element of the `primaryKey` element
703704
* @param existingChain existing chain where the element is present
704-
* @param existingStateHeadState state of the head element of the existing chain where element is present
705+
* @param primaryKeyOfExistingHeadState primary key the `existingHeadState` is registered in `this.elementStates`
706+
* @param existingHeadState state of the head element of the existing chain where element is present
705707
*/
706708
private void updateElementToBecomeHeadOfTheChain(
707709
int primaryKey,
708710
int index, @Nonnull ChainableType predecessor,
709711
@Nonnull TransactionalUnorderedIntArray existingChain,
710-
@Nonnull ChainElementState existingStateHeadState
712+
int primaryKeyOfExistingHeadState,
713+
@Nonnull ChainElementState existingHeadState
711714
) {
712715
// if the primary key is not located at the head of the chain
713716
if (index > 0) {
@@ -717,7 +720,7 @@ private void updateElementToBecomeHeadOfTheChain(
717720
this.chains.put(primaryKey, new TransactionalUnorderedIntArray(subChain));
718721
reclassifyChain(primaryKey, subChain);
719722
// we need to re-check whether the original chain has still circular dependency when this chain was split
720-
verifyIfCircularDependencyExistsAndIsBroken(existingStateHeadState);
723+
verifyIfCircularDependencyExistsAndIsBroken(primaryKeyOfExistingHeadState, existingHeadState);
721724
} else {
722725
// we just need to change the state, since the chain is already present
723726
}
@@ -736,6 +739,7 @@ private void updateElementToBecomeHeadOfTheChain(
736739
* @param index index of the element in the chain
737740
* @param predecessor pointer record to a predecessor element of the `primaryKey` element
738741
* @param existingChain existing chain where the element is present
742+
* @param primaryKeyOfExistingHeadState primary key the `existingHeadState` is registered in `this.elementStates`
739743
* @param existingStateHeadState state of the head element of the existing chain where element is present
740744
* @param existingState existing state of the primary key element
741745
*/
@@ -744,6 +748,7 @@ private void updateElementWithinExistingChain(
744748
int index,
745749
@Nonnull ChainableType predecessor,
746750
@Nonnull TransactionalUnorderedIntArray existingChain,
751+
int primaryKeyOfExistingHeadState,
747752
@Nonnull ChainElementState existingStateHeadState,
748753
@Nonnull ChainElementState existingState
749754
) {
@@ -752,7 +757,7 @@ private void updateElementWithinExistingChain(
752757
// if there is circular conflict - set up a separate chain and update state
753758
if (circularConflict) {
754759
updateElementWithCircularConflict(
755-
primaryKey, index, predecessor, existingChain, existingStateHeadState, existingState
760+
primaryKey, index, predecessor, existingChain, primaryKeyOfExistingHeadState, existingStateHeadState, existingState
756761
);
757762
} else {
758763
final int[] movedChain;
@@ -837,7 +842,7 @@ private void updateElementWithinExistingChain(
837842
}
838843

839844
// verify whether the head of chain was not in circular conflict and if so
840-
verifyIfCircularDependencyExistsAndIsBroken(existingStateHeadState);
845+
verifyIfCircularDependencyExistsAndIsBroken(primaryKeyOfExistingHeadState, existingStateHeadState);
841846
}
842847
}
843848

@@ -867,6 +872,7 @@ private void examineCircularConflictInNewlyAppendedChain(int chainHeadPrimaryKey
867872
* @param index index of the element in the chain
868873
* @param predecessor pointer record to a predecessor element of the `primaryKey` element
869874
* @param existingChain existing chain where the element is present
875+
* @param primaryKeyOfExistingHeadState primary key the `existingHeadState` is registered in `this.elementStates`
870876
* @param existingStateHeadState state of the head element of the existing chain where element is present
871877
* @param existingState existing state of the primary key element
872878
*/
@@ -875,6 +881,7 @@ private void updateElementWithCircularConflict(
875881
int index,
876882
@Nonnull ChainableType predecessor,
877883
@Nonnull TransactionalUnorderedIntArray existingChain,
884+
int primaryKeyOfExistingHeadState,
878885
@Nonnull ChainElementState existingStateHeadState,
879886
@Nonnull ChainElementState existingState
880887
) {
@@ -888,7 +895,7 @@ private void updateElementWithCircularConflict(
888895
this.chains.put(primaryKey, new TransactionalUnorderedIntArray(subChain));
889896
reclassifyChain(primaryKey, subChain);
890897
// we need to re-check whether the original chain has still circular dependency when this chain was split
891-
verifyIfCircularDependencyExistsAndIsBroken(existingStateHeadState);
898+
verifyIfCircularDependencyExistsAndIsBroken(primaryKeyOfExistingHeadState, existingStateHeadState);
892899
}
893900
}
894901

@@ -897,28 +904,35 @@ private void updateElementWithCircularConflict(
897904
* If so, the method checks whether the circular dependency still exists for the current state of the chain and
898905
* if not, it is resolved.
899906
*
907+
* @param primaryKeyOfHeadState primary key that was used to get the state from `this.elementStates` for verification
900908
* @param originalChainHeadState the state of the original chain head before the change
901909
*/
902910
private void verifyIfCircularDependencyExistsAndIsBroken(
911+
int primaryKeyOfHeadState,
903912
@Nonnull ChainElementState originalChainHeadState
904913
) {
905914
// if original chain head is in circular dependency
906915
if (originalChainHeadState.state() == ElementState.CIRCULAR) {
916+
Assert.isPremiseValid(
917+
primaryKeyOfHeadState == originalChainHeadState.inChainOfHeadWithPrimaryKey(),
918+
"Primary key of the state must match the primary key of the state chain head!"
919+
);
920+
907921
final TransactionalUnorderedIntArray originalHeadChain = this.chains.get(originalChainHeadState.inChainOfHeadWithPrimaryKey());
908922
if (originalHeadChain != null) {
909923
// verify it is still in circular dependency
910924
if (originalHeadChain.indexOf(originalChainHeadState.predecessorPrimaryKey()) < 0) {
911925
// the circular dependency was broken
912926
this.elementStates.put(
913-
originalChainHeadState.inChainOfHeadWithPrimaryKey(),
927+
primaryKeyOfHeadState,
914928
new ChainElementState(originalChainHeadState, ElementState.SUCCESSOR)
915929
);
916930
}
917931
} else {
918932
// the circular dependency was broken - the chain is now part of another chain
919933
this.elementStates.compute(
920-
originalChainHeadState.inChainOfHeadWithPrimaryKey(),
921-
(k, newState) -> new ChainElementState(newState, ElementState.SUCCESSOR)
934+
primaryKeyOfHeadState,
935+
(k, existingState) -> new ChainElementState(existingState, ElementState.SUCCESSOR)
922936
);
923937
}
924938
}
@@ -966,7 +980,7 @@ private Integer attemptToCollapseChain(int element) {
966980
if (chainHeadState.state() == ElementState.SUCCESSOR) {
967981
return mergeSuccessorChainToElementChainIfPossible(chainHeadState);
968982
} else if (chainHeadState.state == ElementState.HEAD) {
969-
return findFirstSuccessorChainAndMergeToElementChain(chainHeadState);
983+
return findFirstSuccessorChainAndMergeToElementChain(chainHeadState.inChainOfHeadWithPrimaryKey());
970984
} else {
971985
return null;
972986
}
@@ -977,14 +991,16 @@ private Integer attemptToCollapseChain(int element) {
977991
* the predecessor. In such case, the chain can be merged with this chain. The chain is then merged with it and
978992
* the method returns the primary key of the new head of the chain.
979993
*
980-
* If this primary lookup fails, the {@link #findFirstSuccessorChainAndMergeToElementChain(ChainElementState)}
994+
* If this primary lookup fails, the {@link #findFirstSuccessorChainAndMergeToElementChain(int)}
981995
* method is called to verify whether we can't collapse another chain with tail element of the chain.
982996
*
983997
* @param chainHeadState state of the element pointing to chain which head element we check for predecessor chain
984998
* @return primary key of the new head of the chain or NULL if the chain can't be collapsed
985999
*/
9861000
@Nullable
987-
private Integer mergeSuccessorChainToElementChainIfPossible(@Nonnull ChainElementState chainHeadState) {
1001+
private Integer mergeSuccessorChainToElementChainIfPossible(
1002+
@Nonnull ChainElementState chainHeadState
1003+
) {
9881004
final ChainElementState predecessorState = this.elementStates.get(chainHeadState.predecessorPrimaryKey());
9891005
// predecessor may not yet be present in the index
9901006
if (predecessorState != null) {
@@ -1002,20 +1018,19 @@ private Integer mergeSuccessorChainToElementChainIfPossible(@Nonnull ChainElemen
10021018
return predecessorState.inChainOfHeadWithPrimaryKey();
10031019
}
10041020
}
1005-
return findFirstSuccessorChainAndMergeToElementChain(chainHeadState);
1021+
return findFirstSuccessorChainAndMergeToElementChain(chainHeadState.inChainOfHeadWithPrimaryKey());
10061022
}
10071023

10081024
/**
10091025
* Method checks whether there is a chain that is marked as a SUCCESSOR of a last element of the chain belonging
10101026
* to `chainHeadState`. If so, the SUCCESSOR chain is fully merged with the chain of `chainHeadState` and the
10111027
* method returns the primary key of the new head of the chain.
10121028
*
1013-
* @param chainHeadState state of the element pointing to chain for which we are looking for SUCCESSOR chain
1029+
* @param chainHeadElement the primary key of head of the chain to which we check for successor chain
10141030
* @return primary key of the new head of the chain or NULL if the chain can't be collapsed
10151031
*/
10161032
@Nullable
1017-
private Integer findFirstSuccessorChainAndMergeToElementChain(@Nonnull ChainElementState chainHeadState) {
1018-
final int chainHeadElement = chainHeadState.inChainOfHeadWithPrimaryKey();
1033+
private Integer findFirstSuccessorChainAndMergeToElementChain(int chainHeadElement) {
10191034
final TransactionalUnorderedIntArray chain = this.chains.get(chainHeadElement);
10201035
final int lastRecordId = chain.getLastRecordId();
10211036
final Optional<Integer> collapsableChain = this.chains.keySet()
@@ -1048,7 +1063,7 @@ private Integer findFirstSuccessorChainAndMergeToElementChain(@Nonnull ChainElem
10481063
chainHeadElement,
10491064
new ChainElementState(
10501065
chainHeadElement,
1051-
chainHeadState.predecessorPrimaryKey(),
1066+
chainHeadElementState.predecessorPrimaryKey(),
10521067
ElementState.CIRCULAR
10531068
)
10541069
);

0 commit comments

Comments
 (0)