KeySpaceDirectory { @Nonnull public KeySpaceDirectory addSubdirectory(@Nonnull KeySpaceDirectory subdirectory) { for (KeySpaceDirectory existingSubdir : subdirsByName.values()) { if (existingSubdir.getName().equals(subdirectory.getName())) { throw new RecordCoreArgumentException("Subdirectory already exists", LogMessageKeys.PARENT_DIR, getName(), LogMessageKeys.DIR_NAME, subdirectory.getName()); } if (existingSubdir.getKeyType() == subdirectory.getKeyType()) { if (existingSubdir.getValue() == ANY_VALUE || subdirectory.getValue() == ANY_VALUE) { throw new RecordCoreArgumentException("Cannot add directory due to overlapping type", LogMessageKeys.PARENT_DIR, getName(), LogMessageKeys.DIR_NAME, existingSubdir.getName(), LogMessageKeys.DIR_TYPE, subdirectory.getKeyType()); } if (Objects.equals(subdirectory.getValue(), existingSubdir.getValue())) { throw new RecordCoreArgumentException("Cannot add directory due to overlapping constant value", LogMessageKeys.PARENT_DIR, getName(), LogMessageKeys.DIR_NAME, existingSubdir.getName(), LogMessageKeys.DIR_TYPE, existingSubdir.getKeyType(), LogMessageKeys.DIR_VALUE, existingSubdir.getValue()); } if (!existingSubdir.isCompatible(this, subdirectory) || !subdirectory.isCompatible(this, existingSubdir)) { throw new RecordCoreArgumentException("Cannot add directory due to incompatibility with existing subdirectory", LogMessageKeys.PARENT_DIR, getName(), LogMessageKeys.DIR_TYPE, existingSubdir.getKeyType(), LogMessageKeys.DIR_VALUE, existingSubdir.getValue()); } } } subdirectory.setParent(this); subdirsByName.put(subdirectory.getName(), subdirectory); subdirs.add(subdirectory); return this; } KeySpaceDirectory(@Nonnull String name, @Nonnull KeyType keyType, @Nullable Object value,
                             @Nullable Function<KeySpacePath, KeySpacePath> wrapper);  KeySpaceDirectory(@Nonnull String name, @Nonnull KeyType keyType, @Nullable Function<KeySpacePath, KeySpacePath> wrapper);  KeySpaceDirectory(@Nonnull String name, @Nonnull KeyType keyType);  KeySpaceDirectory(@Nonnull String name, @Nonnull KeyType keyType, @Nullable Object value); int depth(); @Nonnull CompletableFuture<ResolvedKeySpacePath> findChildForValue(@Nonnull FDBRecordContext context,
                                                                     @Nullable ResolvedKeySpacePath parent,
                                                                     @Nullable Object value); @Nonnull KeySpaceDirectory addSubdirectory(@Nonnull KeySpaceDirectory subdirectory); boolean isLeaf(); @Nonnull KeySpaceDirectory getSubdirectory(@Nonnull String name); @Nonnull List<KeySpaceDirectory> getSubdirectories(); @Nullable KeySpaceDirectory getParent(); @Nonnull String getName(); @Nonnull String getNameInTree(); @Nonnull KeyType getKeyType(); @Nullable Object getValue(); String toPathString(); @Override String toString(); void toTree(Writer out); static final Object ANY_VALUE; }
