/*
* ToroDB
* Copyright © 2014 8Kdata Technology (www.8kdata.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.torodb.core.transaction.metainf;
import com.google.common.collect.Maps;
import com.torodb.core.TableRef;
import com.torodb.core.annotations.DoNotChange;
import org.jooq.lambda.Seq;
import org.jooq.lambda.tuple.Tuple2;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Stream;
/**
*
*/
public class WrapperMutableMetaCollection implements MutableMetaCollection {
private final ImmutableMetaCollection wrapped;
private final HashMap<TableRef, WrapperMutableMetaDocPart> newDocParts;
private final Set<WrapperMutableMetaDocPart> modifiedMetaDocParts;
private final HashMap<String, Tuple2<MutableMetaIndex, MetaElementState>> indexesByName;
private final Consumer<WrapperMutableMetaCollection> changeConsumer;
private final Map<String, Tuple2<MutableMetaIndex, MetaElementState>> aliveIndexesMap;
public WrapperMutableMetaCollection(ImmutableMetaCollection wrappedCollection,
Consumer<WrapperMutableMetaCollection> changeConsumer) {
this.wrapped = wrappedCollection;
this.changeConsumer = changeConsumer;
this.newDocParts = new HashMap<>();
modifiedMetaDocParts = new HashSet<>();
wrappedCollection.streamContainedMetaDocParts().forEach((docPart) -> {
WrapperMutableMetaDocPart mutable = createMetaDocPart(docPart);
newDocParts.put(mutable.getTableRef(), mutable);
});
this.indexesByName = new HashMap<>();
wrappedCollection.streamContainedMetaIndexes().forEach((index) -> {
WrapperMutableMetaIndex mutable = createMetaIndex(index);
indexesByName.put(mutable.getName(), new Tuple2<>(mutable, MetaElementState.NOT_CHANGED));
});
aliveIndexesMap = Maps.filterValues(indexesByName, tuple -> tuple.v2().isAlive());
}
protected WrapperMutableMetaDocPart createMetaDocPart(ImmutableMetaDocPart immutable) {
return new WrapperMutableMetaDocPart(immutable, this::onDocPartChange);
}
protected WrapperMutableMetaIndex createMetaIndex(ImmutableMetaIndex immutable) {
return new WrapperMutableMetaIndex(immutable, this::onIndexChange);
}
@Override
public WrapperMutableMetaDocPart addMetaDocPart(TableRef tableRef, String tableId) throws
IllegalArgumentException {
if (getMetaDocPartByTableRef(tableRef) != null) {
throw new IllegalArgumentException("There is another doc part whose table ref is "
+ tableRef);
}
assert getMetaDocPartByIdentifier(tableId) == null : "There is another doc part whose id is "
+ tableRef;
WrapperMutableMetaDocPart result = createMetaDocPart(
new ImmutableMetaDocPart(tableRef, tableId));
newDocParts.put(tableRef, result);
onDocPartChange(result);
return result;
}
@Override
@DoNotChange
public Iterable<? extends WrapperMutableMetaDocPart> getModifiedMetaDocParts() {
return modifiedMetaDocParts;
}
@Override
public MutableMetaIndex addMetaIndex(String name, boolean unique)
throws IllegalArgumentException {
if (getMetaIndexByName(name) != null) {
throw new IllegalArgumentException("There is another index whose name is " + name);
}
WrapperMutableMetaIndex result = createMetaIndex(
new ImmutableMetaIndex(name, unique));
indexesByName.put(name, new Tuple2<>(result, MetaElementState.ADDED));
changeConsumer.accept(this);
return result;
}
@Override
public boolean removeMetaIndexByName(String indexName) {
WrapperMutableMetaIndex metaIndex = getMetaIndexByName(indexName);
if (metaIndex == null) {
return false;
}
indexesByName.put(metaIndex.getName(), new Tuple2<>(metaIndex, MetaElementState.REMOVED));
changeConsumer.accept(this);
return true;
}
@Override
public Iterable<Tuple2<MutableMetaIndex, MetaElementState>> getModifiedMetaIndexes() {
return Maps.filterValues(indexesByName, tuple -> tuple.v2().hasChanged())
.values();
}
@Override
public ImmutableMetaCollection immutableCopy() {
if (modifiedMetaDocParts.isEmpty() && indexesByName.values().stream().noneMatch(tuple -> tuple
.v2().hasChanged())) {
return wrapped;
} else {
ImmutableMetaCollection.Builder builder = new ImmutableMetaCollection.Builder(wrapped);
for (MutableMetaDocPart modifiedMetaDocPart : modifiedMetaDocParts) {
builder.put(modifiedMetaDocPart.immutableCopy());
}
indexesByName.values()
.forEach(tuple -> {
switch (tuple.v2()) {
case ADDED:
case MODIFIED:
case NOT_CHANGED:
builder.put(tuple.v1().immutableCopy());
break;
case REMOVED:
builder.remove(tuple.v1());
break;
case NOT_EXISTENT:
default:
throw new AssertionError("Unexpected case" + tuple.v2());
}
});
return builder.build();
}
}
@Override
public String getName() {
return wrapped.getName();
}
@Override
public String getIdentifier() {
return wrapped.getIdentifier();
}
@Override
public Stream<? extends WrapperMutableMetaDocPart> streamContainedMetaDocParts() {
return newDocParts.values().stream();
}
@Override
public WrapperMutableMetaDocPart getMetaDocPartByIdentifier(String docPartId) {
Optional<WrapperMutableMetaDocPart> newDocPart = newDocParts.values().stream()
.filter((docPart) -> docPart.getIdentifier().equals(docPartId))
.findAny();
return newDocPart.orElse(null);
}
@Override
public WrapperMutableMetaDocPart getMetaDocPartByTableRef(TableRef tableRef) {
return newDocParts.get(tableRef);
}
@Override
public Stream<? extends WrapperMutableMetaIndex> streamContainedMetaIndexes() {
return aliveIndexesMap.values().stream().map(tuple -> (WrapperMutableMetaIndex) tuple.v1());
}
@Override
public WrapperMutableMetaIndex getMetaIndexByName(String indexName) {
Tuple2<MutableMetaIndex, MetaElementState> tuple = aliveIndexesMap.get(indexName);
if (tuple == null) {
return null;
}
return (WrapperMutableMetaIndex) tuple.v1();
}
@Override
public List<Tuple2<MetaIndex, List<String>>> getMissingIndexesForNewField(
MutableMetaDocPart docPart,
MetaField newField) {
return wrapped.getMissingIndexesForNewField(streamContainedMetaIndexes(), docPart, newField);
}
@Override
public Optional<? extends MetaIndex> getAnyMissedIndex(MetaCollection oldCol,
MutableMetaDocPart newStructure, ImmutableMetaDocPart oldStructure,
ImmutableMetaField newField) {
return oldCol.streamContainedMetaIndexes()
.filter(oldIndex -> oldIndex
.getMetaIndexFieldByTableRefAndName(oldStructure.getTableRef(), newField.getName())
!= null && (getMetaIndexByName(oldIndex.getName()) == null || Seq.seq(oldIndex
.iteratorMetaDocPartIndexesIdentifiers(newStructure))
.filter(identifiers -> identifiers.contains(newField.getIdentifier()))
.anyMatch(identifiers -> newStructure.streamIndexes()
.noneMatch(newDocPartIndex -> oldIndex.isMatch(newStructure, identifiers,
newDocPartIndex)))))
.findAny();
}
@Override
public Optional<ImmutableMetaIndex> getAnyMissedIndex(ImmutableMetaCollection oldCol,
ImmutableMetaIdentifiedDocPartIndex newRemovedDocPartIndex) {
return oldCol.streamContainedMetaIndexes()
.flatMap(oldIndex -> oldIndex.streamTableRefs()
.map(tableRef -> oldCol.getMetaDocPartByTableRef(tableRef))
.filter(oldDocPart -> oldDocPart != null && oldIndex.isCompatible(oldDocPart,
newRemovedDocPartIndex) && Seq.seq(getModifiedMetaIndexes())
.noneMatch(newIndex -> newIndex.v2() == MetaElementState.REMOVED && newIndex
.v1().getName().equals(oldIndex.getName())))
.map(tableRef -> oldIndex))
.findAny();
}
@Override
public Optional<? extends MetaIndex> getAnyRelatedIndex(ImmutableMetaCollection oldCol,
MetaDocPart newStructure, ImmutableMetaIdentifiedDocPartIndex newDocPartIndex) {
Optional<? extends MetaIndex> anyNewRelatedIndex =
Seq.seq(getModifiedMetaIndexes())
.map(modIndex -> modIndex.v1())
.filter(newIndex -> newIndex.isCompatible(newStructure, newDocPartIndex))
.findAny();
if (anyNewRelatedIndex.isPresent()) {
return anyNewRelatedIndex;
}
Optional<ImmutableMetaIndex> anyOldRelatedIndex = oldCol.streamContainedMetaIndexes()
.filter(oldIndex -> oldIndex.isCompatible(newStructure, newDocPartIndex))
.findAny();
return anyOldRelatedIndex;
}
@Override
public Optional<ImmutableMetaIndex> getAnyConflictingIndex(
ImmutableMetaCollection oldStructure, MutableMetaIndex newIndex) {
return oldStructure.streamContainedMetaIndexes()
.filter(index -> index.isMatch(newIndex) && Seq.seq(getModifiedMetaIndexes())
.noneMatch(modifiedIndex -> modifiedIndex.v2() == MetaElementState.REMOVED
&& modifiedIndex.v1().getName().equals(index.getName())))
.findAny();
}
@Override
public Optional<ImmutableMetaDocPart> getAnyDocPartWithMissedDocPartIndex(
ImmutableMetaCollection oldStructure, MutableMetaIndex newIndex) {
return newIndex.streamTableRefs()
.map(tableRef -> oldStructure.getMetaDocPartByTableRef(tableRef))
.filter(docPart -> docPart != null && newIndex.isCompatible(docPart) && Seq.seq(newIndex
.iteratorMetaDocPartIndexesIdentifiers(docPart))
.filter(identifiers -> docPart.streamIndexes()
.noneMatch(docPartIndex -> newIndex.isMatch(docPart, identifiers, docPartIndex)))
.anyMatch(identifiers -> {
MutableMetaDocPart newDocPart = getMetaDocPartByTableRef(docPart.getTableRef());
return Seq.seq(newDocPart.getModifiedMetaDocPartIndexes())
.filter(docPartIndex -> docPartIndex.v2() != MetaElementState.REMOVED)
.noneMatch(docPartIndex -> newIndex.isMatch(newDocPart, identifiers,
docPartIndex.v1()));
}))
.findAny();
}
@Override
public Optional<? extends MetaIdentifiedDocPartIndex> getAnyOrphanDocPartIndex(
ImmutableMetaCollection oldStructure, MutableMetaIndex newRemovedIndex) {
return newRemovedIndex.streamTableRefs()
.map(tableRef -> (MetaDocPart) oldStructure.getMetaDocPartByTableRef(tableRef))
.filter(docPart -> docPart != null && newRemovedIndex.isCompatible(docPart))
.flatMap(oldDocPart -> oldDocPart.streamIndexes()
.filter(oldDocPartIndex -> newRemovedIndex.isCompatible(oldDocPart, oldDocPartIndex)
&& Seq.seq(getMetaDocPartByTableRef(oldDocPart.getTableRef())
.getModifiedMetaDocPartIndexes())
.noneMatch(newDocPartIndex -> newDocPartIndex.v2() == MetaElementState.REMOVED
&& newDocPartIndex.v1().getIdentifier().equals(oldDocPartIndex
.getIdentifier())) && oldStructure.streamContainedMetaIndexes()
.noneMatch(oldIndex -> oldIndex.isCompatible(oldDocPart, oldDocPartIndex) && Seq
.seq(getModifiedMetaIndexes())
.noneMatch(newIndex -> newIndex.v2() == MetaElementState.REMOVED && newIndex
.v1().getName().equals(oldIndex.getName()))
)
)
)
.findAny();
}
@Override
public String toString() {
return defautToString();
}
protected void onDocPartChange(WrapperMutableMetaDocPart changedDocPart) {
modifiedMetaDocParts.add(changedDocPart);
changeConsumer.accept(this);
}
private boolean isTransitionAllowed(MetaIndex metaIndex, MetaElementState newState) {
MetaElementState oldState;
Tuple2<MutableMetaIndex, MetaElementState> tuple = indexesByName.get(metaIndex.getName());
if (tuple == null) {
oldState = MetaElementState.NOT_EXISTENT;
} else {
oldState = tuple.v2();
}
oldState.assertLegalTransition(newState);
return true;
}
protected void onIndexChange(WrapperMutableMetaIndex changedIndex) {
assert isTransitionAllowed(changedIndex, MetaElementState.MODIFIED);
indexesByName.put(
changedIndex.getName(),
new Tuple2<>(changedIndex, MetaElementState.MODIFIED)
);
changeConsumer.accept(this);
}
}