Skip to content

Commit 8a3c53c

Browse files
committed
more javadoc and tests
1 parent 09a37ad commit 8a3c53c

File tree

14 files changed

+959
-234
lines changed

14 files changed

+959
-234
lines changed

fdb-extensions/src/main/java/com/apple/foundationdb/async/hnsw/AbstractNode.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,14 @@
2727
import java.util.List;
2828

2929
/**
30-
* TODO.
31-
* @param <N> node type class.
30+
* An abstract base class implementing the {@link Node} interface.
31+
* <p>
32+
* This class provides the fundamental structure for a node within the HNSW graph,
33+
* managing a unique {@link Tuple} primary key and an immutable list of its neighbors.
34+
* Subclasses are expected to provide concrete implementations, potentially adding
35+
* more state or behavior.
36+
*
37+
* @param <N> the type of the node reference used for neighbors, which must extend {@link NodeReference}
3238
*/
3339
abstract class AbstractNode<N extends NodeReference> implements Node<N> {
3440
@Nonnull
@@ -37,24 +43,53 @@ abstract class AbstractNode<N extends NodeReference> implements Node<N> {
3743
@Nonnull
3844
private final List<N> neighbors;
3945

46+
/**
47+
* Constructs a new {@code AbstractNode} with a specified primary key and a list of neighbors.
48+
* <p>
49+
* This constructor creates a defensive, immutable copy of the provided {@code neighbors} list.
50+
* This ensures that the internal state of the node cannot be modified by external
51+
* changes to the original list after construction.
52+
*
53+
* @param primaryKey the unique identifier for this node; must not be {@code null}
54+
* @param neighbors the list of nodes connected to this node; must not be {@code null}
55+
*/
4056
protected AbstractNode(@Nonnull final Tuple primaryKey,
4157
@Nonnull final List<N> neighbors) {
4258
this.primaryKey = primaryKey;
4359
this.neighbors = ImmutableList.copyOf(neighbors);
4460
}
4561

62+
/**
63+
* Gets the primary key that uniquely identifies this object.
64+
* @return the primary key {@link Tuple}, which will never be {@code null}.
65+
*/
4666
@Nonnull
4767
@Override
4868
public Tuple getPrimaryKey() {
4969
return primaryKey;
5070
}
5171

72+
/**
73+
* Gets the list of neighbors connected to this node.
74+
* <p>
75+
* This method returns a direct reference to the internal list which is
76+
* immutable.
77+
* @return a non-null, possibly empty, list of neighbors.
78+
*/
5279
@Nonnull
5380
@Override
5481
public List<N> getNeighbors() {
5582
return neighbors;
5683
}
5784

85+
/**
86+
* Gets the neighbor at the specified index.
87+
* <p>
88+
* This method provides access to a specific neighbor by its zero-based position
89+
* in the internal list of neighbors.
90+
* @param index the zero-based index of the neighbor to retrieve.
91+
* @return the neighbor at the specified index, guaranteed to be non-null.
92+
*/
5893
@Nonnull
5994
@Override
6095
public N getNeighbor(final int index) {

fdb-extensions/src/main/java/com/apple/foundationdb/async/hnsw/AbstractStorageAdapter.java

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,14 @@
3232
import java.util.concurrent.CompletableFuture;
3333

3434
/**
35-
* Implementations and attributes common to all concrete implementations of {@link StorageAdapter}.
35+
* An abstract base class for {@link StorageAdapter} implementations.
36+
* <p>
37+
* This class provides the common infrastructure for managing HNSW graph data within {@link Subspace}.
38+
* It handles the configuration, node creation, and listener management, while delegating the actual
39+
* storage-specific read and write operations to concrete subclasses through the {@code fetchNodeInternal}
40+
* and {@code writeNodeInternal} abstract methods.
41+
*
42+
* @param <N> the type of {@link NodeReference} used to reference nodes in the graph
3643
*/
3744
abstract class AbstractStorageAdapter<N extends NodeReference> implements StorageAdapter<N> {
3845
@Nonnull
@@ -51,6 +58,19 @@ abstract class AbstractStorageAdapter<N extends NodeReference> implements Storag
5158

5259
private final Subspace dataSubspace;
5360

61+
/**
62+
* Constructs a new {@code AbstractStorageAdapter}.
63+
* <p>
64+
* This constructor initializes the adapter with the necessary configuration,
65+
* factories, and listeners for managing an HNSW graph. It also sets up a
66+
* dedicated data subspace within the provided main subspace for storing node data.
67+
*
68+
* @param config the HNSW graph configuration
69+
* @param nodeFactory the factory to create new nodes of type {@code <N>}
70+
* @param subspace the primary subspace for storing all graph-related data
71+
* @param onWriteListener the listener to be called on write operations
72+
* @param onReadListener the listener to be called on read operations
73+
*/
5474
protected AbstractStorageAdapter(@Nonnull final HNSW.Config config, @Nonnull final NodeFactory<N> nodeFactory,
5575
@Nonnull final Subspace subspace,
5676
@Nonnull final OnWriteListener onWriteListener,
@@ -63,55 +83,138 @@ protected AbstractStorageAdapter(@Nonnull final HNSW.Config config, @Nonnull fin
6383
this.dataSubspace = subspace.subspace(Tuple.from(SUBSPACE_PREFIX_DATA));
6484
}
6585

86+
/**
87+
* Returns the configuration used to build and search this HNSW graph.
88+
*
89+
* @return the current {@link HNSW.Config} object, never {@code null}.
90+
*/
6691
@Override
6792
@Nonnull
6893
public HNSW.Config getConfig() {
6994
return config;
7095
}
7196

97+
/**
98+
* Gets the factory responsible for creating new nodes.
99+
* <p>
100+
* This factory is used to instantiate nodes of the generic type {@code N}
101+
* for the current context. The {@code @Nonnull} annotation guarantees that
102+
* this method will never return {@code null}.
103+
*
104+
* @return the non-null {@link NodeFactory} instance.
105+
*/
72106
@Nonnull
73107
@Override
74108
public NodeFactory<N> getNodeFactory() {
75109
return nodeFactory;
76110
}
77111

112+
/**
113+
* Gets the kind of this node, which uniquely identifies the type of node.
114+
* <p>
115+
* This method is an override and provides a way to determine the concrete
116+
* type of node without using {@code instanceof} checks.
117+
*
118+
* @return the non-null {@link NodeKind} representing the type of this node.
119+
*/
78120
@Nonnull
79121
@Override
80122
public NodeKind getNodeKind() {
81123
return getNodeFactory().getNodeKind();
82124
}
83125

126+
/**
127+
* Gets the subspace in which this key or value is stored.
128+
* <p>
129+
* This subspace provides a logical separation for keys within the underlying key-value store.
130+
*
131+
* @return the non-null {@link Subspace} for this context
132+
*/
84133
@Override
85134
@Nonnull
86135
public Subspace getSubspace() {
87136
return subspace;
88137
}
89138

139+
/**
140+
* Gets the subspace for the data associated with this component.
141+
* <p>
142+
* The data subspace defines the portion of the directory space where the data
143+
* for this component is stored.
144+
*
145+
* @return the non-null {@link Subspace} for the data
146+
*/
90147
@Override
91148
@Nonnull
92149
public Subspace getDataSubspace() {
93150
return dataSubspace;
94151
}
95152

153+
/**
154+
* Returns the listener that is notified upon write events.
155+
* <p>
156+
* This method is an override and guarantees a non-null return value,
157+
* as indicated by the {@code @Nonnull} annotation.
158+
*
159+
* @return the configured {@link OnWriteListener} instance; will never be {@code null}.
160+
*/
96161
@Override
97162
@Nonnull
98163
public OnWriteListener getOnWriteListener() {
99164
return onWriteListener;
100165
}
101166

167+
/**
168+
* Gets the listener that is notified upon completion of a read operation.
169+
* <p>
170+
* This method is an override and provides the currently configured listener instance.
171+
* The returned listener is guaranteed to be non-null as indicated by the
172+
* {@code @Nonnull} annotation.
173+
*
174+
* @return the non-null {@link OnReadListener} instance.
175+
*/
102176
@Override
103177
@Nonnull
104178
public OnReadListener getOnReadListener() {
105179
return onReadListener;
106180
}
107181

182+
/**
183+
* Asynchronously fetches a node from a specific layer of the HNSW.
184+
* <p>
185+
* The node is identified by its {@code layer} and {@code primaryKey}. The entire fetch operation is
186+
* performed within the given {@link ReadTransaction}. After the underlying
187+
* fetch operation completes, the retrieved node is validated by the
188+
* {@link #checkNode(Node)} method before the returned future is completed.
189+
*
190+
* @param readTransaction the non-null transaction to use for the read operation
191+
* @param layer the layer of the tree from which to fetch the node
192+
* @param primaryKey the non-null primary key that identifies the node to fetch
193+
*
194+
* @return a {@link CompletableFuture} that will complete with the fetched {@link Node}
195+
* once it has been read from storage and validated
196+
*/
108197
@Nonnull
109198
@Override
110199
public CompletableFuture<Node<N>> fetchNode(@Nonnull final ReadTransaction readTransaction,
111200
int layer, @Nonnull Tuple primaryKey) {
112201
return fetchNodeInternal(readTransaction, layer, primaryKey).thenApply(this::checkNode);
113202
}
114203

204+
/**
205+
* Asynchronously fetches a specific node from the data store for a given layer and primary key.
206+
* <p>
207+
* This is an internal, abstract method that concrete subclasses must implement to define
208+
* the storage-specific logic for retrieving a node. The operation is performed within the
209+
* context of the provided {@link ReadTransaction}.
210+
*
211+
* @param readTransaction the transaction to use for the read operation; must not be {@code null}
212+
* @param layer the layer index from which to fetch the node
213+
* @param primaryKey the primary key that uniquely identifies the node to be fetched; must not be {@code null}
214+
*
215+
* @return a {@link CompletableFuture} that will be completed with the fetched {@link Node}.
216+
* The future will complete with {@code null} if no node is found for the given key and layer.
217+
*/
115218
@Nonnull
116219
protected abstract CompletableFuture<Node<N>> fetchNodeInternal(@Nonnull ReadTransaction readTransaction,
117220
int layer, @Nonnull Tuple primaryKey);
@@ -129,6 +232,21 @@ private Node<N> checkNode(@Nullable final Node<N> node) {
129232
return node;
130233
}
131234

235+
/**
236+
* Writes a given node and its neighbor modifications to the underlying storage.
237+
* <p>
238+
* This operation is executed within the context of the provided {@link Transaction}.
239+
* It handles persisting the node's data at a specific {@code layer} and applies
240+
* the changes to its neighbors as defined in the {@link NeighborsChangeSet}.
241+
* This method delegates the core writing logic to an internal method and provides
242+
* debug logging upon completion.
243+
*
244+
* @param transaction the non-null {@link Transaction} context for this write operation
245+
* @param node the non-null {@link Node} to be written to storage
246+
* @param layer the layer index where the node is being written
247+
* @param changeSet the non-null {@link NeighborsChangeSet} detailing the modifications
248+
* to the node's neighbors
249+
*/
132250
@Override
133251
public void writeNode(@Nonnull Transaction transaction, @Nonnull Node<N> node, int layer,
134252
@Nonnull NeighborsChangeSet<N> changeSet) {
@@ -138,6 +256,20 @@ public void writeNode(@Nonnull Transaction transaction, @Nonnull Node<N> node, i
138256
}
139257
}
140258

259+
/**
260+
* Writes a single node to the data store as part of a larger transaction.
261+
* <p>
262+
* This is an abstract method that concrete implementations must provide.
263+
* It is responsible for the low-level persistence of the given {@code node} at a
264+
* specific {@code layer}. The implementation should also handle the modifications
265+
* to the node's neighbors, as detailed in the {@code changeSet}.
266+
*
267+
* @param transaction the non-null transaction context for the write operation
268+
* @param node the non-null {@link Node} to write
269+
* @param layer the layer or level of the node in the structure
270+
* @param changeSet the non-null {@link NeighborsChangeSet} detailing additions or
271+
* removals of neighbor links
272+
*/
141273
protected abstract void writeNodeInternal(@Nonnull Transaction transaction, @Nonnull Node<N> node, int layer,
142274
@Nonnull NeighborsChangeSet<N> changeSet);
143275

fdb-extensions/src/main/java/com/apple/foundationdb/async/hnsw/BaseNeighborsChangeSet.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,62 @@
3030
import java.util.function.Predicate;
3131

3232
/**
33-
* TODO.
33+
* A base implementation of the {@link NeighborsChangeSet} interface.
34+
* <p>
35+
* This class represents a complete, non-delta state of a node's neighbors. It holds a fixed, immutable
36+
* list of neighbors provided at construction time. As such, it does not support parent change sets or writing deltas.
37+
*
38+
* @param <N> the type of the node reference, which must extend {@link NodeReference}
3439
*/
3540
class BaseNeighborsChangeSet<N extends NodeReference> implements NeighborsChangeSet<N> {
3641
@Nonnull
3742
private final List<N> neighbors;
3843

44+
/**
45+
* Creates a new change set with the specified neighbors.
46+
* <p>
47+
* This constructor creates an immutable copy of the provided list.
48+
*
49+
* @param neighbors the list of neighbors for this change set; must not be null.
50+
*/
3951
public BaseNeighborsChangeSet(@Nonnull final List<N> neighbors) {
4052
this.neighbors = ImmutableList.copyOf(neighbors);
4153
}
4254

55+
/**
56+
* Gets the parent change set.
57+
* <p>
58+
* This implementation always returns {@code null}, as this type of change set
59+
* does not have a parent.
60+
*
61+
* @return always {@code null}.
62+
*/
4363
@Nullable
4464
@Override
4565
public BaseNeighborsChangeSet<N> getParent() {
4666
return null;
4767
}
4868

69+
/**
70+
* Retrieves the list of neighbors associated with this object.
71+
* <p>
72+
* This implementation fulfills the {@code merge} contract by simply returning the
73+
* existing list of neighbors without performing any additional merging logic.
74+
* @return a non-null list of neighbors. The generic type {@code N} represents
75+
* the type of the neighboring elements.
76+
*/
4977
@Nonnull
5078
@Override
5179
public List<N> merge() {
5280
return neighbors;
5381
}
5482

83+
/**
84+
* {@inheritDoc}
85+
*
86+
* <p>This implementation is a no-op and does not write any delta information,
87+
* as indicated by the empty method body.
88+
*/
5589
@Override
5690
public void writeDelta(@Nonnull final InliningStorageAdapter storageAdapter, @Nonnull final Transaction transaction,
5791
final int layer, @Nonnull final Node<N> node,

0 commit comments

Comments
 (0)