@Test public void testRestrictAnyLongAndDirectoryLayerLong() throws Exception { assertThrows(RecordCoreArgumentException.class, () -> new KeySpace( new KeySpaceDirectory("root", KeyType.STRING, "production") .addSubdirectory(new DirectoryLayerDirectory("dir1")) .addSubdirectory(new KeySpaceDirectory("dir2", KeyType.LONG)))); assertThrows(RecordCoreArgumentException.class, () -> new KeySpace( new KeySpaceDirectory("root", KeyType.STRING, "production") .addSubdirectory(new DirectoryLayerDirectory("dir1", "A")) .addSubdirectory(new KeySpaceDirectory("dir2", KeyType.LONG)))); }
@Test public void testRestrictConstantLongAndDirectoryLayerLong() throws Exception { assertThrows(RecordCoreArgumentException.class, () -> new KeySpace( new KeySpaceDirectory("root", KeyType.STRING, "production") .addSubdirectory(new DirectoryLayerDirectory("dir1")) .addSubdirectory(new KeySpaceDirectory("dir2", KeyType.LONG, 10L)))); assertThrows(RecordCoreArgumentException.class, () -> new KeySpace( new KeySpaceDirectory("root", KeyType.STRING, "production") .addSubdirectory(new DirectoryLayerDirectory("dir1", "A")) .addSubdirectory(new KeySpaceDirectory("dir2", KeyType.LONG, 10L)))); }
@Test public void testRestrictAnyDirectoryLayerAndConstantDirectoryLayer() throws Exception { assertThrows(RecordCoreArgumentException.class, () -> new KeySpace( new KeySpaceDirectory("root", KeyType.STRING, "production") .addSubdirectory(new DirectoryLayerDirectory("dir1", "A")) .addSubdirectory(new DirectoryLayerDirectory("dir2")))); }
@Test public void testRestrictSameConstantDirectoryLayer() throws Exception { assertThrows(RecordCoreArgumentException.class, () -> new KeySpace( new KeySpaceDirectory("root", KeyType.STRING, "production") .addSubdirectory(new DirectoryLayerDirectory("dir1", "A")) .addSubdirectory(new DirectoryLayerDirectory("dir2", "A")))); }
@Test public void testAllowDifferentConstantDirectoryLayer() throws Exception { new KeySpace( new KeySpaceDirectory("root", KeyType.STRING, "production") .addSubdirectory(new DirectoryLayerDirectory("dir1", "A")) .addSubdirectory(new DirectoryLayerDirectory("dir2", "B"))); }
@Test public void testPathToAndFromTuple() throws Exception { KeySpace root = new KeySpace( new DirectoryLayerDirectory("production", "production") .addSubdirectory(new KeySpaceDirectory("userid", KeyType.LONG) .addSubdirectory(new DirectoryLayerDirectory("application") .addSubdirectory(new KeySpaceDirectory("dataStore", KeyType.NULL)) .addSubdirectory(new DirectoryLayerDirectory("metadataStore", "S")))), new DirectoryLayerDirectory("test", "test") .addSubdirectory(new KeySpaceDirectory("userid", KeyType.LONG) .addSubdirectory(new DirectoryLayerDirectory("application") .addSubdirectory(new KeySpaceDirectory("dataStore", KeyType.NULL)) .addSubdirectory(new DirectoryLayerDirectory("metadataStore", "S"))))); final KeySpacePath path1 = root.path("production") .add("userid", 123456789L) .add("application", "com.mybiz.application1") .add("dataStore"); final KeySpacePath path2 = root.path("test") .add("userid", 987654321L) .add("application", "com.mybiz.application2") .add("metadataStore"); final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(); final Tuple path1Tuple; final Tuple path2Tuple; try (FDBRecordContext context = database.openContext()) { path1Tuple = path1.toTuple(context); path2Tuple = path2.toTuple(context); context.commit(); } final Tuple path1ExpectedTuple; final Tuple path2ExpectedTuple; try (FDBRecordContext context = database.openContext()) { List<Long> entries = resolveBatch(context, "production", "test", "com.mybiz.application1", "com.mybiz.application2", "S"); path1ExpectedTuple = Tuple.from(entries.get(0), 123456789L, entries.get(2), null); path2ExpectedTuple = Tuple.from(entries.get(1), 987654321L, entries.get(3), entries.get(4)); assertEquals(path1ExpectedTuple, path1Tuple); assertEquals(path2ExpectedTuple, path2Tuple); List<ResolvedKeySpacePath> revPath1 = root.resolveFromKey(context, path1ExpectedTuple).flatten(); assertEquals("production", revPath1.get(0).getDirectoryName()); assertEquals(entries.get(0), revPath1.get(0).getResolvedValue()); assertEquals("userid", revPath1.get(1).getDirectoryName()); assertEquals(123456789L, revPath1.get(1).getResolvedValue()); assertEquals("application", revPath1.get(2).getDirectoryName()); assertEquals(entries.get(2), revPath1.get(2).getResolvedValue()); assertEquals("dataStore", revPath1.get(3).getDirectoryName()); assertEquals(null, revPath1.get(3).getResolvedValue()); Tuple extendedPath2 = path2ExpectedTuple.add(10L); List<ResolvedKeySpacePath> revPath2 = root.resolveFromKey(context, extendedPath2).flatten(); assertEquals("test", revPath2.get(0).getDirectoryName()); assertEquals("userid", revPath2.get(1).getDirectoryName()); assertEquals("application", revPath2.get(2).getDirectoryName()); assertEquals("metadataStore", revPath2.get(3).getDirectoryName()); assertEquals(Tuple.from(10L), revPath2.get(3).getRemainder()); } }
@Test public void testInvalidPath() throws Exception { KeySpace root = new KeySpace( new KeySpaceDirectory("root", KeyType.STRING, "production") .addSubdirectory(new KeySpaceDirectory("a", KeyType.STRING) .addSubdirectory(new KeySpaceDirectory("b", KeyType.STRING)))); final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(); try (FDBRecordContext context = database.openContext()) { Tuple tuple = root.path("root") .add("a", "foo") .add("b", "bar").toTuple(context); assertEquals(Tuple.from("production", "foo", "bar"), tuple); assertThrows(NoSuchDirectoryException.class, () -> root.path("foo").add("a", "bar").toTuple(context)); } }
@Test public void testDirectoryLayerDirectoryUsingLongs() throws Exception { KeySpace root = new KeySpace( new DirectoryLayerDirectory("cabinet", "cabinet") .addSubdirectory(new DirectoryLayerDirectory("game"))); final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(); final Tuple senetTuple; final Tuple urTuple; try (FDBRecordContext context = database.openContext()) { senetTuple = root.path("cabinet").add("game", "senet").toTuple(context); urTuple = root.path("cabinet").add("game", "royal_game_of_ur").toTuple(context); context.commit(); } try (FDBRecordContext context = database.openContext()) { assertEquals(senetTuple, root.path("cabinet") .add("game", senetTuple.getLong(1)).toTuple(context)); assertEquals(urTuple, root.path("cabinet") .add("game", urTuple.getLong(1)).toTuple(context)); } }
@Test public void testDirectoryLayerDirectoryValidation() throws Exception { KeySpace root = new KeySpace( new DirectoryLayerDirectory("school", "school") .addSubdirectory(new DirectoryLayerDirectory("school_name") .addSubdirectory(new DirectoryLayerDirectory("teachers", "teachers")) .addSubdirectory(new DirectoryLayerDirectory("students", "students")))); final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(); final Tuple teachersTuple; final Tuple studentsTuple; try (FDBRecordContext context = database.openContext()) { teachersTuple = root.path("school").add("school_name", "Football Tech").add("teachers").toTuple(context); studentsTuple = root.path("school").add("school_name", "Football Tech").add("students").toTuple(context); context.commit(); } try (FDBRecordContext context = database.openContext()) { assertThrows(RecordCoreArgumentException.class, () -> root.path("school") .add("school_name", "Football Tech") .add("teachers", studentsTuple.getLong(1)).toTuple(context)); assertThrows(RecordCoreArgumentException.class, () -> root.path("school") .add("school_name", "Football Tech") .add("students", teachersTuple.getLong(1)).toTuple(context)); assertThrows(NoSuchElementException.class, () -> root.path("school") .add("school_name", -746464638L) .add("teachers").toTuple(context)); } }
@Test public void testListObeysReturnedRowAndScanLimits() { KeySpace root = new KeySpace( new KeySpaceDirectory("root", KeyType.STRING, "root-" + random.nextInt(Integer.MAX_VALUE)) .addSubdirectory(new KeySpaceDirectory("a", KeyType.LONG) .addSubdirectory(new KeySpaceDirectory("b", KeyType.LONG)))); final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(); try (FDBRecordContext context = database.openContext()) { Transaction tr = context.ensureActive(); for (int i = 0; i < 5; i++) { for (int j = 0; j < 2; j++) { tr.set(root.path("root") .add("a", i) .add("b", j) .toTuple(context).pack(), Tuple.from(i + j).pack()); } } tr.commit().join(); } doLimitedScan(database, root, 5, Integer.MAX_VALUE, RecordCursor.NoNextReason.RETURN_LIMIT_REACHED); doLimitedScan(database, root, Integer.MAX_VALUE, 5, RecordCursor.NoNextReason.SCAN_LIMIT_REACHED); }
@Test public void testFromTupleWithConstantValue() throws Exception { KeySpace root = new KeySpace( new KeySpaceDirectory("root", KeyType.LONG, 1L) .addSubdirectory(new KeySpaceDirectory("dir1", KeyType.STRING, "a")) .addSubdirectory(new KeySpaceDirectory("dir2", KeyType.STRING, "b")) .addSubdirectory(new KeySpaceDirectory("dir3", KeyType.LONG))); final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(); try (FDBRecordContext context = database.openContext()) { Tuple tuple = Tuple.from(1L, "a"); assertEquals(tuple, root.resolveFromKey(context, tuple).toTuple()); tuple = Tuple.from(1L, "b"); assertEquals(tuple, root.resolveFromKey(context, tuple).toTuple()); final Tuple badTuple1 = Tuple.from(1L, "c", "d"); assertThrows(RecordCoreArgumentException.class, () -> root.resolveFromKey(context, badTuple1).toTuple(), "key_tuple", badTuple1, "key_tuple_pos", 1); } }
@Test public void testDeleteAllDataAndHasData() throws Exception { KeySpace root = new KeySpace( new KeySpaceDirectory("root", KeyType.LONG, Math.abs(random.nextLong())) .addSubdirectory(new KeySpaceDirectory("dir1", KeyType.STRING, "a") .addSubdirectory(new KeySpaceDirectory("dir1_1", KeyType.LONG))) .addSubdirectory(new KeySpaceDirectory("dir2", KeyType.STRING, "b") .addSubdirectory(new KeySpaceDirectory("dir2_1", KeyType.LONG))) .addSubdirectory(new KeySpaceDirectory("dir3", KeyType.STRING, "c") .addSubdirectory(new KeySpaceDirectory("dir3_1", KeyType.LONG)))); final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(); try (FDBRecordContext context = database.openContext()) { Transaction tr = context.ensureActive(); for (int i = 0; i < 5; i++) { tr.set(root.path("root").add("dir1").add("dir1_1", i).toTuple(context).pack(), Tuple.from(i).pack()); tr.set(root.path("root").add("dir2").add("dir2_1", i).toTuple(context).pack(), Tuple.from(i).pack()); tr.set(root.path("root").add("dir3").add("dir3_1", i).toTuple(context).pack(), Tuple.from(i).pack()); } context.commit(); } try (FDBRecordContext context = database.openContext()) { for (int i = 1; i <= 3; i++) { assertTrue(root.path("root").add("dir" + i).hasData(context), "dir" + i + " is empty!"); } root.path("root").add("dir2").deleteAllData(context); context.commit(); } try (FDBRecordContext context = database.openContext()) { assertTrue(root.path("root").add("dir1").hasData(context), "dir1 is empty!"); assertFalse(root.path("root").add("dir2").hasData(context), "dir2 has data!"); assertTrue(root.path("root").add("dir3").hasData(context), "dir3 is empty!"); } }
@Test public void testInvalidListRange() throws Exception { final String rootDir = "root_dir"; final String stringDir = "string_dir"; final String longConstDir = "long_const_dir"; KeySpaceDirectory dirA = new KeySpaceDirectory(rootDir, KeyType.LONG, random.nextLong()) .addSubdirectory(new KeySpaceDirectory(stringDir, KeyType.STRING)) .addSubdirectory(new KeySpaceDirectory(longConstDir, KeyType.LONG, 100)); KeySpace root = new KeySpace(dirA); final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(); try (FDBRecordContext context = database.openContext()) { root.path(rootDir) .listSubdirectory(context, stringDir, new ValueRange<>("A", "B", EndpointType.RANGE_INCLUSIVE, EndpointType.RANGE_EXCLUSIVE), null, ScanProperties.FORWARD_SCAN); assertThrows(RecordCoreArgumentException.class, () -> root.path(rootDir).listSubdirectory( context, stringDir, new ValueRange<>(100, 200, EndpointType.RANGE_INCLUSIVE, EndpointType.RANGE_EXCLUSIVE), null, ScanProperties.FORWARD_SCAN) ); assertThrows(RecordCoreArgumentException.class, () -> root.path(rootDir).listSubdirectory( context, stringDir, new ValueRange<>("A", "B", EndpointType.PREFIX_STRING, EndpointType.RANGE_EXCLUSIVE), null, ScanProperties.FORWARD_SCAN) ); assertThrows(RecordCoreArgumentException.class, () -> root.path(rootDir).listSubdirectory( context, longConstDir, new ValueRange<>(100, 200, EndpointType.RANGE_INCLUSIVE, EndpointType.RANGE_EXCLUSIVE), null, ScanProperties.FORWARD_SCAN) ); } }
@Test public void flattenPreservesWrapper() { KeySpace keySpace = new KeySpace( new KeySpaceDirectory("a", KeyType.STRING, PathA::new) .addSubdirectory(new KeySpaceDirectory("b", KeyType.STRING, PathB::new))); final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(); List<KeySpacePath> path = keySpace.path("a", "foo").add("b", "bar").flatten(); assertThat("a should be pathA", path.get(0), instanceOf(PathA.class)); assertThat("b should be pathB", path.get(1), instanceOf(PathB.class)); }
@Test public void testPathCompareByValue() { KeySpace keySpace = new KeySpace( new KeySpaceDirectory("a", KeyType.STRING) .addSubdirectory(new KeySpaceDirectory("b", KeyType.STRING) .addSubdirectory(new KeySpaceDirectory("d", KeyType.STRING, TestWrapper1::new))) .addSubdirectory(new KeySpaceDirectory("c", KeyType.LONG) .addSubdirectory(new KeySpaceDirectory("d", KeyType.STRING, TestWrapper1::new)))); final KeySpacePath p1 = keySpace.path("a", "alpha") .add("b", "bravo") .add("d", "delta"); final KeySpacePath p2 = keySpace.path("a", "alpha") .add("b", "bravo") .add("d", "duck"); final KeySpacePath p3 = keySpace.path("a", "alpha") .add("c", 23) .add("d", "delta"); final KeySpacePath sameAsP1 = keySpace.path("a", "alpha") .add("b", "bravo") .add("d", "delta"); assertNotEquals(p1, p2, "paths have different values"); assertNotEquals(p1.hashCode(), p2.hashCode(), "they have distinct hash codes"); assertNotEquals(p1, p3, "paths have different parents"); assertNotEquals(p1.hashCode(), p3.hashCode(), "they have distinct hash codes"); assertNotSame(p1, sameAsP1, "the paths are not equal by reference"); assertEquals(p1, sameAsP1, "they are equal by value"); assertEquals(p1.hashCode(), sameAsP1.hashCode(), "they have the same hash code"); }