
com.apple.foundationdb.async.rtree.BySlotStorageAdapter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fdb-extensions Show documentation
Show all versions of fdb-extensions Show documentation
Extensions to the FoundationDB Java API.
The newest version!
/*
* BySlotStorageAdapter.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2015-2023 Apple Inc. and the FoundationDB project authors
*
* 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 com.apple.foundationdb.async.rtree;
import com.apple.foundationdb.KeyValue;
import com.apple.foundationdb.Range;
import com.apple.foundationdb.ReadTransaction;
import com.apple.foundationdb.StreamingMode;
import com.apple.foundationdb.Transaction;
import com.apple.foundationdb.async.AsyncUtil;
import com.apple.foundationdb.subspace.Subspace;
import com.apple.foundationdb.tuple.Tuple;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import javax.annotation.Nonnull;
import java.math.BigInteger;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
/**
* Storage adapter that normalizes internal nodes such that each node slot is a key/value pair in the database.
*/
class BySlotStorageAdapter extends AbstractStorageAdapter implements StorageAdapter {
public BySlotStorageAdapter(@Nonnull final RTree.Config config, @Nonnull final Subspace subspace,
@Nonnull final Subspace nodeSlotIndexSubspace,
@Nonnull final Function hilbertValueFunction,
@Nonnull final OnWriteListener onWriteListener,
@Nonnull final OnReadListener onReadListener) {
super(config, subspace, nodeSlotIndexSubspace, hilbertValueFunction, onWriteListener, onReadListener);
}
@Override
public void writeLeafNodeSlot(@Nonnull final Transaction transaction, @Nonnull final LeafNode node, @Nonnull final ItemSlot itemSlot) {
writeNodeSlot(transaction, node, itemSlot);
}
private void writeNodeSlot(@Nonnull final Transaction transaction, @Nonnull final Node node, @Nonnull final NodeSlot nodeSlot) {
Tuple keyTuple = Tuple.from(node.getKind().getSerialized());
keyTuple = keyTuple.addAll(nodeSlot.getSlotKey(getConfig().isStoreHilbertValues()));
final byte[] packedKey = keyTuple.pack(packWithSubspace(node.getId()));
final byte[] packedValue = nodeSlot.getSlotValue().pack();
transaction.set(packedKey, packedValue);
getOnWriteListener().onKeyValueWritten(node, packedKey, packedValue);
}
@Override
public void clearLeafNodeSlot(@Nonnull final Transaction transaction, @Nonnull final LeafNode node, @Nonnull final ItemSlot itemSlot) {
clearNodeSlot(transaction, node, itemSlot);
}
private void clearNodeSlot(@Nonnull final Transaction transaction, @Nonnull final Node node, @Nonnull final NodeSlot nodeSlot) {
Tuple keyTuple = Tuple.from(node.getKind().getSerialized());
keyTuple = keyTuple.addAll(nodeSlot.getSlotKey(getConfig().isStoreHilbertValues()));
final byte[] packedKey = keyTuple.pack(packWithSubspace(node.getId()));
transaction.clear(packedKey);
getOnWriteListener().onKeyCleared(node, packedKey);
}
@Nonnull
@Override
public CompletableFuture fetchNodeInternal(@Nonnull final ReadTransaction transaction,
@Nonnull final byte[] nodeId) {
return AsyncUtil.collect(transaction.getRange(Range.startsWith(packWithSubspace(nodeId)),
ReadTransaction.ROW_LIMIT_UNLIMITED, false, StreamingMode.WANT_ALL), transaction.getExecutor())
.thenApply(keyValues -> {
if (keyValues.isEmpty()) {
return null;
}
final Node node = fromKeyValues(nodeId, keyValues);
final OnReadListener onReadListener = getOnReadListener();
onReadListener.onNodeRead(node);
keyValues.forEach(keyValue -> onReadListener.onKeyValueRead(node, keyValue.getKey(), keyValue.getValue()));
return node;
});
}
/**
* Interpret the returned key/value pairs from a fetch of slots from the database and reconstruct an in-memory
* {@link Node}.
*
* @param nodeId node id
* @param keyValues a list of key values as returned by the call to {@link Transaction#getRange(Range)}.
*
* @return an instance of a subclass of {@link Node} specific to the node kind as read from the database.
*/
@SuppressWarnings("ConstantValue")
@Nonnull
private Node fromKeyValues(@Nonnull final byte[] nodeId, final List keyValues) {
List itemSlots = null;
List childSlots = null;
NodeKind nodeKind = null;
Verify.verify(!keyValues.isEmpty());
// one key/value pair corresponds to one slot
for (final KeyValue keyValue : keyValues) {
final Tuple keyTuple = getSubspace().unpack(keyValue.getKey()).popFront();
final Tuple valueTuple = Tuple.fromBytes(keyValue.getValue());
final NodeKind currentNodeKind = NodeKind.fromSerializedNodeKind((byte)keyTuple.getLong(0));
if (nodeKind == null) {
nodeKind = currentNodeKind;
} else if (nodeKind != currentNodeKind) {
throw new IllegalArgumentException("same node id uses different node kinds");
}
final Tuple slotKeyTuple = keyTuple.popFront();
if (nodeKind == NodeKind.LEAF) {
if (itemSlots == null) {
itemSlots = Lists.newArrayList();
}
itemSlots.add(ItemSlot.fromKeyAndValue(slotKeyTuple, valueTuple, getHilbertValueFunction()));
} else {
Verify.verify(nodeKind == NodeKind.INTERMEDIATE);
if (childSlots == null) {
childSlots = Lists.newArrayList();
}
childSlots.add(ChildSlot.fromKeyAndValue(slotKeyTuple, valueTuple));
}
}
Verify.verify((nodeKind == NodeKind.LEAF && itemSlots != null && childSlots == null) ||
(nodeKind == NodeKind.INTERMEDIATE && itemSlots == null && childSlots != null));
if (nodeKind == NodeKind.LEAF &&
!getConfig().isStoreHilbertValues()) {
//
// We need to sort the slots by the computed Hilbert value/key. This is not necessary when we store
// the Hilbert value as fdb does the sorting for us.
//
itemSlots.sort(ItemSlot.comparator);
}
return nodeKind == NodeKind.LEAF
? new LeafNode(nodeId, itemSlots)
: new IntermediateNode(nodeId, childSlots);
}
@Nonnull
@Override
public > AbstractChangeSet
newInsertChangeSet(@Nonnull final N node, final int level, @Nonnull final List insertedSlots) {
return new InsertChangeSet<>(node, level, insertedSlots);
}
@Nonnull
@Override
public > AbstractChangeSet
newUpdateChangeSet(@Nonnull final N node, final int level, @Nonnull final S originalSlot, @Nonnull final S updatedSlot) {
return new UpdateChangeSet<>(node, level, originalSlot, updatedSlot);
}
@Nonnull
@Override
public > AbstractChangeSet
newDeleteChangeSet(@Nonnull final N node, final int level, @Nonnull final List deletedSlots) {
return new DeleteChangeSet<>(node, level, deletedSlots);
}
private class InsertChangeSet> extends AbstractChangeSet {
@Nonnull
private final List insertedSlots;
public InsertChangeSet(@Nonnull final N node, final int level, @Nonnull final List insertedSlots) {
super(node.getChangeSet(), node, level);
this.insertedSlots = ImmutableList.copyOf(insertedSlots);
}
@Override
public void apply(@Nonnull final Transaction transaction) {
super.apply(transaction);
for (final S insertedSlot : insertedSlots) {
writeNodeSlot(transaction, getNode(), insertedSlot);
if (isUpdateNodeSlotIndex()) {
insertIntoNodeIndexIfNecessary(transaction, getLevel(), insertedSlot);
}
}
}
}
private class UpdateChangeSet> extends AbstractChangeSet {
@Nonnull
private final S originalSlot;
@Nonnull
private final S updatedSlot;
public UpdateChangeSet(@Nonnull final N node, final int level, @Nonnull final S originalSlot,
@Nonnull final S updatedSlot) {
super(node.getChangeSet(), node, level);
this.originalSlot = originalSlot;
this.updatedSlot = updatedSlot;
}
@Override
public void apply(@Nonnull final Transaction transaction) {
super.apply(transaction);
clearNodeSlot(transaction, getNode(), originalSlot);
writeNodeSlot(transaction, getNode(), updatedSlot);
if (isUpdateNodeSlotIndex()) {
deleteFromNodeIndexIfNecessary(transaction, getLevel(), originalSlot);
insertIntoNodeIndexIfNecessary(transaction, getLevel(), updatedSlot);
}
}
}
private class DeleteChangeSet> extends AbstractChangeSet {
@Nonnull
private final List deletedSlots;
public DeleteChangeSet(@Nonnull final N node, final int level, @Nonnull final List deletedSlots) {
super(node.getChangeSet(), node, level);
this.deletedSlots = ImmutableList.copyOf(deletedSlots);
}
@Override
public void apply(@Nonnull final Transaction transaction) {
super.apply(transaction);
for (final S deletedSlot : deletedSlots) {
clearNodeSlot(transaction, getNode(), deletedSlot);
if (isUpdateNodeSlotIndex()) {
deleteFromNodeIndexIfNecessary(transaction, getLevel(), deletedSlot);
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy