Skip to content

Commit cc2420e

Browse files
committed
Fix bug when exporting at the leaf node
1 parent d9b92ab commit cc2420e

File tree

4 files changed

+149
-90
lines changed

4 files changed

+149
-90
lines changed

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.apple.foundationdb.KeyValue;
2424
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext;
2525
import com.apple.foundationdb.tuple.Tuple;
26+
import com.apple.foundationdb.tuple.TupleHelpers;
2627

2728
import java.util.concurrent.CompletableFuture;
2829

@@ -50,7 +51,14 @@ public DataInKeySpacePath(KeySpacePath path, KeyValue rawKeyValue, FDBRecordCont
5051
// The remaining part of the key should be resolved from the resolved path's directory
5152
if (keyTuple.size() > pathLength) {
5253
// There's more in the key than just the path, so resolve the rest
53-
return resolvedPath.getDirectory().findChildForKey(context, resolvedPath, keyTuple, keyTuple.size(), pathLength);
54+
if (resolvedPath.getDirectory().getSubdirectories().isEmpty()) {
55+
return CompletableFuture.completedFuture(
56+
new ResolvedKeySpacePath(resolvedPath.getParent(), resolvedPath.toPath(),
57+
resolvedPath.getResolvedPathValue(),
58+
TupleHelpers.subTuple(keyTuple, pathTuple.size(), keyTuple.size())));
59+
} else {
60+
return resolvedPath.getDirectory().findChildForKey(context, resolvedPath, keyTuple, keyTuple.size(), pathLength);
61+
}
5462
} else {
5563
// The key exactly matches the path
5664
return CompletableFuture.completedFuture(resolvedPath);

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

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.junit.jupiter.params.ParameterizedTest;
3737
import org.junit.jupiter.params.provider.ValueSource;
3838

39+
import java.util.List;
3940
import java.util.UUID;
4041
import java.util.concurrent.CompletableFuture;
4142

@@ -53,19 +54,23 @@ class DataInKeySpacePathTest {
5354
@RegisterExtension
5455
final FDBDatabaseExtension dbExtension = new FDBDatabaseExtension();
5556

56-
@Test
57-
void testSimpleTwoLevelPath() {
57+
@ParameterizedTest
58+
@ValueSource(ints = {0, 1, 2})
59+
void testSimpleTwoLevelPath(int depth) {
5860
KeySpace root = new KeySpace(
5961
new KeySpaceDirectory("app", KeyType.STRING, UUID.randomUUID().toString())
60-
.addSubdirectory(new KeySpaceDirectory("user", KeyType.LONG)));
62+
.addSubdirectory(new KeySpaceDirectory("locality", KeyType.STRING, "Foo")
63+
.addSubdirectory(new KeySpaceDirectory("user", KeyType.LONG))));
6164

6265
final FDBDatabase database = dbExtension.getDatabase();
6366

6467
// Store test data and create DataInKeySpacePath
6568
try (FDBRecordContext context = database.openContext()) {
6669
Transaction tr = context.ensureActive();
67-
68-
KeySpacePath userPath = root.path("app").add("user", 123L);
70+
71+
KeySpacePath appPath = root.path("app");
72+
KeySpacePath localityPath = appPath.add("locality");
73+
KeySpacePath userPath = localityPath.add("user", 123L);
6974
final Subspace pathSubspace = userPath.toSubspace(context);
7075

7176
// Add additional tuple elements after the KeySpacePath (this is how data is actually stored)
@@ -74,10 +79,10 @@ void testSimpleTwoLevelPath() {
7479

7580
tr.set(keyBytes, valueBytes);
7681
KeyValue keyValue = new KeyValue(keyBytes, valueBytes);
77-
78-
// Create DataInKeySpacePath from the app-level path
79-
KeySpacePath appPath = root.path("app");
80-
DataInKeySpacePath dataInPath = new DataInKeySpacePath(appPath, keyValue, context);
82+
83+
final List<KeySpacePath> queryPaths = List.of(appPath, localityPath, userPath);
84+
DataInKeySpacePath dataInPath = new DataInKeySpacePath(
85+
queryPaths.get(depth), keyValue, context);
8186

8287
// Verify the resolved path
8388
CompletableFuture<ResolvedKeySpacePath> resolvedFuture = dataInPath.getResolvedPath();
@@ -93,11 +98,14 @@ void testSimpleTwoLevelPath() {
9398
// Verify parent path
9499
ResolvedKeySpacePath parent = resolved.getParent();
95100
assertNotNull(parent);
96-
assertEquals("app", parent.getDirectoryName());
97-
101+
assertEquals("locality", parent.getDirectoryName());
102+
ResolvedKeySpacePath grandParent = parent.getParent();
103+
assertNotNull(grandParent);
104+
assertEquals("app", grandParent.getDirectoryName());
105+
98106
// Verify the resolved path recreates the KeySpacePath portion (not the full key)
99107
Tuple resolvedTuple = resolved.toTuple();
100-
assertEquals(TupleHelpers.subTuple(Tuple.fromBytes(keyBytes), 0, 2), resolvedTuple);
108+
assertEquals(TupleHelpers.subTuple(Tuple.fromBytes(keyBytes), 0, 3), resolvedTuple);
101109

102110
// Verify that the remainder contains the additional tuple elements
103111
Tuple remainder = resolved.getRemainder();
@@ -546,4 +554,28 @@ void testNullKeyTypeDirectory() {
546554
context.commit();
547555
}
548556
}
557+
558+
@Test
559+
void testWithWrapper() {
560+
final FDBDatabase database = dbExtension.getDatabase();
561+
final EnvironmentKeySpace keySpace = EnvironmentKeySpace.setupSampleData(database);
562+
563+
// Test export at different levels through wrapper methods
564+
try (FDBRecordContext context = database.openContext()) {
565+
// Test 4: Export from specific data store level
566+
EnvironmentKeySpace.DataPath dataStore = keySpace.root().userid(100L).application("app1").dataStore();
567+
568+
final byte[] key = dataStore.toTuple(context).add("record2").add(0).pack();
569+
final byte[] value = Tuple.from("data").pack();
570+
final DataInKeySpacePath dataInKeySpacePath = new DataInKeySpacePath(dataStore, new KeyValue(key, value), context);
571+
572+
final ResolvedKeySpacePath resolvedPath = dataInKeySpacePath.getResolvedPath().join();
573+
assertEquals(dataStore.toResolvedPath(context), withoutRemainder(resolvedPath));
574+
assertEquals(Tuple.from("record2", 0), resolvedPath.getRemainder());
575+
}
576+
}
577+
578+
private ResolvedKeySpacePath withoutRemainder(final ResolvedKeySpacePath path) {
579+
return new ResolvedKeySpacePath(path.getParent(), path.toPath(), path.getResolvedPathValue(), null);
580+
}
549581
}

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@
2020

2121
package com.apple.foundationdb.record.provider.foundationdb.keyspace;
2222

23+
import com.apple.foundationdb.Transaction;
24+
import com.apple.foundationdb.record.provider.foundationdb.FDBDatabase;
2325
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext;
2426
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore;
2527
import com.apple.foundationdb.tuple.Tuple;
2628

29+
import javax.annotation.Nonnull;
30+
import java.util.UUID;
31+
2732
/**
2833
* This provides an example of a way in which you can define a KeySpace in a relatively clean and type-safe
2934
* manner. It defines a keyspace that looks like:
@@ -69,6 +74,37 @@ public EnvironmentKeySpace(String rootName) {
6974
.addSubdirectory(new KeySpaceDirectory(METADATA_KEY, KeySpaceDirectory.KeyType.LONG, METADATA_VALUE, MetadataPath::new)))));
7075
}
7176

77+
@Nonnull
78+
static EnvironmentKeySpace setupSampleData(@Nonnull final FDBDatabase database) {
79+
EnvironmentKeySpace keySpace = new EnvironmentKeySpace(UUID.randomUUID().toString());
80+
81+
// Store test data at different levels of the hierarchy
82+
try (FDBRecordContext context = database.openContext()) {
83+
Transaction tr = context.ensureActive();
84+
85+
// Create paths for different users and applications
86+
ApplicationPath app1User1 = keySpace.root().userid(100L).application("app1");
87+
ApplicationPath app2User1 = keySpace.root().userid(100L).application("app2");
88+
ApplicationPath app1User2 = keySpace.root().userid(200L).application("app1");
89+
90+
DataPath dataUser1App1 = app1User1.dataStore();
91+
MetadataPath metaUser1App1 = app1User1.metadataStore();
92+
DataPath dataUser1App2 = app2User1.dataStore();
93+
DataPath dataUser2App1 = app1User2.dataStore();
94+
95+
// Store data records with additional tuple elements after the KeySpacePath
96+
tr.set(dataUser1App1.toTuple(context).add("record1").pack(), Tuple.from("user100_app1_data1").pack());
97+
tr.set(dataUser1App1.toTuple(context).add("record2").add(0).pack(), Tuple.from("user100_app1_data2_0").pack());
98+
tr.set(dataUser1App1.toTuple(context).add("record2").add(1).pack(), Tuple.from("user100_app1_data2_1").pack());
99+
tr.set(metaUser1App1.toTuple(context).add("config1").pack(), Tuple.from("user100_app1_meta1").pack());
100+
tr.set(dataUser1App2.toTuple(context).add("record3").pack(), Tuple.from("user100_app2_data3").pack());
101+
tr.set(dataUser2App1.toTuple(context).add("record4").pack(), Tuple.from("user200_app1_data4").pack());
102+
103+
context.commit();
104+
}
105+
return keySpace;
106+
}
107+
72108
public String getRootName() {
73109
return rootName;
74110
}

0 commit comments

Comments
 (0)