diff --git a/rest-api/common-rest/src/main/resources/version.properties b/rest-api/common-rest/src/main/resources/version.properties index e26645a4bc2..8cb4d96428b 100644 --- a/rest-api/common-rest/src/main/resources/version.properties +++ b/rest-api/common-rest/src/main/resources/version.properties @@ -13,5 +13,5 @@ # See the License for the specific language governing permissions and # limitations under the License. # -gaffer.version=2.2.5-SNAPSHOT +gaffer.version=${project.version} koryphe.version=${koryphe.version} diff --git a/store-implementation/accumulo-store/src/main/java/uk/gov/gchq/gaffer/accumulostore/operation/impl/GetElementsWithinSet.java b/store-implementation/accumulo-store/src/main/java/uk/gov/gchq/gaffer/accumulostore/operation/impl/GetElementsWithinSet.java index 3697c7b0ca6..8c18f284e56 100644 --- a/store-implementation/accumulo-store/src/main/java/uk/gov/gchq/gaffer/accumulostore/operation/impl/GetElementsWithinSet.java +++ b/store-implementation/accumulo-store/src/main/java/uk/gov/gchq/gaffer/accumulostore/operation/impl/GetElementsWithinSet.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 Crown Copyright + * Copyright 2016-2024 Crown Copyright * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import uk.gov.gchq.gaffer.data.element.id.EntityId; import uk.gov.gchq.gaffer.data.elementdefinition.view.View; import uk.gov.gchq.gaffer.operation.Operation; -import uk.gov.gchq.gaffer.operation.graph.GraphFilters; +import uk.gov.gchq.gaffer.operation.graph.SeededGraphFilters; import uk.gov.gchq.gaffer.operation.io.InputOutput; import uk.gov.gchq.gaffer.operation.io.MultiEntityIdInput; import uk.gov.gchq.gaffer.operation.serialisation.TypeReferenceImpl; @@ -44,11 +44,12 @@ public class GetElementsWithinSet implements InputOutput, Iterable>, MultiEntityIdInput, - GraphFilters { + SeededGraphFilters { private View view; private DirectedType directedType; private Iterable input; private Map options; + private IncludeIncomingOutgoingType includeIncomingOutGoing; @Override public View getView() { @@ -95,10 +96,21 @@ public void setOptions(final Map options) { this.options = options; } + @Override + public IncludeIncomingOutgoingType getIncludeIncomingOutGoing() { + return includeIncomingOutGoing; + } + + @Override + public void setIncludeIncomingOutGoing(final IncludeIncomingOutgoingType inOutType) { + this.includeIncomingOutGoing = inOutType; + } + @Override public GetElementsWithinSet shallowClone() { return new GetElementsWithinSet.Builder() .view(view) + .inOutType(includeIncomingOutGoing) .directedType(directedType) .input(input) .options(options) @@ -108,7 +120,7 @@ public GetElementsWithinSet shallowClone() { public static class Builder extends Operation.BaseBuilder implements InputOutput.Builder, Iterable, Builder>, MultiEntityIdInput.Builder, - GraphFilters.Builder { + SeededGraphFilters.Builder { public Builder() { super(new GetElementsWithinSet()); } diff --git a/store-implementation/accumulo-store/src/test/java/uk/gov/gchq/gaffer/accumulostore/integration/delete/GetElementsWithinSetDeletedElementsIT.java b/store-implementation/accumulo-store/src/test/java/uk/gov/gchq/gaffer/accumulostore/integration/delete/GetElementsWithinSetDeletedElementsIT.java index 878ebfeacc4..24469883650 100644 --- a/store-implementation/accumulo-store/src/test/java/uk/gov/gchq/gaffer/accumulostore/integration/delete/GetElementsWithinSetDeletedElementsIT.java +++ b/store-implementation/accumulo-store/src/test/java/uk/gov/gchq/gaffer/accumulostore/integration/delete/GetElementsWithinSetDeletedElementsIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 Crown Copyright + * Copyright 2017-2024 Crown Copyright * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import uk.gov.gchq.gaffer.accumulostore.operation.impl.GetElementsWithinSet; import uk.gov.gchq.gaffer.data.element.Element; +import uk.gov.gchq.gaffer.operation.graph.SeededGraphFilters.IncludeIncomingOutgoingType; public class GetElementsWithinSetDeletedElementsIT extends AbstractDeletedElementsIT> { @@ -25,6 +26,7 @@ public class GetElementsWithinSetDeletedElementsIT extends AbstractDeletedElemen protected GetElementsWithinSet createGetOperation() { return new GetElementsWithinSet.Builder() .input((Object[]) VERTICES) + .inOutType(IncludeIncomingOutgoingType.OUTGOING) .build(); } } diff --git a/store-implementation/accumulo-store/src/test/java/uk/gov/gchq/gaffer/accumulostore/operation/handler/GetElementsWithinSetHandlerTest.java b/store-implementation/accumulo-store/src/test/java/uk/gov/gchq/gaffer/accumulostore/operation/handler/GetElementsWithinSetHandlerTest.java index 7fb10bcf8df..387d539b6f6 100644 --- a/store-implementation/accumulo-store/src/test/java/uk/gov/gchq/gaffer/accumulostore/operation/handler/GetElementsWithinSetHandlerTest.java +++ b/store-implementation/accumulo-store/src/test/java/uk/gov/gchq/gaffer/accumulostore/operation/handler/GetElementsWithinSetHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 Crown Copyright + * Copyright 2016-2024 Crown Copyright * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,7 @@ import uk.gov.gchq.gaffer.data.elementdefinition.view.ViewElementDefinition; import uk.gov.gchq.gaffer.operation.OperationException; import uk.gov.gchq.gaffer.operation.data.EntitySeed; +import uk.gov.gchq.gaffer.operation.graph.SeededGraphFilters.IncludeIncomingOutgoingType; import uk.gov.gchq.gaffer.operation.impl.add.AddElements; import uk.gov.gchq.gaffer.store.Context; import uk.gov.gchq.gaffer.store.StoreException; @@ -69,18 +70,21 @@ public class GetElementsWithinSetHandlerTest { .source("A0") .dest("A23") .directed(true) + .matchedVertex(EdgeId.MatchedVertex.SOURCE) .build(); private static Edge expectedEdge2 = new Edge.Builder() .group(TestGroups.EDGE) .source("A0") .dest("A23") .directed(true) + .matchedVertex(EdgeId.MatchedVertex.SOURCE) .build(); private static Edge expectedEdge3 = new Edge.Builder() .group(TestGroups.EDGE) .source("A0") .dest("A23") .directed(true) + .matchedVertex(EdgeId.MatchedVertex.SOURCE) .build(); private static Entity expectedEntity1 = new Entity.Builder() .group(TestGroups.ENTITY) @@ -101,12 +105,14 @@ public class GetElementsWithinSetHandlerTest { .source("A0") .dest("A23") .directed(true) + .matchedVertex(EdgeId.MatchedVertex.SOURCE) .build(); private static Edge expectedSummarisedEdgePropertiesFiltered = new Edge.Builder() .group(TestGroups.EDGE) .source("A0") .dest("A23") .directed(true) + .matchedVertex(EdgeId.MatchedVertex.SOURCE) .property(AccumuloPropertyNames.COUNT, 23 * 3) .build(); @@ -160,12 +166,12 @@ public void reInitialise() throws StoreException, OperationException, TableExist } @Test - public void shouldReturnElementsNoSummarisationByteEntityStore() throws OperationException { + void shouldReturnElementsNoSummarisationByteEntityStore() throws OperationException { shouldReturnElementsNoSummarisation(BYTE_ENTITY_STORE); } @Test - public void shouldReturnElementsNoSummarisationGaffer1Store() throws OperationException { + void shouldReturnElementsNoSummarisationGaffer1Store() throws OperationException { shouldReturnElementsNoSummarisation(GAFFER_1_KEY_STORE); } @@ -187,7 +193,7 @@ private void shouldReturnElementsNoSummarisation(final AccumuloStore store) thro } @Test - public void shouldSummariseByteEntityStore() throws OperationException { + void shouldSummariseByteEntityStore() throws OperationException { final View view = new View.Builder(defaultView) .entity(TestGroups.ENTITY, new ViewElementDefinition.Builder() .groupBy() @@ -200,11 +206,11 @@ public void shouldSummariseByteEntityStore() throws OperationException { .build()) .build(); - runTest(BYTE_ENTITY_STORE, view, expectedSummarisedEdge, expectedEntity1, expectedEntity2); + runTest(BYTE_ENTITY_STORE, view, IncludeIncomingOutgoingType.OUTGOING, expectedSummarisedEdge, expectedEntity1, expectedEntity2); } @Test - public void shouldSummariseGaffer1Store() throws OperationException { + void shouldSummariseGaffer1Store() throws OperationException { final View view = new View.Builder(defaultView) .entity(TestGroups.ENTITY, new ViewElementDefinition.Builder() .groupBy() @@ -217,11 +223,11 @@ public void shouldSummariseGaffer1Store() throws OperationException { .build()) .build(); - runTest(GAFFER_1_KEY_STORE, view, expectedSummarisedEdge, expectedEntity1, expectedEntity2); + runTest(GAFFER_1_KEY_STORE, view, IncludeIncomingOutgoingType.OUTGOING, expectedSummarisedEdge, expectedEntity1, expectedEntity2); } @Test - public void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesByteEntityStore() throws OperationException { + void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesByteEntityStore() throws OperationException { final View view = new View.Builder() .edge(TestGroups.EDGE, new ViewElementDefinition.Builder() .groupBy() @@ -231,11 +237,11 @@ public void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesByteEntityStore() thr .build()) .build(); - runTest(BYTE_ENTITY_STORE, view, expectedSummarisedEdge); + runTest(BYTE_ENTITY_STORE, view, IncludeIncomingOutgoingType.OUTGOING, expectedSummarisedEdge); } @Test - public void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesGaffer1Store() throws OperationException { + void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesGaffer1Store() throws OperationException { final View view = new View.Builder() .edge(TestGroups.EDGE, new ViewElementDefinition.Builder() .groupBy() @@ -245,11 +251,11 @@ public void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesGaffer1Store() throws .build()) .build(); - runTest(GAFFER_1_KEY_STORE, view, expectedSummarisedEdge); + runTest(GAFFER_1_KEY_STORE, view, IncludeIncomingOutgoingType.OUTGOING, expectedSummarisedEdge); } @Test - public void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesPropertiesFilteredByteEntityStore() throws OperationException { + void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesPropertiesFilteredByteEntityStore() throws OperationException { final View view = new View.Builder() .edge(TestGroups.EDGE, new ViewElementDefinition.Builder() .properties(Collections.singleton(AccumuloPropertyNames.COUNT)) @@ -261,11 +267,11 @@ public void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesPropertiesFilteredByt .build()) .build(); - runTest(BYTE_ENTITY_STORE, view, expectedSummarisedEdgePropertiesFiltered); + runTest(BYTE_ENTITY_STORE, view, IncludeIncomingOutgoingType.OUTGOING, expectedSummarisedEdgePropertiesFiltered); } @Test - public void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesPropertiesFilteredGaffer1Store() throws OperationException { + void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesPropertiesFilteredGaffer1Store() throws OperationException { final View view = new View.Builder() .edge(TestGroups.EDGE, new ViewElementDefinition.Builder() .properties(Collections.singleton(AccumuloPropertyNames.COUNT)) @@ -277,11 +283,11 @@ public void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesPropertiesFilteredGaf .build()) .build(); - runTest(GAFFER_1_KEY_STORE, view, expectedSummarisedEdgePropertiesFiltered); + runTest(GAFFER_1_KEY_STORE, view, IncludeIncomingOutgoingType.OUTGOING, expectedSummarisedEdgePropertiesFiltered); } @Test - public void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesPropertiesEmptySetByteEntityStore() throws OperationException { + void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesPropertiesEmptySetByteEntityStore() throws OperationException { final View view = new View.Builder() .edge(TestGroups.EDGE, new ViewElementDefinition.Builder() .properties(Collections.emptySet()) @@ -293,11 +299,11 @@ public void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesPropertiesEmptySetByt .build()) .build(); - runTest(BYTE_ENTITY_STORE, view, expectedSummarisedEdgePropertiesEmptySet); + runTest(BYTE_ENTITY_STORE, view, IncludeIncomingOutgoingType.OUTGOING, expectedSummarisedEdgePropertiesEmptySet); } @Test - public void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesPropertiesEmptySetGaffer1Store() throws OperationException { + void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesPropertiesEmptySetGaffer1Store() throws OperationException { final View view = new View.Builder() .edge(TestGroups.EDGE, new ViewElementDefinition.Builder() .properties(Collections.emptySet()) @@ -309,33 +315,152 @@ public void shouldReturnOnlyEdgesWhenViewContainsNoEntitiesPropertiesEmptySetGaf .build()) .build(); - runTest(GAFFER_1_KEY_STORE, view, expectedSummarisedEdgePropertiesEmptySet); + runTest(GAFFER_1_KEY_STORE, view, IncludeIncomingOutgoingType.OUTGOING, expectedSummarisedEdgePropertiesEmptySet); } @Test - public void shouldReturnOnlyEntitiesWhenViewContainsNoEdgesByteEntityStore() throws OperationException { + void shouldReturnOnlyEntitiesWhenViewContainsNoEdgesByteEntityStore() throws OperationException { final View view = new View.Builder() .entity(TestGroups.ENTITY, new ViewElementDefinition.Builder() .groupBy() .build()) .build(); - runTest(BYTE_ENTITY_STORE, view, expectedEntity1, expectedEntity2); + runTest(BYTE_ENTITY_STORE, view, IncludeIncomingOutgoingType.OUTGOING, expectedEntity1, expectedEntity2); } @Test - public void shouldReturnOnlyEntitiesWhenViewContainsNoEdgesGaffer1Store() throws OperationException { + void shouldReturnOnlyEntitiesWhenViewContainsNoEdgesGaffer1Store() throws OperationException { final View view = new View.Builder() .entity(TestGroups.ENTITY, new ViewElementDefinition.Builder() .groupBy() .build()) .build(); - runTest(GAFFER_1_KEY_STORE, view, expectedEntity1, expectedEntity2); + runTest(GAFFER_1_KEY_STORE, view, IncludeIncomingOutgoingType.OUTGOING, expectedEntity1, expectedEntity2); } - private void runTest(final AccumuloStore store, final View view, final Element... expectedElements) throws OperationException { + @Test + void shouldGetIncomingEdgesOnlyGaffer1Store() throws OperationException { + final View view = new View.Builder() + .edge(TestGroups.EDGE, new ViewElementDefinition.Builder() + .properties(Collections.emptySet()) + .groupBy() + .build()) + .build(); + + // Incoming edge should have matched vertex DESTINATION + final Edge expectedEdge = new Edge.Builder() + .group(TestGroups.EDGE) + .source("A0") + .dest("A23") + .directed(true) + .matchedVertex(EdgeId.MatchedVertex.DESTINATION) + .build(); + + runTest(GAFFER_1_KEY_STORE, view, IncludeIncomingOutgoingType.INCOMING, expectedEdge); + } + + @Test + void shouldGetIncomingEdgesOnlyByteEntityStore() throws OperationException { + final View view = new View.Builder() + .edge(TestGroups.EDGE, new ViewElementDefinition.Builder() + .properties(Collections.emptySet()) + .groupBy() + .build()) + .build(); + + // Incoming edge should have matched vertex DESTINATION + final Edge expectedEdge = new Edge.Builder() + .group(TestGroups.EDGE) + .source("A0") + .dest("A23") + .directed(true) + .matchedVertex(EdgeId.MatchedVertex.DESTINATION) + .build(); + + runTest(BYTE_ENTITY_STORE, view, IncludeIncomingOutgoingType.INCOMING, expectedEdge); + } + + @Test + void shouldGetEdgesWhenDestAndSourceAreInDifferentBatchesGaffer1Store() throws OperationException { + // Given + // Set batch scanner entries to 20 - so edge A99 -> A1 will have its src in the final batch but its + // dest in the first + GAFFER_1_KEY_STORE.getProperties().setMaxEntriesForBatchScanner("20"); + final Set seedSet = new HashSet<>(); + for (int i = 1; i < 100; i++) { + seedSet.add(new EntitySeed("A" + i)); + } + + // When + final GetElementsWithinSet op = new GetElementsWithinSet.Builder() + .view(new View.Builder() + .edge(TestGroups.EDGE, new ViewElementDefinition.Builder() + .properties(Collections.emptySet()) + .groupBy() + .build()) + .build()) + .input(seedSet) + .build(); + final GetElementsWithinSetHandler handler = new GetElementsWithinSetHandler(); + + final Iterable results = handler.doOperation(op, user, GAFFER_1_KEY_STORE); + + // Then + final Edge expectedEdge = new Edge.Builder() + .group(TestGroups.EDGE) + .source("A99") + .dest("A1") + .directed(true) + .build(); + + assertThat(results) + .asInstanceOf(InstanceOfAssertFactories.iterable(Element.class)) + .contains(expectedEdge); + } + + @Test + void shouldGetEdgesWhenDestAndSourceAreInDifferentBatchesByteEntityStore() throws OperationException { + // Given + // Set batch scanner entries to 20 - so edge A99 -> A1 will have its src in the final batch but its + // dest in the first + BYTE_ENTITY_STORE.getProperties().setMaxEntriesForBatchScanner("20"); + final Set seedSet = new HashSet<>(); + for (int i = 1; i < 100; i++) { + seedSet.add(new EntitySeed("A" + i)); + } + + // When + final GetElementsWithinSet op = new GetElementsWithinSet.Builder() + .view(new View.Builder() + .edge(TestGroups.EDGE, new ViewElementDefinition.Builder() + .properties(Collections.emptySet()) + .groupBy() + .build()) + .build()) + .input(seedSet) + .build(); + final GetElementsWithinSetHandler handler = new GetElementsWithinSetHandler(); + + final Iterable results = handler.doOperation(op, user, GAFFER_1_KEY_STORE); + + // Then + final Edge expectedEdge = new Edge.Builder() + .group(TestGroups.EDGE) + .source("A99") + .dest("A1") + .directed(true) + .build(); + + assertThat(results) + .asInstanceOf(InstanceOfAssertFactories.iterable(Element.class)) + .contains(expectedEdge); + } + + private void runTest(final AccumuloStore store, final View view, final IncludeIncomingOutgoingType type, final Element... expectedElements) throws OperationException { final GetElementsWithinSet operation = new GetElementsWithinSet.Builder().view(view).input(seeds).build(); + operation.setIncludeIncomingOutGoing(type); final GetElementsWithinSetHandler handler = new GetElementsWithinSetHandler(); final Iterable elements = handler.doOperation(operation, user, store); @@ -398,6 +523,14 @@ private static void setupGraph(final AccumuloStore store) throws OperationExcept .property(AccumuloPropertyNames.PROP_4, 0) .build()); + data.add(new Edge.Builder() + .group(TestGroups.EDGE) + .source("A99") + .dest("A1") + .directed(true) + .property(AccumuloPropertyNames.COLUMN_QUALIFIER, 1) + .build()); + data.add(new Entity.Builder() .group(TestGroups.ENTITY) .vertex("A" + i) diff --git a/store-implementation/accumulo-store/src/test/java/uk/gov/gchq/gaffer/accumulostore/operation/impl/GetElementsWithinSetTest.java b/store-implementation/accumulo-store/src/test/java/uk/gov/gchq/gaffer/accumulostore/operation/impl/GetElementsWithinSetTest.java index bfe11a36df4..2e130cfa29d 100644 --- a/store-implementation/accumulo-store/src/test/java/uk/gov/gchq/gaffer/accumulostore/operation/impl/GetElementsWithinSetTest.java +++ b/store-implementation/accumulo-store/src/test/java/uk/gov/gchq/gaffer/accumulostore/operation/impl/GetElementsWithinSetTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 Crown Copyright + * Copyright 2016-2024 Crown Copyright * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,18 +25,16 @@ import uk.gov.gchq.gaffer.exception.SerialisationException; import uk.gov.gchq.gaffer.jsonserialisation.JSONSerialiser; import uk.gov.gchq.gaffer.operation.OperationTest; +import uk.gov.gchq.gaffer.operation.graph.SeededGraphFilters; import java.util.Iterator; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNotSame; -public class GetElementsWithinSetTest extends OperationTest { +class GetElementsWithinSetTest extends OperationTest { @Test - public void shouldJSONSerialiseAndDeserialise() throws SerialisationException { + void shouldJSONSerialiseAndDeserialise() throws SerialisationException { // Given final GetElementsWithinSet op = new GetElementsWithinSet.Builder() .input(AccumuloTestData.SEED_SOURCE_1, @@ -65,15 +63,18 @@ public void builderShouldCreatePopulatedOperation() { final GetElementsWithinSet getElementsWithinSet = new GetElementsWithinSet.Builder() .input(AccumuloTestData.SEED_A) .directedType(DirectedType.DIRECTED) + .inOutType(SeededGraphFilters.IncludeIncomingOutgoingType.INCOMING) .option(AccumuloTestData.TEST_OPTION_PROPERTY_KEY, "true") .view(new View.Builder() .edge("testEdgegroup") .build()) .build(); - assertEquals("true", getElementsWithinSet.getOption(AccumuloTestData.TEST_OPTION_PROPERTY_KEY)); - assertEquals(DirectedType.DIRECTED, getElementsWithinSet.getDirectedType()); + + assertThat(getElementsWithinSet.getOption(AccumuloTestData.TEST_OPTION_PROPERTY_KEY)).isEqualTo("true"); + assertThat(getElementsWithinSet.getDirectedType()).isEqualTo(DirectedType.DIRECTED); + assertThat(getElementsWithinSet.getIncludeIncomingOutGoing()).isEqualTo(SeededGraphFilters.IncludeIncomingOutgoingType.INCOMING); assertThat(getElementsWithinSet.getInput().iterator().next()).isEqualTo(AccumuloTestData.SEED_A); - assertNotNull(getElementsWithinSet.getView()); + assertThat(getElementsWithinSet.getView()).isNotNull(); } @Test @@ -86,6 +87,7 @@ public void shouldShallowCloneOperation() { final GetElementsWithinSet getElementsWithinSet = new GetElementsWithinSet.Builder() .input(AccumuloTestData.SEED_A) .directedType(DirectedType.DIRECTED) + .inOutType(SeededGraphFilters.IncludeIncomingOutgoingType.INCOMING) .option(AccumuloTestData.TEST_OPTION_PROPERTY_KEY, "true") .view(view) .build(); @@ -94,25 +96,22 @@ public void shouldShallowCloneOperation() { final GetElementsWithinSet clone = getElementsWithinSet.shallowClone(); // Then - assertNotSame(getElementsWithinSet, clone); - assertEquals("true", clone.getOption(AccumuloTestData.TEST_OPTION_PROPERTY_KEY)); - assertEquals(DirectedType.DIRECTED, clone.getDirectedType()); + assertThat(clone).isNotSameAs(getElementsWithinSet); + assertThat(clone.getOption(AccumuloTestData.TEST_OPTION_PROPERTY_KEY)).isEqualTo("true"); + assertThat(clone.getIncludeIncomingOutGoing()).isEqualTo(SeededGraphFilters.IncludeIncomingOutgoingType.INCOMING); assertThat(clone.getInput().iterator().next()).isEqualTo(AccumuloTestData.SEED_A); - assertEquals(view, clone.getView()); + assertThat(clone.getView()).isEqualTo(view); } @Test - public void shouldCreateInputFromVertices() { + void shouldCreateInputFromVertices() { // When final GetElementsWithinSet op = new GetElementsWithinSet.Builder() .input(AccumuloTestData.SEED_B, AccumuloTestData.SEED_B1.getVertex()) .build(); // Then - assertEquals( - Lists.newArrayList(AccumuloTestData.SEED_B, AccumuloTestData.SEED_B1), - Lists.newArrayList(op.getInput()) - ); + assertThat(Lists.newArrayList(op.getInput())).isEqualTo(Lists.newArrayList(AccumuloTestData.SEED_B, AccumuloTestData.SEED_B1)); } @Override diff --git a/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/FederatedStoreGetElementsWithinSetTest.java b/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/FederatedStoreGetElementsWithinSetTest.java new file mode 100644 index 00000000000..f0ec586b3db --- /dev/null +++ b/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/FederatedStoreGetElementsWithinSetTest.java @@ -0,0 +1,273 @@ +/* + * Copyright 2024 Crown Copyright + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.gchq.gaffer.federatedstore; + +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import uk.gov.gchq.gaffer.accumulostore.AccumuloProperties; +import uk.gov.gchq.gaffer.accumulostore.operation.impl.GetElementsWithinSet; +import uk.gov.gchq.gaffer.commonutil.StreamUtil; +import uk.gov.gchq.gaffer.data.element.Edge; +import uk.gov.gchq.gaffer.data.element.Element; +import uk.gov.gchq.gaffer.data.element.Entity; +import uk.gov.gchq.gaffer.data.element.id.EntityId; +import uk.gov.gchq.gaffer.data.elementdefinition.view.View; +import uk.gov.gchq.gaffer.federatedstore.operation.AddGraph; +import uk.gov.gchq.gaffer.federatedstore.operation.FederatedOperation; +import uk.gov.gchq.gaffer.graph.Graph; +import uk.gov.gchq.gaffer.graph.GraphConfig; +import uk.gov.gchq.gaffer.operation.OperationException; +import uk.gov.gchq.gaffer.operation.data.EntitySeed; +import uk.gov.gchq.gaffer.operation.impl.add.AddElements; +import uk.gov.gchq.gaffer.store.schema.Schema; +import uk.gov.gchq.gaffer.store.schema.SchemaEdgeDefinition; +import uk.gov.gchq.gaffer.store.schema.SchemaEntityDefinition; +import uk.gov.gchq.gaffer.user.User; + +import static org.assertj.core.api.Assertions.assertThat; +import static uk.gov.gchq.gaffer.commonutil.TestPropertyNames.STRING; +import static uk.gov.gchq.gaffer.federatedstore.FederatedStoreTestUtil.GRAPH_ID_A; +import static uk.gov.gchq.gaffer.federatedstore.FederatedStoreTestUtil.GRAPH_ID_B; +import static uk.gov.gchq.gaffer.federatedstore.FederatedStoreTestUtil.GRAPH_ID_TEST_FEDERATED_STORE; +import static uk.gov.gchq.gaffer.federatedstore.FederatedStoreTestUtil.GROUP_BASIC_EDGE; +import static uk.gov.gchq.gaffer.federatedstore.FederatedStoreTestUtil.GROUP_BASIC_ENTITY; +import static uk.gov.gchq.gaffer.federatedstore.FederatedStoreTestUtil.getFederatedStorePropertiesWithHashMapCache; +import static uk.gov.gchq.gaffer.federatedstore.FederatedStoreTestUtil.resetForFederatedTests; +import static uk.gov.gchq.gaffer.store.TestTypes.BOOLEAN_TYPE; +import static uk.gov.gchq.gaffer.store.TestTypes.DIRECTED_EITHER; +import static uk.gov.gchq.gaffer.store.TestTypes.STRING_TYPE; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +class FederatedStoreGetElementsWithinSetTest { + private static final AccumuloProperties ACCUMULO_PROPERTIES = AccumuloProperties.loadStoreProperties(StreamUtil.openStream(FederatedStoreGetElementsWithinSetTest.class, "properties/singleUseAccumuloStore.properties")); + private static final User USER = new User(); + private static final Set SEEDS = new LinkedHashSet<>(); + private static final String GRAPH_IDS = String.format("%s,%s", GRAPH_ID_A, GRAPH_ID_B); + private static final View EDGE_VIEW = new View.Builder().edge(GROUP_BASIC_EDGE).build(); + private static final View ENTITY_VIEW = new View.Builder().entity(GROUP_BASIC_ENTITY).build(); + + private Graph federatedGraph; + + @AfterAll + static void tearDownCache() { + resetForFederatedTests(); + } + + @BeforeEach + void setUp() throws Exception { + resetForFederatedTests(); + FederatedStoreProperties federatedStoreProperties = getFederatedStorePropertiesWithHashMapCache(); + for (int i = 0; i < 300; i++) { + SEEDS.add(new EntitySeed("A" + i)); + } + + federatedGraph = new Graph.Builder() + .config(new GraphConfig.Builder() + .graphId(GRAPH_ID_TEST_FEDERATED_STORE) + .build()) + .addStoreProperties(federatedStoreProperties) + .build(); + + federatedGraph.execute(new AddGraph.Builder() + .graphId(GRAPH_ID_A) + .storeProperties(ACCUMULO_PROPERTIES) + .schema(getSchema()) + .build(), USER); + + federatedGraph.execute(new AddGraph.Builder() + .graphId(GRAPH_ID_B) + .storeProperties(ACCUMULO_PROPERTIES) + .schema(getSchema()) + .build(), USER); + + for (int i = 300; i >= 0; i--) { + addEntities("A" + i, GRAPH_ID_A); + addEntities("A" + i, GRAPH_ID_B); + } + + // Add 6 edges total - should all have src and dest in different batches + addEdges("A244", "A87", GRAPH_ID_A); + addEdges("A168", "A110", GRAPH_ID_A); + addEdges("A56", "A299", GRAPH_ID_A); + addEdges("A297", "A193", GRAPH_ID_B); + addEdges("A15", "A285", GRAPH_ID_B); + addEdges("A1", "A52", GRAPH_ID_B); + } + + @Test + void shouldReturnOnlyEdgesWhenViewContainsNoEntities() throws Exception { + // Given/When + final Iterable results = (Iterable) federatedGraph.execute(new FederatedOperation.Builder() + .op(new GetElementsWithinSet.Builder() + .view(EDGE_VIEW) + .input(SEEDS) + .build()) + .graphIdsCSV(GRAPH_IDS) + .build(), USER); + + // Then + assertThat(results) + .asInstanceOf(InstanceOfAssertFactories.iterable(Element.class)) + .containsAll(getExpectedEdges()); + } + + @Test + void shouldReturnOnlyEntitiesWhenViewContainsNoEdges() throws Exception { + // Given/When + final Iterable results = (Iterable) federatedGraph.execute(new FederatedOperation.Builder() + .op(new GetElementsWithinSet.Builder() + .view(ENTITY_VIEW) + .input(SEEDS) + .build()) + .graphIdsCSV(GRAPH_IDS) + .build(), USER); + + // Then + assertThat(results).hasSize(600); + assertThat(results).extracting(r -> r.getGroup()).contains(GROUP_BASIC_ENTITY); + } + + @Test + void shouldGetAllEdgesWithSmallerBatchSizeInAFederatedOperation() throws Exception { + // Given + // Set batch scanner entries to 50 - so some edges will have its src in the final batch but its + // dest in the first - should all still be retrieved + ACCUMULO_PROPERTIES.setMaxEntriesForBatchScanner("50"); + + // When + final Iterable results = (Iterable) federatedGraph.execute(new FederatedOperation.Builder() + .op(new GetElementsWithinSet.Builder() + .view(EDGE_VIEW) + .input(SEEDS) + .build()) + .graphIdsCSV(GRAPH_IDS) + .build(), USER); + + // Then + assertThat(results) + .asInstanceOf(InstanceOfAssertFactories.iterable(Element.class)) + .containsAll(getExpectedEdges()); + } + + @Test + void shouldGetAllEdgesWithSmallerBatchSizeStandardOperation() throws Exception { + // Given + // Set batch scanner entries to 50 - so some edges will have its src in the final batch but its + // dest in the first - should all still be retrieved + ACCUMULO_PROPERTIES.setMaxEntriesForBatchScanner("50"); + + // When + final GetElementsWithinSet op = new GetElementsWithinSet.Builder() + .view(EDGE_VIEW) + .input(SEEDS) + .build(); + final Iterable results = federatedGraph.execute(op, USER); + + // Then + assertThat(results) + .asInstanceOf(InstanceOfAssertFactories.iterable(Element.class)) + .containsAll(getExpectedEdges()); + } + + private void addEntities(final String source, final String graphId) throws OperationException { + federatedGraph.execute(new FederatedOperation.Builder() + .op(new AddElements.Builder() + .input(new Entity.Builder() + .group(GROUP_BASIC_ENTITY) + .vertex(source) + .build()) + .build()) + .graphIdsCSV(graphId) + .build(), USER); + } + + private void addEdges(final String source, final String dest, final String graphId) throws OperationException { + federatedGraph.execute(new FederatedOperation.Builder() + .op(new AddElements.Builder() + .input(new Edge.Builder() + .group(GROUP_BASIC_EDGE) + .source(source) + .dest(dest) + .directed(true) + .build()) + .build()) + .graphIdsCSV(graphId) + .build(), USER); + } + + private Set getExpectedEdges() { + final Set expectedEdges = new HashSet<>(); + expectedEdges.add(new Edge.Builder() + .group(GROUP_BASIC_EDGE) + .source("A244") + .dest("A87") + .directed(true) + .build()); + expectedEdges.add(new Edge.Builder() + .group(GROUP_BASIC_EDGE) + .source("A168") + .dest("A110") + .directed(true) + .build()); + expectedEdges.add(new Edge.Builder() + .group(GROUP_BASIC_EDGE) + .source("A56") + .dest("A299") + .directed(true) + .build()); + expectedEdges.add(new Edge.Builder() + .group(GROUP_BASIC_EDGE) + .source("A297") + .dest("A193") + .directed(true) + .build()); + expectedEdges.add(new Edge.Builder() + .group(GROUP_BASIC_EDGE) + .source("A15") + .dest("A285") + .directed(true) + .build()); + expectedEdges.add(new Edge.Builder() + .group(GROUP_BASIC_EDGE) + .source("A1") + .dest("A52") + .directed(true) + .build()); + return expectedEdges; + } + + private Schema getSchema() { + return new Schema.Builder() + .entity(GROUP_BASIC_ENTITY, new SchemaEntityDefinition.Builder() + .vertex(STRING) + .build()) + .edge(GROUP_BASIC_EDGE, new SchemaEdgeDefinition.Builder() + .source(STRING) + .destination(STRING) + .directed(DIRECTED_EITHER) + .build()) + .type(STRING, STRING_TYPE) + .type(DIRECTED_EITHER, BOOLEAN_TYPE) + .build(); + } +}