diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/DataInKeySpacePath.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/DataInKeySpacePath.java index a51d52fcd6..f2d2284730 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/DataInKeySpacePath.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/DataInKeySpacePath.java @@ -24,11 +24,10 @@ import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.record.RecordCoreArgumentException; import com.apple.foundationdb.record.logging.LogMessageKeys; -import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext; -import com.apple.foundationdb.tuple.ByteArrayUtil2; +import com.apple.foundationdb.tuple.Tuple; import javax.annotation.Nonnull; -import java.util.concurrent.CompletableFuture; +import javax.annotation.Nullable; /** * Class representing a {@link KeyValue} pair within in {@link KeySpacePath}. @@ -37,24 +36,35 @@ public class DataInKeySpacePath { @Nonnull - private final CompletableFuture resolvedPath; + private final KeySpacePath path; + @Nullable + private final Tuple remainder; @Nonnull private final byte[] value; - public DataInKeySpacePath(KeySpacePath path, KeyValue rawKeyValue, FDBRecordContext context) { - this.resolvedPath = path.toResolvedPathAsync(context, rawKeyValue.getKey()); - this.value = rawKeyValue.getValue(); - if (this.value == null) { + public DataInKeySpacePath(@Nonnull final KeySpacePath path, @Nullable final Tuple remainder, + @Nullable final byte[] value) { + this.path = path; + this.remainder = remainder; + if (value == null) { throw new RecordCoreArgumentException("Value cannot be null") - .addLogInfo(LogMessageKeys.KEY, ByteArrayUtil2.loggable(rawKeyValue.getKey())); + .addLogInfo(LogMessageKeys.KEY, path); } - } - - public CompletableFuture getResolvedPath() { - return resolvedPath; + this.value = value; } public byte[] getValue() { return this.value; } + + @Nonnull + public KeySpacePath getPath() { + return path; + } + + @Nullable + public Tuple getRemainder() { + return remainder; + } + } diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpace.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpace.java index 0976ede974..e70eb612f3 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpace.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpace.java @@ -189,8 +189,6 @@ public KeySpacePath path(@Nonnull String name, @Nullable Object value) { *

* If entries remained in the tuple beyond the leaf directory, then {@link ResolvedKeySpacePath#getRemainder()} can be * used to fetch the remaining portion. - * See also {@link KeySpacePath#toResolvedPathAsync(FDBRecordContext, byte[])} if you need to resolve and you - * know that it is part of a given path. *

* @param context context used, if needed, for any database operations * @param key the tuple to be decoded diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePath.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePath.java index 98d8678ebb..ff228ef1be 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePath.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePath.java @@ -157,25 +157,6 @@ default Tuple toTuple(@Nonnull FDBRecordContext context) { @Nonnull CompletableFuture toResolvedPathAsync(@Nonnull FDBRecordContext context); - /** - * Given a tuple from an FDB key, attempts to determine what sub-path through this directory the tuple - * represents, returning a ResolvedKeySpacePath representing the leaf-most directory in the path. - *

- * If entries remained in the tuple beyond the leaf directory, then {@link ResolvedKeySpacePath#getRemainder()} - * can be used to fetch the remaining portion. - * See also {@link KeySpace#resolveFromKeyAsync(FDBRecordContext, Tuple)} if you need to resolve from the root. - *

- * @param context context used, if needed, for any database operations - * @param key a raw key from the database - * @return the {@link ResolvedKeySpacePath} corresponding to that key, with a potential remainder. - * @throws com.apple.foundationdb.record.RecordCoreArgumentException if the key provided is not part of this path - */ - @API(API.Status.EXPERIMENTAL) - @Nonnull - default CompletableFuture toResolvedPathAsync(@Nonnull FDBRecordContext context, byte[] key) { - throw new UnsupportedOperationException("toResolvedPathAsync is not supported"); - } - /** * Resolves the path into a {@link ResolvedKeySpacePath}, a form the retains all of the information about * the path itself along with the value to which each path entry is resolved. diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathImpl.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathImpl.java index f98c16778f..794672fd04 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathImpl.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathImpl.java @@ -34,6 +34,7 @@ import com.apple.foundationdb.tuple.ByteArrayUtil; import com.apple.foundationdb.tuple.Tuple; import com.apple.foundationdb.tuple.TupleHelpers; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import javax.annotation.Nonnull; @@ -181,8 +182,8 @@ public CompletableFuture toResolvedPathAsync(@Nonnull FDBR } @Nonnull - @Override - public CompletableFuture toResolvedPathAsync(@Nonnull final FDBRecordContext context, final byte[] key) { + @VisibleForTesting + CompletableFuture toResolvedPathAsync(@Nonnull final FDBRecordContext context, final byte[] key) { final Tuple keyTuple = Tuple.fromBytes(key); return toResolvedPathAsync(context).thenCompose(resolvedPath -> { // Now use the resolved path to find the child for the key @@ -308,7 +309,11 @@ public RecordCursor exportAllData(@Nonnull FDBRecordContext .setScanProperties(scanProperties) .build()), context.getExecutor()) - .map(keyValue -> new DataInKeySpacePath(this, keyValue, context)); + .mapPipelined(keyValue -> + toResolvedPathAsync(context, keyValue.getKey()) + .thenApply(resolvedKey -> + new DataInKeySpacePath(resolvedKey.toPath(), resolvedKey.getRemainder(), keyValue.getValue())), + 1); } /** diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathWrapper.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathWrapper.java index 622d44b0c2..1cec9787ea 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathWrapper.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathWrapper.java @@ -175,12 +175,6 @@ public CompletableFuture toResolvedPathAsync(@Nonnull FDBR return inner.toResolvedPathAsync(context); } - @Nonnull - @Override - public CompletableFuture toResolvedPathAsync(@Nonnull final FDBRecordContext context, final byte[] key) { - return inner.toResolvedPathAsync(context, key); - } - @Override public boolean equals(Object obj) { return inner.equals(obj); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/DataInKeySpacePathTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/DataInKeySpacePathTest.java index 64eccab827..6e8f0fb0b7 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/DataInKeySpacePathTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/DataInKeySpacePathTest.java @@ -20,337 +20,86 @@ package com.apple.foundationdb.record.provider.foundationdb.keyspace; -import com.apple.foundationdb.KeyValue; -import com.apple.foundationdb.Transaction; import com.apple.foundationdb.record.RecordCoreArgumentException; -import com.apple.foundationdb.record.provider.foundationdb.FDBDatabase; -import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext; import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpaceDirectory.KeyType; -import com.apple.foundationdb.record.test.FDBDatabaseExtension; import com.apple.foundationdb.tuple.Tuple; -import com.apple.foundationdb.tuple.TupleHelpers; -import com.apple.test.BooleanSource; -import com.apple.test.Tags; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.util.List; import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutionException; -import java.util.function.Function; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests for {@link DataInKeySpacePath}. */ -@Tag(Tags.RequiresFDB) class DataInKeySpacePathTest { - @RegisterExtension - final FDBDatabaseExtension dbExtension = new FDBDatabaseExtension(); - - @ParameterizedTest - @ValueSource(ints = {0, 1, 2, 3, 4, 5}) - void resolution(int depth) { - // Include some extra children to make sure resolution doesn't get confused - final String companyUuid = UUID.randomUUID().toString(); - KeySpace root = new KeySpace( - new KeySpaceDirectory("company", KeyType.STRING, companyUuid) - .addSubdirectory(new KeySpaceDirectory("department", KeyType.STRING) - .addSubdirectory(new KeySpaceDirectory("team_id", KeyType.LONG) - .addSubdirectory(new KeySpaceDirectory("employee_uuid", KeyType.UUID) - .addSubdirectory(new KeySpaceDirectory("active", KeyType.BOOLEAN) - .addSubdirectory(new KeySpaceDirectory("data", KeyType.NULL, null)) - .addSubdirectory(new KeySpaceDirectory("metaData", KeyType.LONG, 0)))) - .addSubdirectory(new KeySpaceDirectory("buildings", KeyType.STRING))))); - - final FDBDatabase database = dbExtension.getDatabase(); - - try (FDBRecordContext context = database.openContext()) { - Transaction tr = context.ensureActive(); - - UUID employeeId = UUID.randomUUID(); - - KeySpacePath employeePath = root.path("company") - .add("department", "engineering") - .add("team_id", 42L) - .add("employee_uuid", employeeId) - .add("active", true) - .add("data"); - - // Add additional tuple elements after the KeySpacePath - final Tuple remainderTuple = Tuple.from("salary", 75000L, "start_date", "2023-01-15"); - byte[] keyBytes = employeePath.toSubspace(context).pack(remainderTuple); - byte[] valueBytes = Tuple.from("employee_record").pack(); - - tr.set(keyBytes, valueBytes); - KeyValue keyValue = new KeyValue(keyBytes, valueBytes); - - // Create DataInKeySpacePath from the company-level path - List> pathNavigation = List.of( - path -> path.add("department", "engineering"), - path -> path.add("team_id", 42L), - path -> path.add("employee_uuid", employeeId), - path -> path.add("active", true), - path -> path.add("data") - ); - KeySpacePath toResolve = root.path("company"); - for (final Function function : pathNavigation.subList(0, depth)) { - toResolve = function.apply(toResolve); - } - DataInKeySpacePath dataInPath = new DataInKeySpacePath(toResolve, keyValue, context); - - ResolvedKeySpacePath resolved = dataInPath.getResolvedPath().join(); - - // Verify the path - ResolvedKeySpacePath activeLevel = assertNameAndValue(resolved, "data", null); - ResolvedKeySpacePath uuidLevel = assertNameAndValue(activeLevel, "active", true); - ResolvedKeySpacePath teamLevel = assertNameAndValue(uuidLevel, "employee_uuid", employeeId); - ResolvedKeySpacePath deptLevel = assertNameAndValue(teamLevel, "team_id", 42L); - ResolvedKeySpacePath companyLevel = assertNameAndValue(deptLevel, "department", "engineering"); - assertNull(assertNameAndValue(companyLevel, "company", companyUuid)); - - // Verify the resolved path recreates the KeySpacePath portion - assertEquals(TupleHelpers.subTuple(Tuple.fromBytes(keyBytes), 0, 6), resolved.toTuple()); - - // Verify that the remainder contains the additional tuple elements - assertEquals(remainderTuple, resolved.getRemainder()); - - context.commit(); - } - } @Test - void pathWithConstantValues() { - final String appUuid = UUID.randomUUID().toString(); + void basicAccessors() { KeySpace root = new KeySpace( - new KeySpaceDirectory("application", KeyType.STRING, appUuid) - .addSubdirectory(new KeySpaceDirectory("version", KeyType.LONG, 1L) - .addSubdirectory(new KeySpaceDirectory("environment", KeyType.STRING, "production") - .addSubdirectory(new KeySpaceDirectory("data", KeyType.STRING))))); - - final FDBDatabase database = dbExtension.getDatabase(); - - try (FDBRecordContext context = database.openContext()) { - Transaction tr = context.ensureActive(); - - KeySpacePath dataPath = root.path("application") - .add("version") // Uses constant value 1L - .add("environment") // Uses constant value "production" - .add("data", "user_records"); - // Add additional tuple elements after the KeySpacePath - byte[] keyBytes = dataPath.toSubspace(context).pack( - Tuple.from("config_id", 1001L, "version", "v2.1")); - byte[] valueBytes = Tuple.from("constant_test_data").pack(); - - tr.set(keyBytes, valueBytes); - KeyValue keyValue = new KeyValue(keyBytes, valueBytes); - - // Create DataInKeySpacePath from the application-level path - KeySpacePath appPath = root.path("application"); - DataInKeySpacePath dataInPath = new DataInKeySpacePath(appPath, keyValue, context); - - ResolvedKeySpacePath resolved = dataInPath.getResolvedPath().join(); - - // Verify the path using assertNameAndValue - ResolvedKeySpacePath envLevel = assertNameAndValue(resolved, "data", "user_records"); - ResolvedKeySpacePath versionLevel = assertNameAndValue(envLevel, "environment", "production"); - ResolvedKeySpacePath applicationLevel = assertNameAndValue(versionLevel, "version", 1L); - assertNull(assertNameAndValue(applicationLevel, "application", appUuid)); - - // Verify the resolved path recreates the KeySpacePath portion - assertEquals(TupleHelpers.subTuple(Tuple.fromBytes(keyBytes), 0, 4), resolved.toTuple()); - - // Verify that the remainder contains the additional tuple elements - Tuple remainder = resolved.getRemainder(); - assertNotNull(remainder); - assertEquals(4, remainder.size()); - assertEquals("config_id", remainder.getString(0)); - assertEquals(1001L, remainder.getLong(1)); - assertEquals("version", remainder.getString(2)); - assertEquals("v2.1", remainder.getString(3)); - - context.commit(); - } - } - - @Test - void pathWithDirectoryLayer() { - final String tenantUuid = UUID.randomUUID().toString(); - KeySpace root = new KeySpace( - new DirectoryLayerDirectory("tenant", tenantUuid) - .addSubdirectory(new KeySpaceDirectory("user_id", KeyType.LONG) - .addSubdirectory(new DirectoryLayerDirectory("service")))); - - final FDBDatabase database = dbExtension.getDatabase(); + new KeySpaceDirectory("test", KeyType.STRING, UUID.randomUUID().toString())); - try (FDBRecordContext context = database.openContext()) { - Transaction tr = context.ensureActive(); - - KeySpacePath servicePath = root.path("tenant") - .add("user_id", 999L) - .add("service", "analytics"); - - Tuple keyTuple = servicePath.toTuple(context); - byte[] keyBytes = keyTuple.pack(); - byte[] valueBytes = Tuple.from("directory_layer_data").pack(); - - tr.set(keyBytes, valueBytes); - KeyValue keyValue = new KeyValue(keyBytes, valueBytes); - - // Create DataInKeySpacePath from the tenant-level path - KeySpacePath tenantPath = root.path("tenant"); - DataInKeySpacePath dataInPath = new DataInKeySpacePath(tenantPath, keyValue, context); - - ResolvedKeySpacePath serviceLevel = dataInPath.getResolvedPath().join(); - - final ResolvedKeySpacePath userLevel = assertNameAndDirectoryScopedValue( - serviceLevel, "service", "analytics", servicePath, context); - ResolvedKeySpacePath tenantLevel = assertNameAndValue(userLevel, "user_id", 999L); + KeySpacePath testPath = root.path("test"); + byte[] valueBytes = Tuple.from("test_value").pack(); - assertNull(assertNameAndDirectoryScopedValue(tenantLevel, "tenant", tenantUuid, tenantPath, context)); + DataInKeySpacePath dataInPath = new DataInKeySpacePath(testPath, null, valueBytes); - context.commit(); - } - } - - @Test - void pathWithBinaryData() { - final String storeUuid = UUID.randomUUID().toString(); - KeySpace root = new KeySpace( - new KeySpaceDirectory("binary_store", KeyType.STRING, storeUuid) - .addSubdirectory(new KeySpaceDirectory("blob_id", KeyType.BYTES))); - - final FDBDatabase database = dbExtension.getDatabase(); - - try (FDBRecordContext context = database.openContext()) { - Transaction tr = context.ensureActive(); - - byte[] blobId = {0x01, 0x02, 0x03, (byte) 0xFF, (byte) 0xFE}; - KeySpacePath blobPath = root.path("binary_store").add("blob_id", blobId); - - Tuple keyTuple = blobPath.toTuple(context); - byte[] keyBytes = keyTuple.pack(); - byte[] valueBytes = "binary_test_data".getBytes(); - - tr.set(keyBytes, valueBytes); - KeyValue keyValue = new KeyValue(keyBytes, valueBytes); - - // Create DataInKeySpacePath from the binary_store-level path - KeySpacePath storePath = root.path("binary_store"); - DataInKeySpacePath dataInPath = new DataInKeySpacePath(storePath, keyValue, context); - - ResolvedKeySpacePath resolved = dataInPath.getResolvedPath().join(); - - // Verify the path using assertNameAndValue - assertEquals("blob_id", resolved.getDirectoryName()); - byte[] resolvedBytes = (byte[]) resolved.getResolvedValue(); - assertArrayEquals(blobId, resolvedBytes); - - ResolvedKeySpacePath storeLevel = assertNameAndValue(resolved.getParent(), "binary_store", storeUuid); - assertNull(storeLevel); - - // Verify the resolved path can recreate the original key - assertEquals(keyTuple, resolved.toTuple()); - - context.commit(); - } + // Verify accessors + assertSame(testPath, dataInPath.getPath()); + assertNull(dataInPath.getRemainder()); + assertArrayEquals(valueBytes, dataInPath.getValue()); } @ParameterizedTest - @BooleanSource("withRemainder") - void keyValueAccessors(boolean withRemainder) throws ExecutionException, InterruptedException { + @ValueSource(ints = {0, 1, 2, 5, 10}) + void accessorsWithRemainder(int remainderSize) { KeySpace root = new KeySpace( new KeySpaceDirectory("test", KeyType.STRING, UUID.randomUUID().toString())); - final FDBDatabase database = dbExtension.getDatabase(); - - try (FDBRecordContext context = database.openContext()) { - KeySpacePath testPath = root.path("test"); - Tuple pathTuple = testPath.toTuple(context); - byte[] keyBytes = withRemainder ? pathTuple.add("Remainder").pack() : pathTuple.pack(); - byte[] valueBytes = Tuple.from("accessor_test").pack(); - - KeyValue originalKeyValue = new KeyValue(keyBytes, valueBytes); - DataInKeySpacePath dataInPath = new DataInKeySpacePath(testPath, originalKeyValue, context); + KeySpacePath testPath = root.path("test"); - // Verify resolved path future is not null - CompletableFuture resolvedFuture = dataInPath.getResolvedPath(); - assertNotNull(resolvedFuture); - assertTrue(resolvedFuture.isDone() || !resolvedFuture.isCancelled()); - - final ResolvedKeySpacePath resolvedPath = resolvedFuture.get(); - assertEquals(pathTuple, resolvedPath.toTuple()); - assertArrayEquals(originalKeyValue.getValue(), dataInPath.getValue()); - if (withRemainder) { - assertEquals(Tuple.from("Remainder"), resolvedPath.getRemainder()); - } else { - assertNull(resolvedPath.getRemainder()); + // Create a remainder tuple with the specified number of elements + Tuple remainderTuple; + if (remainderSize > 0) { + remainderTuple = Tuple.from(); + for (int i = 0; i < remainderSize; i++) { + remainderTuple.add("element_" + i); } + } else { + remainderTuple = null; } - } - @Test - void withWrapper() { - final FDBDatabase database = dbExtension.getDatabase(); - final EnvironmentKeySpace keySpace = EnvironmentKeySpace.setupSampleData(database); + byte[] valueBytes = Tuple.from("test_value").pack(); - // Test export at different levels through wrapper methods - try (FDBRecordContext context = database.openContext()) { - // Test 4: Export from specific data store level - final EnvironmentKeySpace.ApplicationPath appPath = keySpace.root().userid(100L).application("app1"); - EnvironmentKeySpace.DataPath dataStore = appPath.dataStore(); + DataInKeySpacePath dataInPath = new DataInKeySpacePath(testPath, remainderTuple, valueBytes); - final byte[] key = dataStore.toTuple(context).add("record2").add(0).pack(); - final byte[] value = Tuple.from("data").pack(); - final DataInKeySpacePath dataInKeySpacePath = new DataInKeySpacePath(dataStore, new KeyValue(key, value), context); - - final ResolvedKeySpacePath resolvedPath = dataInKeySpacePath.getResolvedPath().join(); - assertEquals(dataStore.toResolvedPath(context), withoutRemainder(resolvedPath)); - assertEquals(Tuple.from("record2", 0), resolvedPath.getRemainder()); - - // Verify the path using assertNameAndValue - // Note: We expect the path to be: [environment] -> userid -> application -> data - ResolvedKeySpacePath appLevel; - appLevel = assertNameAndValue(resolvedPath, "data", EnvironmentKeySpace.DATA_VALUE); - ResolvedKeySpacePath userLevel = assertNameAndDirectoryScopedValue(appLevel, "application", "app1", - appPath, context); - ResolvedKeySpacePath envLevel = assertNameAndValue(userLevel, "userid", 100L); - assertNull(assertNameAndDirectoryScopedValue(envLevel, keySpace.root().getDirectoryName(), - keySpace.root().getValue(), keySpace.root(), context)); - } + // Verify accessors + assertSame(testPath, dataInPath.getPath()); + assertEquals(remainderTuple, dataInPath.getRemainder()); + assertArrayEquals(valueBytes, dataInPath.getValue()); } @Test - void badPath() { - final String rootUuid = UUID.randomUUID().toString(); + void withBinaryValue() { KeySpace root = new KeySpace( - new KeySpaceDirectory("test", KeyType.STRING, rootUuid)); + new KeySpaceDirectory("binary_store", KeyType.STRING, UUID.randomUUID().toString())); - final FDBDatabase database = dbExtension.getDatabase(); + KeySpacePath storePath = root.path("binary_store"); + Tuple remainder = Tuple.from("key1", "key2"); + byte[] binaryValue = {0x01, 0x02, 0x03, (byte) 0xFF, (byte) 0xFE}; - try (FDBRecordContext context = database.openContext()) { - KeySpacePath testPath = root.path("test"); - byte[] keyBytes = Tuple.from("banana", rootUuid).pack(); - byte[] valueBytes = Tuple.from("accessor_test").pack(); + DataInKeySpacePath dataInPath = new DataInKeySpacePath(storePath, remainder, binaryValue); - KeyValue originalKeyValue = new KeyValue(keyBytes, valueBytes); - DataInKeySpacePath dataInPath = new DataInKeySpacePath(testPath, originalKeyValue, context); - final CompletionException completionException = assertThrows(CompletionException.class, - () -> dataInPath.getResolvedPath().join()); - assertInstanceOf(RecordCoreArgumentException.class, completionException.getCause()); - } + // Verify accessors + assertSame(storePath, dataInPath.getPath()); + assertEquals(remainder, dataInPath.getRemainder()); + assertArrayEquals(binaryValue, dataInPath.getValue()); } /** @@ -363,38 +112,10 @@ void nullValue() { KeySpace root = new KeySpace( new KeySpaceDirectory("test", KeyType.STRING, UUID.randomUUID().toString())); - final FDBDatabase database = dbExtension.getDatabase(); - - try (FDBRecordContext context = database.openContext()) { - KeySpacePath testPath = root.path("test"); - byte[] keyBytes = testPath.toTuple(context).pack(); - byte[] valueBytes = null; - - KeyValue originalKeyValue = new KeyValue(keyBytes, valueBytes); - assertThrows(RecordCoreArgumentException.class, - () -> new DataInKeySpacePath(testPath, originalKeyValue, context)); - } - } - - private static ResolvedKeySpacePath assertNameAndDirectoryScopedValue(ResolvedKeySpacePath resolved, - String name, Object logicalValue, - KeySpacePath path, FDBRecordContext context) { - assertNotNull(resolved); - assertEquals(name, resolved.getDirectoryName()); - assertEquals(path.toResolvedPath(context).getResolvedValue(), resolved.getResolvedValue()); - assertEquals(logicalValue, resolved.getLogicalValue()); - return resolved.getParent(); - } - - private static ResolvedKeySpacePath assertNameAndValue(ResolvedKeySpacePath resolved, String name, Object value) { - assertNotNull(resolved); - assertEquals(name, resolved.getDirectoryName()); - assertEquals(value, resolved.getResolvedValue()); - assertEquals(value, resolved.getLogicalValue()); - return resolved.getParent(); - } + KeySpacePath testPath = root.path("test"); + byte[] valueBytes = null; - private ResolvedKeySpacePath withoutRemainder(final ResolvedKeySpacePath path) { - return new ResolvedKeySpacePath(path.getParent(), path.toPath(), path.getResolvedPathValue(), null); + assertThrows(RecordCoreArgumentException.class, + () -> new DataInKeySpacePath(testPath, null, valueBytes)); } } diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathDataExportTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathDataExportTest.java index 52cf9fe423..1ff39187dd 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathDataExportTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathDataExportTest.java @@ -105,16 +105,16 @@ void exportAllDataFromSimplePath() throws ExecutionException, InterruptedExcepti // Verify the data is sorted by key for (int i = 1; i < allData.size(); i++) { - assertTrue(getKey(allData.get(i - 1)).compareTo(getKey(allData.get(i))) < 0); + assertTrue(getKey(allData.get(i - 1), context).compareTo(getKey(allData.get(i), context)) < 0); } } } // `toTuple` does not include the remainder, I'm not sure if that is intentional, or an oversight. - private Tuple getKey(final DataInKeySpacePath dataInKeySpacePath) throws ExecutionException, InterruptedException { - final ResolvedKeySpacePath resolvedKeySpacePath = dataInKeySpacePath.getResolvedPath().get(); - if (resolvedKeySpacePath.getRemainder() != null) { - return resolvedKeySpacePath.toTuple().addAll(resolvedKeySpacePath.getRemainder()); + private Tuple getKey(final DataInKeySpacePath dataInKeySpacePath, final FDBRecordContext context) throws ExecutionException, InterruptedException { + final ResolvedKeySpacePath resolvedKeySpacePath = dataInKeySpacePath.getPath().toResolvedPathAsync(context).get(); + if (dataInKeySpacePath.getRemainder() != null) { + return resolvedKeySpacePath.toTuple().addAll(dataInKeySpacePath.getRemainder()); } else { return resolvedKeySpacePath.toTuple(); } @@ -490,7 +490,7 @@ void exportSingleKeyWithContinuation(boolean forward, boolean withRemainder, int // Store test data final List> expectedBatches; - final KeySpacePath pathToExport = root.path("continuation"); + final KeySpacePath pathToExport = root.path("continuation").add("item", 42L); try (FDBRecordContext context = database.openContext()) { Transaction tr = context.ensureActive(); byte[] key; @@ -586,21 +586,19 @@ void exportAllDataThroughKeySpacePathWrapperResolvedPaths() { try (FDBRecordContext context = database.openContext()) { // Test 4: Export from specific data store level EnvironmentKeySpace.DataPath dataStore = keySpace.root().userid(100L).application("app1").dataStore(); - final List dataStoreData = dataStore.exportAllData(context, null, ScanProperties.FORWARD_SCAN) - .mapPipelined(DataInKeySpacePath::getResolvedPath, 1).asList().join(); + final List dataStoreData = dataStore.exportAllData(context, null, ScanProperties.FORWARD_SCAN) + .asList().join(); // Verify data store records have correct remainder - final ArrayList remainders = new ArrayList<>(); - for (ResolvedKeySpacePath kv : dataStoreData) { + for (DataInKeySpacePath data : dataStoreData) { // Path tuple should be the same - Tuple dataStoreTuple = dataStore.toTuple(context); - assertEquals(dataStoreTuple, kv.toTuple()); - remainders.add(kv.getRemainder()); + assertEquals(dataStore, data.getPath()); } assertEquals(List.of( Tuple.from("record1"), Tuple.from("record2", 0), Tuple.from("record2", 1) - ), remainders, "remainders should be the same"); + ), dataStoreData.stream().map(DataInKeySpacePath::getRemainder).collect(Collectors.toList()), + "remainders should be the same"); } } @@ -627,11 +625,10 @@ private static List exportAllData(final KeySpacePath pathToE .asList().join(); // assert that the resolved paths contain the right prefix - final List resolvedPaths = pathToExport.exportAllData(context, null, ScanProperties.FORWARD_SCAN) - .mapPipelined(DataInKeySpacePath::getResolvedPath, 1).asList().join(); - final ResolvedKeySpacePath rootResolvedPath = pathToExport.toResolvedPath(context); - for (ResolvedKeySpacePath resolvedPath : resolvedPaths) { - assertStartsWith(rootResolvedPath, resolvedPath); + final List dataPaths = pathToExport.exportAllData(context, null, ScanProperties.FORWARD_SCAN) + .map(DataInKeySpacePath::getPath).asList().join(); + for (KeySpacePath dataPath : dataPaths) { + assertStartsWith(pathToExport, dataPath); } // assert that the reverse scan is the same as the forward scan, but in reverse @@ -666,20 +663,22 @@ private static void assertDataInKeySpacePathEquals(final List actualList) { assertThat(actualList).zipSatisfy(expectedList, (actual, other) -> { - assertThat(actual.getResolvedPath().join()).isEqualTo(other.getResolvedPath().join()); + assertThat(actual.getPath()).isEqualTo(other.getPath()); + // I don't know why intelliJ can't handle this without the explicit type parameter + Assertions.assertThat(actual.getRemainder()).isEqualTo(other.getRemainder()); assertThat(actual.getValue()).isEqualTo(other.getValue()); }); } - private static void assertStartsWith(final ResolvedKeySpacePath rootResolvedPath, ResolvedKeySpacePath resolvedPath) { - ResolvedKeySpacePath searchPath = resolvedPath.withRemainder(null); + private static void assertStartsWith(final KeySpacePath rootPath, KeySpacePath childPath) { + KeySpacePath searchPath = childPath; do { - if (searchPath.equals(rootResolvedPath)) { + if (searchPath.equals(rootPath)) { return; } searchPath = searchPath.getParent(); } while (searchPath != null); - Assertions.fail("Expected <" + resolvedPath + "> to start with <" + rootResolvedPath + "> but it didn't"); + Assertions.fail("Expected <" + childPath + "> to start with <" + rootPath + "> but it didn't"); } private static void verifyExtractedData(final List app1User100Data, diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathTest.java index ce6952d697..1e0417e3a4 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/keyspace/KeySpacePathTest.java @@ -36,7 +36,6 @@ import java.util.concurrent.ExecutionException; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; /** @@ -89,7 +88,7 @@ void testToResolvedPathAsync(boolean withRemainder, boolean useDirectoryLayer, b try (FDBRecordContext context = database.openContext()) { KeySpacePath rootPath = keySpace.path("test_root"); - KeySpacePath branchPath = rootPath.add("branch"); + KeySpacePathImpl branchPath = (KeySpacePathImpl)rootPath.add("branch"); // Build the full path - add leaf with or without value based on constant KeySpacePath fullPath; @@ -118,41 +117,6 @@ void testToResolvedPathAsync(boolean withRemainder, boolean useDirectoryLayer, b } } - @Test - void testToResolvedPathAsyncWithWrapper() { - final FDBDatabase database = dbExtension.getDatabase(); - final EnvironmentKeySpace keySpace = EnvironmentKeySpace.setupSampleData(database); - - try (FDBRecordContext context = database.openContext()) { - // Use the wrapper paths which extend KeySpacePathWrapper - EnvironmentKeySpace.ApplicationPath appPath = keySpace.root().userid(100L).application("app1"); - EnvironmentKeySpace.DataPath dataPath = appPath.dataStore(); - - // Create a key with remainder - Tuple remainderTuple = Tuple.from("record_id", 42L, "version", 1); - byte[] keyBytes = dataPath.toSubspace(context).pack(remainderTuple); - - // Test toResolvedPathAsync on the wrapper - should resolve from appPath through dataPath - ResolvedKeySpacePath resolved = appPath.toResolvedPathAsync(context, keyBytes).join(); - - // Verify the resolved path - assertEquals(EnvironmentKeySpace.DATA_KEY, resolved.getDirectoryName()); - assertEquals(EnvironmentKeySpace.DATA_VALUE, resolved.getResolvedValue()); - assertEquals(remainderTuple, resolved.getRemainder()); - - // Verify parent structure - ResolvedKeySpacePath appLevel = resolved.getParent(); - assertNotNull(appLevel); - assertEquals(EnvironmentKeySpace.APPLICATION_KEY, appLevel.getDirectoryName()); - assertEquals("app1", appLevel.getLogicalValue()); - - ResolvedKeySpacePath userLevel = appLevel.getParent(); - assertNotNull(userLevel); - assertEquals(EnvironmentKeySpace.USER_KEY, userLevel.getDirectoryName()); - assertEquals(100L, userLevel.getResolvedValue()); - } - } - @Test void testToResolvedPathAsyncWithKeyNotSubPath() { final FDBDatabase database = dbExtension.getDatabase(); @@ -160,7 +124,7 @@ void testToResolvedPathAsyncWithKeyNotSubPath() { try (FDBRecordContext context = database.openContext()) { KeySpacePath rootPath = keySpace.path("test_root"); - KeySpacePath branchPath = rootPath.add("branch"); + KeySpacePathImpl branchPath = (KeySpacePathImpl)rootPath.add("branch"); // Create a key that is shorter than branchPath - it stops at root byte[] shorterKeyBytes = rootPath.toSubspace(context).pack(); @@ -179,7 +143,7 @@ void testToResolvedPathAsyncWithInvalidTuple() { try (FDBRecordContext context = database.openContext()) { KeySpacePath rootPath = keySpace.path("test_root"); - KeySpacePath branchPath = rootPath.add("branch"); + KeySpacePathImpl branchPath = (KeySpacePathImpl)rootPath.add("branch"); // Create a byte array that is not a valid tuple byte[] invalidBytes = new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF}; @@ -201,9 +165,6 @@ void testDefaultMethods() { try (FDBRecordContext context = database.openContext()) { // thenCallReadMethod throws an error if there is not a default implementation - Mockito.when(mock.toResolvedPathAsync(Mockito.any(), Mockito.any())).thenCallRealMethod(); - assertThrows(UnsupportedOperationException.class, - () -> mock.toResolvedPathAsync(context, Tuple.from("foo").pack())); Mockito.when(mock.exportAllData(Mockito.any(), Mockito.any(), Mockito.any())).thenCallRealMethod(); assertThrows(UnsupportedOperationException.class, () -> mock.exportAllData(context, null, ScanProperties.FORWARD_SCAN));