Skip to content

Commit 1a58550

Browse files
committed
Respond to PR #3566 comments from @ohadzeliger
Predominately: - DataInKeySpace now errors if it gets a null value - Improved javadoc linking KeySpace.resolveFromKeyAsync and KeySpacePath.toResolvedPathAsync - Fix error message in default method - Reduce visibility of ResolvedKeySPacePath.withRemainder - new negative tests in DataInKeySpacePathTest - Better coverage of exporting with mixed-type sub-directories
1 parent 5595a93 commit 1a58550

File tree

6 files changed

+97
-34
lines changed

6 files changed

+97
-34
lines changed

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/DataInKeySpacePath.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
package com.apple.foundationdb.record.provider.foundationdb.keyspace;
2222

2323
import com.apple.foundationdb.KeyValue;
24+
import com.apple.foundationdb.record.RecordCoreArgumentException;
25+
import com.apple.foundationdb.record.logging.LogMessageKeys;
2426
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext;
27+
import com.apple.foundationdb.tuple.ByteArrayUtil2;
2528

2629
import javax.annotation.Nonnull;
2730
import java.util.concurrent.CompletableFuture;
@@ -39,6 +42,10 @@ public class DataInKeySpacePath {
3942
public DataInKeySpacePath(KeySpacePath path, KeyValue rawKeyValue, FDBRecordContext context) {
4043
this.resolvedPath = path.toResolvedPathAsync(context, rawKeyValue.getKey());
4144
this.value = rawKeyValue.getValue();
45+
if (this.value == null) {
46+
throw new RecordCoreArgumentException("Value cannot be null")
47+
.addLogInfo(LogMessageKeys.KEY, ByteArrayUtil2.loggable(rawKeyValue.getKey()));
48+
}
4249
}
4350

4451
public CompletableFuture<ResolvedKeySpacePath> getResolvedPath() {

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpace.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,12 @@ public KeySpacePath pathFromKey(@Nonnull FDBRecordContext context, @Nonnull Tupl
227227
/**
228228
* Given a tuple from an FDB key, attempts to determine what path through this directory the tuple
229229
* represents, returning a <code>ResolvedKeySpacePath</code> representing the leaf-most directory in the path.
230-
* If entries remained in the tuple beyond the leaf directory, then {@link KeySpacePath#getRemainder()}
231-
* can be used to fetch the remaining portion.
232-
*
230+
* <p>
231+
* If entries remained in the tuple beyond the leaf directory, then {@link KeySpacePath#getRemainder()} can be
232+
* used to fetch the remaining portion.
233+
* See also {@link KeySpacePath#toResolvedPathAsync(FDBRecordContext, byte[])} if you need to resolve and you
234+
* know that it is part of a given path.
235+
* </p>
233236
* @param context context used, if needed, for any database operations
234237
* @param key the tuple to be decoded
235238
* @return a path entry representing the leaf directory entry that corresponds to a value in the

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePath.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,15 +199,22 @@ default Tuple toTuple(@Nonnull FDBRecordContext context) {
199199
CompletableFuture<ResolvedKeySpacePath> toResolvedPathAsync(@Nonnull FDBRecordContext context);
200200

201201
/**
202-
* Resolves the given key within this path.
203-
* @param context the transaction to lookup any necessary directory layer entries.
202+
* Given a tuple from an FDB key, attempts to determine what sub-path through this directory the tuple
203+
* represents, returning a <code>ResolvedKeySpacePath</code> representing the leaf-most directory in the path.
204+
* <p>
205+
* If entries remained in the tuple beyond the leaf directory, then {@link KeySpacePath#getRemainder()}
206+
* can be used to fetch the remaining portion.
207+
* See also {@link KeySpace#resolveFromKeyAsync(FDBRecordContext, Tuple)} if you need to resolve from the root.
208+
* </p>
209+
* @param context context used, if needed, for any database operations
204210
* @param key a raw key from the database
205211
* @return the {@link ResolvedKeySpacePath} corresponding to that key, with a potential remainder.
212+
* @throws com.apple.foundationdb.record.RecordCoreArgumentException if the key provided is not part of this path
206213
*/
207214
@API(API.Status.EXPERIMENTAL)
208215
@Nonnull
209216
default CompletableFuture<ResolvedKeySpacePath> toResolvedPathAsync(@Nonnull FDBRecordContext context, byte[] key) {
210-
throw new UnsupportedOperationException("toResolvedPath is not supported");
217+
throw new UnsupportedOperationException("toResolvedPathAsync is not supported");
211218
}
212219

213220
/**

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/ResolvedKeySpacePath.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.apple.foundationdb.subspace.Subspace;
2525
import com.apple.foundationdb.tuple.ByteArrayUtil2;
2626
import com.apple.foundationdb.tuple.Tuple;
27+
import com.google.common.annotations.VisibleForTesting;
2728

2829
import javax.annotation.Nonnull;
2930
import javax.annotation.Nullable;
@@ -278,7 +279,8 @@ public static void appendValue(StringBuilder sb, Object value) {
278279
* @return a new {@code ResolvedKeySpacePath} that is the same as this, except with a different {@link #getRemainder()}.
279280
*/
280281
@Nonnull
281-
public ResolvedKeySpacePath withRemainder(@Nullable final Tuple newRemainder) {
282+
@VisibleForTesting
283+
ResolvedKeySpacePath withRemainder(@Nullable final Tuple newRemainder) {
282284
// this could probably copy the cachedTuple & cachedSubspace
283285
return new ResolvedKeySpacePath(parent, inner, value, newRemainder);
284286
}

fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/DataInKeySpacePathTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import com.apple.foundationdb.KeyValue;
2424
import com.apple.foundationdb.Transaction;
25+
import com.apple.foundationdb.record.RecordCoreArgumentException;
2526
import com.apple.foundationdb.record.provider.foundationdb.FDBDatabase;
2627
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext;
2728
import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpaceDirectory.KeyType;
@@ -39,13 +40,16 @@
3940
import java.util.List;
4041
import java.util.UUID;
4142
import java.util.concurrent.CompletableFuture;
43+
import java.util.concurrent.CompletionException;
4244
import java.util.concurrent.ExecutionException;
4345
import java.util.function.Function;
4446

4547
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4648
import static org.junit.jupiter.api.Assertions.assertEquals;
49+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
4750
import static org.junit.jupiter.api.Assertions.assertNotNull;
4851
import static org.junit.jupiter.api.Assertions.assertNull;
52+
import static org.junit.jupiter.api.Assertions.assertThrows;
4953
import static org.junit.jupiter.api.Assertions.assertTrue;
5054

5155
/**
@@ -328,6 +332,50 @@ void withWrapper() {
328332
}
329333
}
330334

335+
@Test
336+
void badPath() {
337+
final String rootUuid = UUID.randomUUID().toString();
338+
KeySpace root = new KeySpace(
339+
new KeySpaceDirectory("test", KeyType.STRING, rootUuid));
340+
341+
final FDBDatabase database = dbExtension.getDatabase();
342+
343+
try (FDBRecordContext context = database.openContext()) {
344+
KeySpacePath testPath = root.path("test");
345+
byte[] keyBytes = Tuple.from("banana", rootUuid).pack();
346+
byte[] valueBytes = Tuple.from("accessor_test").pack();
347+
348+
KeyValue originalKeyValue = new KeyValue(keyBytes, valueBytes);
349+
DataInKeySpacePath dataInPath = new DataInKeySpacePath(testPath, originalKeyValue, context);
350+
final CompletionException completionException = assertThrows(CompletionException.class,
351+
() -> dataInPath.getResolvedPath().join());
352+
assertInstanceOf(RecordCoreArgumentException.class, completionException.getCause());
353+
}
354+
}
355+
356+
/**
357+
* Test if there is a null value. FDB shouldn't ever return this, and if you got it somehow, you wouldn't be
358+
* able to insert it back into a database because {@link com.apple.foundationdb.FDBTransaction#set(byte[], byte[])}
359+
* does not support a {@code null} key or value.
360+
*/
361+
@Test
362+
void nullValue() {
363+
KeySpace root = new KeySpace(
364+
new KeySpaceDirectory("test", KeyType.STRING, UUID.randomUUID().toString()));
365+
366+
final FDBDatabase database = dbExtension.getDatabase();
367+
368+
try (FDBRecordContext context = database.openContext()) {
369+
KeySpacePath testPath = root.path("test");
370+
byte[] keyBytes = testPath.toTuple(context).pack();
371+
byte[] valueBytes = null;
372+
373+
KeyValue originalKeyValue = new KeyValue(keyBytes, valueBytes);
374+
assertThrows(RecordCoreArgumentException.class,
375+
() -> new DataInKeySpacePath(testPath, originalKeyValue, context));
376+
}
377+
}
378+
331379
private static ResolvedKeySpacePath assertNameAndDirectoryScopedValue(ResolvedKeySpacePath resolved,
332380
String name, Object logicalValue,
333381
KeySpacePath path, FDBRecordContext context) {

fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathDataExportTest.java

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import java.util.Collections;
4747
import java.util.HashSet;
4848
import java.util.List;
49+
import java.util.Map;
4950
import java.util.Set;
5051
import java.util.UUID;
5152
import java.util.concurrent.ExecutionException;
@@ -212,35 +213,30 @@ void exportAllDataWithDirectoryLayer() {
212213

213214
@Test
214215
void exportAllDataWithDifferentKeyTypes() {
215-
KeySpace root = new KeySpace(
216-
new KeySpaceDirectory("mixed", KeyType.STRING, UUID.randomUUID().toString())
217-
.addSubdirectory(new KeySpaceDirectory("strings", KeyType.STRING))
218-
.addSubdirectory(new KeySpaceDirectory("longs", KeyType.LONG))
219-
.addSubdirectory(new KeySpaceDirectory("bytes", KeyType.BYTES))
220-
.addSubdirectory(new KeySpaceDirectory("uuids", KeyType.UUID))
221-
.addSubdirectory(new KeySpaceDirectory("booleans", KeyType.BOOLEAN)));
216+
final KeySpaceDirectory rootDirectory = new KeySpaceDirectory("mixed", KeyType.STRING, UUID.randomUUID().toString());
217+
Map<KeyType, List<Object>> dataByType = Map.of(
218+
KeyType.STRING, List.of("str0", "str1", "str2"),
219+
KeyType.LONG, List.of(10L, 11L, 12L),
220+
KeyType.BYTES, List.of(new byte[]{0, 1}, new byte[]{1, 2}),
221+
KeyType.UUID, List.of(new UUID(0, 0), new UUID(1, 1)),
222+
KeyType.BOOLEAN, List.of(true, false),
223+
KeyType.NULL, Collections.singletonList(null),
224+
KeyType.DOUBLE, List.of(3.1415, -2.718281, 13.23E8),
225+
KeyType.FLOAT, List.of(1.4142135f, -5.8f, 130.23f)
226+
);
227+
dataByType.keySet().forEach(keyType ->
228+
rootDirectory.addSubdirectory(new KeySpaceDirectory(keyType.name(), keyType)));
229+
KeySpace root = new KeySpace(rootDirectory);
230+
assertEquals(Set.of(KeyType.values()), dataByType.keySet());
222231

223232
final FDBDatabase database = dbExtension.getDatabase();
224233

225234
// Store test data with different key types
226235
try (FDBRecordContext context = database.openContext()) {
227236
KeySpacePath basePath = root.path("mixed");
228-
229-
// String keys (str0, str1, str2 -> string_value_0, string_value_1, string_value_2)
230-
setData(List.of("str0", "str1", "str2"), context, basePath, "strings", "string_value_");
231-
232-
// Long keys (10, 11, 12 -> long_value_10, long_value_11, long_value_12)
233-
setData(List.of(10L, 11L, 12L), context, basePath, "longs", "long_value_");
234-
235-
// Bytes keys (arrays -> bytes_value_[0, 1], bytes_value_[1, 2])
236-
setData(List.of(new byte[]{0, 1}, new byte[]{1, 2}), context, basePath, "bytes", "bytes_value_");
237-
238-
// UUID keys (UUIDs -> uuid_value_UUID)
239-
setData(List.of(new UUID(0, 0), new UUID(1, 1)), context, basePath, "uuids", "uuid_value_");
240-
241-
// Boolean keys (true, false -> boolean_value_true, boolean_value_false)
242-
setData(List.of(true, false), context, basePath, "booleans", "boolean_value_");
243-
237+
dataByType.forEach((keyType, data) ->
238+
setData(data, context, basePath, keyType.name(), keyType + "_value_")
239+
);
244240
context.commit();
245241
}
246242

@@ -249,15 +245,15 @@ void exportAllDataWithDifferentKeyTypes() {
249245
KeySpacePath mixedPath = root.path("mixed");
250246
final List<DataInKeySpacePath> allData = exportAllData(mixedPath, context);
251247

252-
// Should have 12 records total (3+3+2+2+2)
253-
assertEquals(12, allData.size());
248+
assertEquals(dataByType.values().stream().mapToLong(List::size).sum(),
249+
allData.size());
254250

255251
// Verify we have different value types
256252
Set<String> valueTypes = allData.stream()
257253
.map(data -> Tuple.fromBytes(data.getValue()).getString(0).split("_")[0])
258254
.collect(Collectors.toSet());
259-
assertEquals(5, valueTypes.size());
260-
assertTrue(valueTypes.containsAll(Arrays.asList("string", "long", "bytes", "uuid", "boolean")));
255+
assertEquals((Arrays.stream(KeyType.values()).map(Enum::name).collect(Collectors.toSet())),
256+
valueTypes);
261257
}
262258
}
263259

0 commit comments

Comments
 (0)