From 97b0c4c1192fe5580a7957c844acc8092b56c604 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sat, 16 Sep 2023 13:19:31 +0200 Subject: chore: remove VIATRA branding Rename VIATRA subprojects to Refinery Interpreter to avoid interfering with Eclipse Foundation trademarks. Uses refering to a specific (historical) version of VIATRA were kept to avoid ambiguity. --- .../store/query/interpreter/DiagonalQueryTest.java | 391 ++++++++++ .../query/interpreter/FunctionalQueryTest.java | 519 ++++++++++++++ .../query/interpreter/OrderedResultSetTest.java | 117 +++ .../store/query/interpreter/QueryTest.java | 794 +++++++++++++++++++++ .../query/interpreter/QueryTransactionTest.java | 370 ++++++++++ .../StronglyConnectedComponentsTest.java | 261 +++++++ .../interpreter/WeaklyConnectedComponentsTest.java | 188 +++++ .../internal/matcher/MatcherUtilsTest.java | 239 +++++++ .../query/interpreter/tests/QueryAssertions.java | 57 ++ .../query/interpreter/tests/QueryBackendHint.java | 27 + .../query/interpreter/tests/QueryEngineTest.java | 21 + .../tests/QueryEvaluationHintSource.java | 24 + 12 files changed, 3008 insertions(+) create mode 100644 subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/DiagonalQueryTest.java create mode 100644 subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/FunctionalQueryTest.java create mode 100644 subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/OrderedResultSetTest.java create mode 100644 subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTest.java create mode 100644 subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTransactionTest.java create mode 100644 subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/StronglyConnectedComponentsTest.java create mode 100644 subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/WeaklyConnectedComponentsTest.java create mode 100644 subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/internal/matcher/MatcherUtilsTest.java create mode 100644 subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryAssertions.java create mode 100644 subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryBackendHint.java create mode 100644 subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEngineTest.java create mode 100644 subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEvaluationHintSource.java (limited to 'subprojects/store-query-interpreter/src/test') diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/DiagonalQueryTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/DiagonalQueryTest.java new file mode 100644 index 00000000..76de8679 --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/DiagonalQueryTest.java @@ -0,0 +1,391 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.query.interpreter; + +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.ModelQueryAdapter; +import tools.refinery.store.query.dnf.Dnf; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.interpreter.tests.QueryEngineTest; +import tools.refinery.store.query.view.AnySymbolView; +import tools.refinery.store.query.view.FunctionView; +import tools.refinery.store.query.view.KeyOnlyView; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.tuple.Tuple; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static tools.refinery.store.query.literal.Literals.not; +import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM; +import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertNullableResults; +import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults; + +class DiagonalQueryTest { + private static final Symbol person = Symbol.of("Person", 1); + private static final Symbol friend = Symbol.of("friend", 2); + private static final Symbol symbol = Symbol.of("symbol", 4); + private static final Symbol intSymbol = Symbol.of("intSymbol", 4, Integer.class); + private static final AnySymbolView personView = new KeyOnlyView<>(person); + private static final AnySymbolView friendView = new KeyOnlyView<>(friend); + private static final AnySymbolView symbolView = new KeyOnlyView<>(symbol); + private static final FunctionView intSymbolView = new FunctionView<>(intSymbol); + + @QueryEngineTest + void inputKeyNegationTest(QueryEvaluationHint hint) { + var query = Query.of("Diagonal", (builder, p1) -> builder.clause(p2 -> List.of( + personView.call(p1), + not(symbolView.call(p1, p1, p2, p2)) + ))); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); + symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); + symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); + symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), true, + Tuple.of(2), true, + Tuple.of(3), false + ), queryResultSet); + } + + @QueryEngineTest + void subQueryNegationTest(QueryEvaluationHint hint) { + var subQuery = Query.of("SubQuery", (builder, p1, p2, p3, p4) -> builder + .clause( + personView.call(p1), + symbolView.call(p1, p2, p3, p4) + ) + .clause( + personView.call(p2), + symbolView.call(p1, p2, p3, p4) + )); + var query = Query.of("Diagonal", (builder, p1) -> builder.clause(p2 -> List.of( + personView.call(p1), + not(subQuery.call(p1, p1, p2, p2)) + ))); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); + symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); + symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); + symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), true, + Tuple.of(2), true, + Tuple.of(3), false + ), queryResultSet); + } + + @QueryEngineTest + void inputKeyCountTest(QueryEvaluationHint hint) { + var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder.clause(p2 -> List.of( + personView.call(p1), + output.assign(symbolView.count(p1, p1, p2, p2)) + ))); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); + symbolInterpretation.put(Tuple.of(0, 0, 2, 2), true); + symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); + symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); + symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(2), + Tuple.of(1), Optional.of(0), + Tuple.of(2), Optional.of(0), + Tuple.of(3), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void subQueryCountTest(QueryEvaluationHint hint) { + var subQuery = Query.of("SubQuery", (builder, p1, p2, p3, p4) -> builder.clause( + personView.call(p1), + symbolView.call(p1, p2, p3, p4) + ) + .clause( + personView.call(p2), + symbolView.call(p1, p2, p3, p4) + )); + var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder.clause(p2 -> List.of( + personView.call(p1), + output.assign(subQuery.count(p1, p1, p2, p2)) + ))); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); + symbolInterpretation.put(Tuple.of(0, 0, 2, 2), true); + symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); + symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); + symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(2), + Tuple.of(1), Optional.of(0), + Tuple.of(2), Optional.of(0), + Tuple.of(3), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void inputKeyAggregationTest(QueryEvaluationHint hint) { + var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder + .clause((p2) -> List.of( + personView.call(p1), + output.assign(intSymbolView.aggregate(INT_SUM, p1, p1, p2, p2)) + ))); + + var store = ModelStore.builder() + .symbols(person, intSymbol) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var intSymbolInterpretation = model.getInterpretation(intSymbol); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + intSymbolInterpretation.put(Tuple.of(0, 0, 1, 1), 1); + intSymbolInterpretation.put(Tuple.of(0, 0, 2, 2), 2); + intSymbolInterpretation.put(Tuple.of(0, 0, 1, 2), 10); + intSymbolInterpretation.put(Tuple.of(1, 1, 0, 1), 11); + intSymbolInterpretation.put(Tuple.of(1, 2, 1, 1), 12); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(3), + Tuple.of(1), Optional.of(0), + Tuple.of(2), Optional.of(0), + Tuple.of(3), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void subQueryAggregationTest(QueryEvaluationHint hint) { + var subQuery = Dnf.of("SubQuery", builder -> { + var p1 = builder.parameter("p1"); + var p2 = builder.parameter("p2"); + var p3 = builder.parameter("p3"); + var p4 = builder.parameter("p4"); + var x = builder.parameter("x", Integer.class); + var y = builder.parameter("y", Integer.class); + builder.clause( + personView.call(p1), + intSymbolView.call(p1, p2, p3, p4, x), + y.assign(x) + ); + builder.clause( + personView.call(p2), + intSymbolView.call(p1, p2, p3, p4, x), + y.assign(x) + ); + }); + var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder + .clause(Integer.class, Integer.class, (p2, y, z) -> List.of( + personView.call(p1), + output.assign(subQuery.aggregateBy(y, INT_SUM, p1, p1, p2, p2, y, z)) + ))); + + var store = ModelStore.builder() + .symbols(person, intSymbol) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var intSymbolInterpretation = model.getInterpretation(intSymbol); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + intSymbolInterpretation.put(Tuple.of(0, 0, 1, 1), 1); + intSymbolInterpretation.put(Tuple.of(0, 0, 2, 2), 2); + intSymbolInterpretation.put(Tuple.of(0, 0, 1, 2), 10); + intSymbolInterpretation.put(Tuple.of(1, 1, 0, 1), 11); + intSymbolInterpretation.put(Tuple.of(1, 2, 1, 1), 12); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(3), + Tuple.of(1), Optional.of(0), + Tuple.of(2), Optional.of(0), + Tuple.of(3), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void inputKeyTransitiveTest(QueryEvaluationHint hint) { + var query = Query.of("Diagonal", (builder, p1) -> builder.clause( + personView.call(p1), + friendView.callTransitive(p1, p1) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 0), true); + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 2), true); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), false, + Tuple.of(2), false, + Tuple.of(3), false + ), queryResultSet); + } + + @QueryEngineTest + void subQueryTransitiveTest(QueryEvaluationHint hint) { + var subQuery = Query.of("SubQuery", (builder, p1, p2) -> builder + .clause( + personView.call(p1), + friendView.call(p1, p2) + ) + .clause( + personView.call(p2), + friendView.call(p1, p2) + )); + var query = Query.of("Diagonal", (builder, p1) -> builder.clause( + personView.call(p1), + subQuery.callTransitive(p1, p1) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 0), true); + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 2), true); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), false, + Tuple.of(2), false, + Tuple.of(3), false + ), queryResultSet); + } +} diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/FunctionalQueryTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/FunctionalQueryTest.java new file mode 100644 index 00000000..00721a34 --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/FunctionalQueryTest.java @@ -0,0 +1,519 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.query.interpreter; + +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import tools.refinery.store.map.Cursor; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.ModelQueryAdapter; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.interpreter.tests.QueryEngineTest; +import tools.refinery.store.query.view.AnySymbolView; +import tools.refinery.store.query.view.FilteredView; +import tools.refinery.store.query.view.FunctionView; +import tools.refinery.store.query.view.KeyOnlyView; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.representation.TruthValue; +import tools.refinery.store.tuple.Tuple; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static tools.refinery.store.query.literal.Literals.check; +import static tools.refinery.store.query.term.int_.IntTerms.*; +import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertNullableResults; +import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults; + +class FunctionalQueryTest { + private static final Symbol person = Symbol.of("Person", 1); + private static final Symbol age = Symbol.of("age", 1, Integer.class); + private static final Symbol friend = Symbol.of("friend", 2, TruthValue.class, TruthValue.FALSE); + private static final AnySymbolView personView = new KeyOnlyView<>(person); + private static final FunctionView ageView = new FunctionView<>(age); + private static final AnySymbolView friendMustView = new FilteredView<>(friend, "must", TruthValue::must); + + @QueryEngineTest + void inputKeyTest(QueryEvaluationHint hint) { + var query = Query.of("InputKey", Integer.class, (builder, p1, output) -> builder.clause( + personView.call(p1), + ageView.call(p1, output) + )); + + var store = ModelStore.builder() + .symbols(person, age) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + ageInterpretation.put(Tuple.of(0), 12); + ageInterpretation.put(Tuple.of(1), 24); + ageInterpretation.put(Tuple.of(2), 36); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(12), + Tuple.of(1), Optional.of(24), + Tuple.of(2), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void predicateTest(QueryEvaluationHint hint) { + var subQuery = Query.of("SubQuery", Integer.class, (builder, p1, x) -> builder.clause( + personView.call(p1), + ageView.call(p1, x) + )); + var query = Query.of("Predicate", Integer.class, (builder, p1, output) -> builder.clause( + personView.call(p1), + output.assign(subQuery.call(p1)) + )); + + var store = ModelStore.builder() + .symbols(person, age) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + ageInterpretation.put(Tuple.of(0), 12); + ageInterpretation.put(Tuple.of(1), 24); + ageInterpretation.put(Tuple.of(2), 36); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(12), + Tuple.of(1), Optional.of(24), + Tuple.of(2), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void computationTest(QueryEvaluationHint hint) { + var query = Query.of("Computation", Integer.class, (builder, p1, output) -> builder.clause(() -> { + var x = Variable.of("x", Integer.class); + return List.of( + personView.call(p1), + ageView.call(p1, x), + output.assign(mul(x, constant(7))) + ); + })); + + var store = ModelStore.builder() + .symbols(person, age) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + ageInterpretation.put(Tuple.of(0), 12); + ageInterpretation.put(Tuple.of(1), 24); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(84), + Tuple.of(1), Optional.of(168), + Tuple.of(2), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void inputKeyCountTest(QueryEvaluationHint hint) { + var query = Query.of("Count", Integer.class, (builder, p1, output) -> builder.clause( + personView.call(p1), + output.assign(friendMustView.count(p1, Variable.of())) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(1), + Tuple.of(1), Optional.of(2), + Tuple.of(2), Optional.of(0), + Tuple.of(3), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void predicateCountTest(QueryEvaluationHint hint) { + var subQuery = Query.of("SubQuery", (builder, p1, p2) -> builder.clause( + personView.call(p1), + personView.call(p2), + friendMustView.call(p1, p2) + )); + var query = Query.of("Count", Integer.class, (builder, p1, output) -> builder.clause( + personView.call(p1), + output.assign(subQuery.count(p1, Variable.of())) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(1), + Tuple.of(1), Optional.of(2), + Tuple.of(2), Optional.of(0), + Tuple.of(3), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void inputKeyAggregationTest(QueryEvaluationHint hint) { + var query = Query.of("Aggregate", Integer.class, (builder, output) -> builder.clause( + output.assign(ageView.aggregate(INT_SUM, Variable.of())) + )); + + var store = ModelStore.builder() + .symbols(age) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + ageInterpretation.put(Tuple.of(0), 12); + ageInterpretation.put(Tuple.of(1), 24); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(), 36), queryResultSet); + } + + @QueryEngineTest + void predicateAggregationTest(QueryEvaluationHint hint) { + var subQuery = Query.of("SubQuery", Integer.class, (builder, p1, x) -> builder.clause( + personView.call(p1), + ageView.call(p1, x) + )); + var query = Query.of("Aggregate", Integer.class, (builder, output) -> builder.clause( + output.assign(subQuery.aggregate(INT_SUM, Variable.of())) + )); + + var store = ModelStore.builder() + .symbols(person, age) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + ageInterpretation.put(Tuple.of(0), 12); + ageInterpretation.put(Tuple.of(1), 24); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(), 36), queryResultSet); + } + + @QueryEngineTest + void extremeValueTest(QueryEvaluationHint hint) { + var subQuery = Query.of("SubQuery", Integer.class, (builder, p1, x) -> builder.clause( + personView.call(p1), + x.assign(friendMustView.count(p1, Variable.of())) + )); + var minQuery = Query.of("Min", Integer.class, (builder, output) -> builder.clause( + output.assign(subQuery.aggregate(INT_MIN, Variable.of())) + )); + var maxQuery = Query.of("Max", Integer.class, (builder, output) -> builder.clause( + output.assign(subQuery.aggregate(INT_MAX, Variable.of())) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(minQuery, maxQuery)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var minResultSet = queryEngine.getResultSet(minQuery); + var maxResultSet = queryEngine.getResultSet(maxQuery); + + assertResults(Map.of(Tuple.of(), Integer.MAX_VALUE), minResultSet); + assertResults(Map.of(Tuple.of(), Integer.MIN_VALUE), maxResultSet); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(), 0), minResultSet); + assertResults(Map.of(Tuple.of(), 2), maxResultSet); + + friendInterpretation.put(Tuple.of(2, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(2, 1), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(), 1), minResultSet); + assertResults(Map.of(Tuple.of(), 2), maxResultSet); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.FALSE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.FALSE); + friendInterpretation.put(Tuple.of(2, 0), TruthValue.FALSE); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(), 0), minResultSet); + assertResults(Map.of(Tuple.of(), 1), maxResultSet); + } + + @QueryEngineTest + void invalidComputationTest(QueryEvaluationHint hint) { + var query = Query.of("InvalidComputation", Integer.class, + (builder, p1, output) -> builder.clause(Integer.class, (x) -> List.of( + personView.call(p1), + ageView.call(p1, x), + output.assign(div(constant(120), x)) + ))); + + var store = ModelStore.builder() + .symbols(person, age) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + ageInterpretation.put(Tuple.of(0), 0); + ageInterpretation.put(Tuple.of(1), 30); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.empty(), + Tuple.of(1), Optional.of(4), + Tuple.of(2), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void invalidAssumeTest(QueryEvaluationHint hint) { + var query = Query.of("InvalidAssume", (builder, p1) -> builder.clause(Integer.class, (x) -> List.of( + personView.call(p1), + ageView.call(p1, x), + check(lessEq(div(constant(120), x), constant(5))) + ))); + + var store = ModelStore.builder() + .symbols(person, age) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + ageInterpretation.put(Tuple.of(0), 0); + ageInterpretation.put(Tuple.of(1), 30); + ageInterpretation.put(Tuple.of(2), 20); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), queryResultSet); + } + + @QueryEngineTest + void multipleAssignmentTest(QueryEvaluationHint hint) { + var query = Query.of("MultipleAssignment", Integer.class, (builder, p1, p2, output) -> builder + .clause(Integer.class, Integer.class, (x1, x2) -> List.of( + ageView.call(p1, x1), + ageView.call(p2, x2), + output.assign(mul(x1, constant(2))), + output.assign(mul(x2, constant(3))) + ))); + + var store = ModelStore.builder() + .symbols(age) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + ageInterpretation.put(Tuple.of(0), 3); + ageInterpretation.put(Tuple.of(1), 2); + ageInterpretation.put(Tuple.of(2), 15); + ageInterpretation.put(Tuple.of(3), 10); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0, 1), Optional.of(6), + Tuple.of(1, 0), Optional.empty(), + Tuple.of(2, 3), Optional.of(30), + Tuple.of(3, 2), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void notFunctionalTest(QueryEvaluationHint hint) { + var query = Query.of("NotFunctional", Integer.class, (builder, p1, output) -> builder.clause((p2) -> List.of( + personView.call(p1), + friendMustView.call(p1, p2), + ageView.call(p2, output) + ))); + + var store = ModelStore.builder() + .symbols(person, age, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + ageInterpretation.put(Tuple.of(0), 24); + ageInterpretation.put(Tuple.of(1), 30); + ageInterpretation.put(Tuple.of(2), 36); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + var invalidTuple = Tuple.of(1); + var cursor = queryResultSet.getAll(); + assertAll( + () -> assertThat("value for key 0", queryResultSet.get(Tuple.of(0)), is(30)), + () -> assertThrows(IllegalStateException.class, () -> queryResultSet.get(invalidTuple), + "multiple values for key 1"), + () -> assertThat("value for key 2", queryResultSet.get(Tuple.of(2)), is(nullValue())), + () -> assertThat("value for key 3", queryResultSet.get(Tuple.of(3)), is(nullValue())) + ); + if (hint.getQueryBackendRequirementType() != QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH) { + // Local search doesn't support throwing an error on multiple function return values. + assertThat("results size", queryResultSet.size(), is(2)); + assertThrows(IllegalStateException.class, () -> enumerateValues(cursor), "move cursor"); + } + } + + private static void enumerateValues(Cursor cursor) { + //noinspection StatementWithEmptyBody + while (cursor.move()) { + // Nothing do, just let the cursor move through the result set. + } + } +} diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/OrderedResultSetTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/OrderedResultSetTest.java new file mode 100644 index 00000000..96d0f302 --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/OrderedResultSetTest.java @@ -0,0 +1,117 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.query.interpreter; + +import org.junit.jupiter.api.Test; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.ModelQueryAdapter; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.resultset.OrderedResultSet; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.view.AnySymbolView; +import tools.refinery.store.query.view.KeyOnlyView; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.tuple.Tuple; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +class OrderedResultSetTest { + private static final Symbol friend = Symbol.of("friend", 2); + private static final AnySymbolView friendView = new KeyOnlyView<>(friend); + + @Test + void relationalFlushTest() { + var query = Query.of("Relation", (builder, p1, p2) -> builder.clause( + friendView.call(p1, p2) + )); + + var store = ModelStore.builder() + .symbols(friend) + .with(QueryInterpreterAdapter.builder() + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var resultSet = queryEngine.getResultSet(query); + + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 2), true); + friendInterpretation.put(Tuple.of(1, 1), true); + queryEngine.flushChanges(); + + try (var orderedResultSet = new OrderedResultSet<>(resultSet)) { + assertThat(orderedResultSet.size(), is(3)); + assertThat(orderedResultSet.getKey(0), is(Tuple.of(0, 1))); + assertThat(orderedResultSet.getKey(1), is(Tuple.of(1, 1))); + assertThat(orderedResultSet.getKey(2), is(Tuple.of(1, 2))); + + friendInterpretation.put(Tuple.of(1, 2), false); + friendInterpretation.put(Tuple.of(0, 2), true); + queryEngine.flushChanges(); + + assertThat(orderedResultSet.size(), is(3)); + assertThat(orderedResultSet.getKey(0), is(Tuple.of(0, 1))); + assertThat(orderedResultSet.getKey(1), is(Tuple.of(0, 2))); + assertThat(orderedResultSet.getKey(2), is(Tuple.of(1, 1))); + } + } + + @Test + void functionalFlushTest() { + var query = Query.of("Function", Integer.class, (builder, p1, output) -> builder.clause( + friendView.call(p1, Variable.of()), + output.assign(friendView.count(p1, Variable.of())) + )); + + var store = ModelStore.builder() + .symbols(friend) + .with(QueryInterpreterAdapter.builder() + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var resultSet = queryEngine.getResultSet(query); + + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 2), true); + friendInterpretation.put(Tuple.of(1, 1), true); + queryEngine.flushChanges(); + + try (var orderedResultSet = new OrderedResultSet<>(resultSet)) { + assertThat(orderedResultSet.size(), is(2)); + assertThat(orderedResultSet.getKey(0), is(Tuple.of(0))); + assertThat(orderedResultSet.getKey(1), is(Tuple.of(1))); + + friendInterpretation.put(Tuple.of(0, 1), false); + friendInterpretation.put(Tuple.of(2, 1), true); + queryEngine.flushChanges(); + + assertThat(orderedResultSet.size(), is(2)); + assertThat(orderedResultSet.getKey(0), is(Tuple.of(1))); + assertThat(orderedResultSet.getKey(1), is(Tuple.of(2))); + + friendInterpretation.put(Tuple.of(1, 1), false); + queryEngine.flushChanges(); + + assertThat(orderedResultSet.size(), is(2)); + assertThat(orderedResultSet.getKey(0), is(Tuple.of(1))); + assertThat(orderedResultSet.getKey(1), is(Tuple.of(2))); + + friendInterpretation.put(Tuple.of(1, 2), false); + friendInterpretation.put(Tuple.of(1, 0), true); + queryEngine.flushChanges(); + + assertThat(orderedResultSet.size(), is(2)); + assertThat(orderedResultSet.getKey(0), is(Tuple.of(1))); + assertThat(orderedResultSet.getKey(1), is(Tuple.of(2))); + } + } +} diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTest.java new file mode 100644 index 00000000..72728dcd --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTest.java @@ -0,0 +1,794 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.query.interpreter; + +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import org.junit.jupiter.api.Test; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.ModelQueryAdapter; +import tools.refinery.store.query.dnf.Dnf; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.term.ParameterDirection; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.interpreter.tests.QueryEngineTest; +import tools.refinery.store.query.view.AnySymbolView; +import tools.refinery.store.query.view.FilteredView; +import tools.refinery.store.query.view.FunctionView; +import tools.refinery.store.query.view.KeyOnlyView; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.representation.TruthValue; +import tools.refinery.store.tuple.Tuple; + +import java.util.List; +import java.util.Map; + +import static tools.refinery.store.query.literal.Literals.check; +import static tools.refinery.store.query.literal.Literals.not; +import static tools.refinery.store.query.term.int_.IntTerms.constant; +import static tools.refinery.store.query.term.int_.IntTerms.greaterEq; +import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults; + +class QueryTest { + private static final Symbol person = Symbol.of("Person", 1); + private static final Symbol friend = Symbol.of("friend", 2, TruthValue.class, TruthValue.FALSE); + private static final AnySymbolView personView = new KeyOnlyView<>(person); + private static final AnySymbolView friendMustView = new FilteredView<>(friend, "must", TruthValue::must); + + @QueryEngineTest + void typeConstraintTest(QueryEvaluationHint hint) { + var asset = Symbol.of("Asset", 1); + + var predicate = Query.of("TypeConstraint", (builder, p1) -> builder.clause(personView.call(p1))); + + var store = ModelStore.builder() + .symbols(person, asset) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var assetInterpretation = model.getInterpretation(asset); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + assetInterpretation.put(Tuple.of(1), true); + assetInterpretation.put(Tuple.of(2), true); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false + ), predicateResultSet); + } + + @QueryEngineTest + void relationConstraintTest(QueryEvaluationHint hint) { + var predicate = Query.of("RelationConstraint", (builder, p1, p2) -> builder.clause( + personView.call(p1), + personView.call(p2), + friendMustView.call(p1, p2) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 3), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0, 1), true, + Tuple.of(1, 0), true, + Tuple.of(1, 2), true, + Tuple.of(2, 1), false + ), predicateResultSet); + } + + @QueryEngineTest + void isConstantTest(QueryEvaluationHint hint) { + var predicate = Query.of("RelationConstraint", (builder, p1, p2) -> builder.clause( + personView.call(p1), + p1.isConstant(1), + friendMustView.call(p1, p2) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0, 1), false, + Tuple.of(1, 0), true, + Tuple.of(1, 2), true, + Tuple.of(2, 1), false + ), predicateResultSet); + } + + @QueryEngineTest + void existTest(QueryEvaluationHint hint) { + var predicate = Query.of("Exists", (builder, p1) -> builder.clause((p2) -> List.of( + personView.call(p1), + personView.call(p2), + friendMustView.call(p1, p2) + ))); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(3, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + } + + @QueryEngineTest + void orTest(QueryEvaluationHint hint) { + var animal = Symbol.of("Animal", 1); + var animalView = new KeyOnlyView<>(animal); + + var predicate = Query.of("Or", (builder, p1, p2) -> builder.clause( + personView.call(p1), + personView.call(p2), + friendMustView.call(p1, p2) + ).clause( + animalView.call(p1), + animalView.call(p2), + friendMustView.call(p1, p2) + )); + + var store = ModelStore.builder() + .symbols(person, animal, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var animalInterpretation = model.getInterpretation(animal); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + animalInterpretation.put(Tuple.of(2), true); + animalInterpretation.put(Tuple.of(3), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(2, 3), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(3, 0), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0, 1), true, + Tuple.of(0, 2), false, + Tuple.of(2, 3), true, + Tuple.of(3, 0), false, + Tuple.of(3, 2), false + ), predicateResultSet); + } + + @QueryEngineTest + void equalityTest(QueryEvaluationHint hint) { + var predicate = Query.of("Equality", (builder, p1, p2) -> builder.clause( + personView.call(p1), + personView.call(p2), + p1.isEquivalent(p2) + )); + + var store = ModelStore.builder() + .symbols(person) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0, 0), true, + Tuple.of(1, 1), true, + Tuple.of(2, 2), true, + Tuple.of(0, 1), false, + Tuple.of(3, 3), false + ), predicateResultSet); + } + + @QueryEngineTest + void inequalityTest(QueryEvaluationHint hint) { + var predicate = Query.of("Inequality", (builder, p1, p2, p3) -> builder.clause( + personView.call(p1), + personView.call(p2), + friendMustView.call(p1, p3), + friendMustView.call(p2, p3), + p1.notEquivalent(p2) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0, 1, 2), true, + Tuple.of(1, 0, 2), true, + Tuple.of(0, 0, 2), false + ), predicateResultSet); + } + + @QueryEngineTest + void patternCallTest(QueryEvaluationHint hint) { + var friendPredicate = Query.of("Friend", (builder, p1, p2) -> builder.clause( + personView.call(p1), + personView.call(p2), + friendMustView.call(p1, p2) + )); + var predicate = Query.of("PositivePatternCall", (builder, p3, p4) -> builder.clause( + personView.call(p3), + personView.call(p4), + friendPredicate.call(p3, p4) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0, 1), true, + Tuple.of(1, 0), true, + Tuple.of(1, 2), true, + Tuple.of(2, 1), false + ), predicateResultSet); + } + + @QueryEngineTest + void patternCallInputArgumentTest(QueryEvaluationHint hint) { + var friendPredicate = Dnf.of("Friend", builder -> { + var p1 = builder.parameter("p1", ParameterDirection.IN); + var p2 = builder.parameter("p2", ParameterDirection.IN); + builder.clause( + personView.call(p1), + personView.call(p2), + friendMustView.call(p1, p2) + ); + }); + var predicate = Query.of("PositivePatternCall", (builder, p3, p4) -> builder.clause( + personView.call(p3), + personView.call(p4), + friendPredicate.call(p3, p4) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0, 1), true, + Tuple.of(1, 0), true, + Tuple.of(1, 2), true, + Tuple.of(2, 1), false + ), predicateResultSet); + } + + @QueryEngineTest + void negativeRelationViewTest(QueryEvaluationHint hint) { + var predicate = Query.of("NegativePatternCall", (builder, p1, p2) -> builder.clause( + personView.call(p1), + personView.call(p2), + not(friendMustView.call(p1, p2)) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0, 0), true, + Tuple.of(0, 2), true, + Tuple.of(1, 1), true, + Tuple.of(2, 0), true, + Tuple.of(2, 1), true, + Tuple.of(2, 2), true, + Tuple.of(0, 1), false, + Tuple.of(1, 0), false, + Tuple.of(1, 2), false, + Tuple.of(0, 3), false + ), predicateResultSet); + } + + @QueryEngineTest + void negativePatternCallTest(QueryEvaluationHint hint) { + var friendPredicate = Query.of("Friend", (builder, p1, p2) -> builder.clause( + personView.call(p1), + personView.call(p2), + friendMustView.call(p1, p2) + )); + var predicate = Query.of("NegativePatternCall", (builder, p3, p4) -> builder.clause( + personView.call(p3), + personView.call(p4), + not(friendPredicate.call(p3, p4)) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0, 0), true, + Tuple.of(0, 2), true, + Tuple.of(1, 1), true, + Tuple.of(2, 0), true, + Tuple.of(2, 1), true, + Tuple.of(2, 2), true, + Tuple.of(0, 1), false, + Tuple.of(1, 0), false, + Tuple.of(1, 2), false, + Tuple.of(0, 3), false + ), predicateResultSet); + } + + @QueryEngineTest + void negativeRelationViewWithQuantificationTest(QueryEvaluationHint hint) { + var predicate = Query.of("Negative", (builder, p1) -> builder.clause( + personView.call(p1), + not(friendMustView.call(p1, Variable.of())) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), true, + Tuple.of(2), true, + Tuple.of(3), false + ), predicateResultSet); + } + + @QueryEngineTest + void negativeWithQuantificationTest(QueryEvaluationHint hint) { + var called = Query.of("Called", (builder, p1, p2) -> builder.clause( + personView.call(p1), + personView.call(p2), + friendMustView.call(p1, p2) + )); + var predicate = Query.of("Negative", (builder, p1) -> builder.clause( + personView.call(p1), + not(called.call(p1, Variable.of())) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), true, + Tuple.of(2), true, + Tuple.of(3), false + ), predicateResultSet); + } + + @QueryEngineTest + void transitiveRelationViewTest(QueryEvaluationHint hint) { + var predicate = Query.of("Transitive", (builder, p1, p2) -> builder.clause( + personView.call(p1), + personView.call(p2), + friendMustView.callTransitive(p1, p2) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0, 0), false, + Tuple.of(0, 1), true, + Tuple.of(0, 2), true, + Tuple.of(1, 0), false, + Tuple.of(1, 1), false, + Tuple.of(1, 2), true, + Tuple.of(2, 0), false, + Tuple.of(2, 1), false, + Tuple.of(2, 2), false, + Tuple.of(2, 3), false + ), predicateResultSet); + } + + @QueryEngineTest + void transitivePatternCallTest(QueryEvaluationHint hint) { + var called = Query.of("Called", (builder, p1, p2) -> builder.clause( + personView.call(p1), + personView.call(p2), + friendMustView.call(p1, p2) + )); + var predicate = Query.of("Transitive", (builder, p1, p2) -> builder.clause( + personView.call(p1), + personView.call(p2), + called.callTransitive(p1, p2) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0, 0), false, + Tuple.of(0, 1), true, + Tuple.of(0, 2), true, + Tuple.of(1, 0), false, + Tuple.of(1, 1), false, + Tuple.of(1, 2), true, + Tuple.of(2, 0), false, + Tuple.of(2, 1), false, + Tuple.of(2, 2), false, + Tuple.of(2, 3), false + ), predicateResultSet); + } + + @Test + void filteredIntegerViewTest() { + var distance = Symbol.of("distance", 2, Integer.class); + var nearView = new FilteredView<>(distance, value -> value < 2); + var farView = new FilteredView<>(distance, value -> value >= 5); + var dangerQuery = Query.of("danger", (builder, a1, a2) -> builder.clause((a3) -> List.of( + a1.notEquivalent(a2), + nearView.call(a1, a3), + nearView.call(a2, a3), + not(farView.call(a1, a2)) + ))); + var store = ModelStore.builder() + .symbols(distance) + .with(QueryInterpreterAdapter.builder() + .queries(dangerQuery)) + .build(); + + var model = store.createEmptyModel(); + var distanceInterpretation = model.getInterpretation(distance); + distanceInterpretation.put(Tuple.of(0, 1), 1); + distanceInterpretation.put(Tuple.of(1, 0), 1); + distanceInterpretation.put(Tuple.of(0, 2), 1); + distanceInterpretation.put(Tuple.of(2, 0), 1); + distanceInterpretation.put(Tuple.of(1, 2), 3); + distanceInterpretation.put(Tuple.of(2, 1), 3); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var dangerResultSet = queryEngine.getResultSet(dangerQuery); + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0, 1), false, + Tuple.of(0, 2), false, + Tuple.of(1, 2), true, + Tuple.of(2, 1), true + ), dangerResultSet); + } + + @Test + void filteredDoubleViewTest() { + var distance = Symbol.of("distance", 2, Double.class); + var nearView = new FilteredView<>(distance, value -> value < 2); + var farView = new FilteredView<>(distance, value -> value >= 5); + var dangerQuery = Query.of("danger", (builder, a1, a2) -> builder.clause((a3) -> List.of( + a1.notEquivalent(a2), + nearView.call(a1, a3), + nearView.call(a2, a3), + not(farView.call(a1, a2)) + ))); + var store = ModelStore.builder() + .symbols(distance) + .with(QueryInterpreterAdapter.builder() + .queries(dangerQuery)) + .build(); + + var model = store.createEmptyModel(); + var distanceInterpretation = model.getInterpretation(distance); + distanceInterpretation.put(Tuple.of(0, 1), 1.0); + distanceInterpretation.put(Tuple.of(1, 0), 1.0); + distanceInterpretation.put(Tuple.of(0, 2), 1.0); + distanceInterpretation.put(Tuple.of(2, 0), 1.0); + distanceInterpretation.put(Tuple.of(1, 2), 3.0); + distanceInterpretation.put(Tuple.of(2, 1), 3.0); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var dangerResultSet = queryEngine.getResultSet(dangerQuery); + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0, 1), false, + Tuple.of(0, 2), false, + Tuple.of(1, 2), true, + Tuple.of(2, 1), true + ), dangerResultSet); + } + + @QueryEngineTest + void assumeTest(QueryEvaluationHint hint) { + var age = Symbol.of("age", 1, Integer.class); + var ageView = new FunctionView<>(age); + + var query = Query.of("Constraint", (builder, p1) -> builder.clause(Integer.class, (x) -> List.of( + personView.call(p1), + ageView.call(p1, x), + check(greaterEq(x, constant(18))) + ))); + + var store = ModelStore.builder() + .symbols(person, age) + .with(QueryInterpreterAdapter.builder() + .defaultHint(hint) + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + ageInterpretation.put(Tuple.of(0), 12); + ageInterpretation.put(Tuple.of(1), 24); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), true, + Tuple.of(2), false + ), queryResultSet); + } + + @Test + void alwaysFalseTest() { + var predicate = Query.of("AlwaysFalse", builder -> builder.parameter("p1")); + + var store = ModelStore.builder() + .symbols(person) + .with(QueryInterpreterAdapter.builder() + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + queryEngine.flushChanges(); + assertResults(Map.of(), predicateResultSet); + } +} diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTransactionTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTransactionTest.java new file mode 100644 index 00000000..1cd05d91 --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/QueryTransactionTest.java @@ -0,0 +1,370 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.query.interpreter; + +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import org.junit.jupiter.api.Test; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.ModelQueryAdapter; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.dnf.RelationalQuery; +import tools.refinery.store.query.view.AnySymbolView; +import tools.refinery.store.query.view.FilteredView; +import tools.refinery.store.query.view.FunctionView; +import tools.refinery.store.query.view.KeyOnlyView; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.tuple.Tuple; + +import java.util.Map; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertNullableResults; +import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults; + +class QueryTransactionTest { + private static final Symbol person = Symbol.of("Person", 1); + private static final Symbol age = Symbol.of("age", 1, Integer.class); + private static final AnySymbolView personView = new KeyOnlyView<>(person); + private static final AnySymbolView ageView = new FunctionView<>(age); + private static final RelationalQuery predicate = Query.of("TypeConstraint", (builder, p1) -> + builder.clause(personView.call(p1))); + + @Test + void flushTest() { + var store = ModelStore.builder() + .symbols(person) + .with(QueryInterpreterAdapter.builder() + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), false, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), false, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + assertTrue(queryEngine.hasPendingChanges()); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + + personInterpretation.put(Tuple.of(1), false); + personInterpretation.put(Tuple.of(2), true); + + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + assertTrue(queryEngine.hasPendingChanges()); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), false, + Tuple.of(2), true, + Tuple.of(3), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + } + + @Test + void localSearchTest() { + var store = ModelStore.builder() + .symbols(person) + .with(QueryInterpreterAdapter.builder() + .defaultHint(new QueryEvaluationHint(null, QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH)) + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), false, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + + personInterpretation.put(Tuple.of(1), false); + personInterpretation.put(Tuple.of(2), true); + + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), false, + Tuple.of(2), true, + Tuple.of(3), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + } + + @Test + void unrelatedChangesTest() { + var asset = Symbol.of("Asset", 1); + + var store = ModelStore.builder() + .symbols(person, asset) + .with(QueryInterpreterAdapter.builder() + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var assetInterpretation = model.getInterpretation(asset); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + assertFalse(queryEngine.hasPendingChanges()); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + assetInterpretation.put(Tuple.of(1), true); + assetInterpretation.put(Tuple.of(2), true); + + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), false, + Tuple.of(2), false, + Tuple.of(3), false, + Tuple.of(4), false + ), predicateResultSet); + assertTrue(queryEngine.hasPendingChanges()); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false, + Tuple.of(4), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + + assetInterpretation.put(Tuple.of(3), true); + assertFalse(queryEngine.hasPendingChanges()); + + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false, + Tuple.of(4), false + ), predicateResultSet); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false, + Tuple.of(4), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + } + + @Test + void tupleChangingChangeTest() { + var query = Query.of("TypeConstraint", Integer.class, (builder, p1, output) -> builder.clause( + personView.call(p1), + ageView.call(p1, output) + )); + + var store = ModelStore.builder() + .symbols(person, age) + .with(QueryInterpreterAdapter.builder() + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + + ageInterpretation.put(Tuple.of(0), 24); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(0), 24), queryResultSet); + + ageInterpretation.put(Tuple.of(0), 25); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(0), 25), queryResultSet); + + ageInterpretation.put(Tuple.of(0), null); + + queryEngine.flushChanges(); + assertNullableResults(Map.of(Tuple.of(0), Optional.empty()), queryResultSet); + } + + @Test + void tuplePreservingUnchangedTest() { + var adultView = new FilteredView<>(age, "adult", n -> n != null && n >= 18); + + var query = Query.of("TypeConstraint", (builder, p1) -> builder.clause( + personView.call(p1), + adultView.call(p1) + )); + + var store = ModelStore.builder() + .symbols(person, age) + .with(QueryInterpreterAdapter.builder() + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + + ageInterpretation.put(Tuple.of(0), 24); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(0), true), queryResultSet); + + ageInterpretation.put(Tuple.of(0), 25); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(0), true), queryResultSet); + + ageInterpretation.put(Tuple.of(0), 17); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(0), false), queryResultSet); + } + + @Test + void commitAfterFlushTest() { + var store = ModelStore.builder() + .symbols(person) + .with(QueryInterpreterAdapter.builder() + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + + var state1 = model.commit(); + + personInterpretation.put(Tuple.of(1), false); + personInterpretation.put(Tuple.of(2), true); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), false, + Tuple.of(2), true, + Tuple.of(3), false + ), predicateResultSet); + + model.restore(state1); + + assertFalse(queryEngine.hasPendingChanges()); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + } + + @Test + void commitWithoutFlushTest() { + var store = ModelStore.builder() + .symbols(person) + .with(QueryInterpreterAdapter.builder() + .queries(predicate)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + assertResults(Map.of(), predicateResultSet); + assertTrue(queryEngine.hasPendingChanges()); + + var state1 = model.commit(); + + personInterpretation.put(Tuple.of(1), false); + personInterpretation.put(Tuple.of(2), true); + + assertResults(Map.of(), predicateResultSet); + assertTrue(queryEngine.hasPendingChanges()); + + model.restore(state1); + + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + } +} diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/StronglyConnectedComponentsTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/StronglyConnectedComponentsTest.java new file mode 100644 index 00000000..edbd9aff --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/StronglyConnectedComponentsTest.java @@ -0,0 +1,261 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.query.interpreter; + +import org.junit.jupiter.api.Test; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.ModelQueryAdapter; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.literal.Connectivity; +import tools.refinery.store.query.literal.RepresentativeElectionLiteral; +import tools.refinery.store.query.view.AnySymbolView; +import tools.refinery.store.query.view.KeyOnlyView; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.tuple.Tuple; + +import java.util.List; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults; + +class StronglyConnectedComponentsTest { + private static final Symbol friend = Symbol.of("friend", 2); + private static final AnySymbolView friendView = new KeyOnlyView<>(friend); + + @Test + void symbolViewTest() { + var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder + .clause(v1 -> List.of( + new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p1, v1), + new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p2, v1) + ))); + + var store = ModelStore.builder() + .symbols(friend) + .with(QueryInterpreterAdapter.builder() + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var resultSet = queryEngine.getResultSet(query); + + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 0), true); + friendInterpretation.put(Tuple.of(1, 2), true); + queryEngine.flushChanges(); + + assertResults(Map.of( + Tuple.of(0, 0), true, + Tuple.of(0, 1), true, + Tuple.of(1, 0), true, + Tuple.of(1, 1), true, + Tuple.of(2, 2), true + ), resultSet); + } + + @Test + void symbolViewInsertTest() { + var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder + .clause(v1 -> List.of( + new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p1, v1), + new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p2, v1) + ))); + + var store = ModelStore.builder() + .symbols(friend) + .with(QueryInterpreterAdapter.builder() + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var resultSet = queryEngine.getResultSet(query); + + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 0), true); + friendInterpretation.put(Tuple.of(1, 2), true); + queryEngine.flushChanges(); + + friendInterpretation.put(Tuple.of(2, 0), true); + friendInterpretation.put(Tuple.of(0, 3), true); + queryEngine.flushChanges(); + + assertResults(Map.of( + Tuple.of(0, 0), true, + Tuple.of(0, 1), true, + Tuple.of(0, 2), true, + Tuple.of(1, 1), true, + Tuple.of(1, 0), true, + Tuple.of(1, 2), true, + Tuple.of(2, 0), true, + Tuple.of(2, 1), true, + Tuple.of(2, 2), true, + Tuple.of(3, 3), true + ), resultSet); + } + + @Test + void symbolViewDeleteTest() { + var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder + .clause(v1 -> List.of( + new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p1, v1), + new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p2, v1) + ))); + + var store = ModelStore.builder() + .symbols(friend) + .with(QueryInterpreterAdapter.builder() + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var resultSet = queryEngine.getResultSet(query); + + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 0), true); + friendInterpretation.put(Tuple.of(1, 2), true); + queryEngine.flushChanges(); + + friendInterpretation.put(Tuple.of(1, 0), false); + friendInterpretation.put(Tuple.of(1, 2), false); + queryEngine.flushChanges(); + + assertResults(Map.of( + Tuple.of(0, 0), true, + Tuple.of(1, 1), true + ), resultSet); + } + + @Test + void diagonalSymbolViewTest() { + var person = Symbol.of("Person", 1); + var personView = new KeyOnlyView<>(person); + + var query = Query.of("SymbolViewRepresentative", (builder, p1) -> builder + .clause( + personView.call(p1), + new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p1, p1) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var resultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 0), true); + friendInterpretation.put(Tuple.of(1, 2), true); + queryEngine.flushChanges(); + + assertThat(resultSet.size(), is(2)); + assertThat(resultSet.get(Tuple.of(2)), is(true)); + } + + @Test + void diagonalDnfTest() { + var person = Symbol.of("Person", 1); + var personView = new KeyOnlyView<>(person); + + var subQuery = Query.of("SubQuery", (builder, p1, p2) -> builder + .clause( + personView.call(p1), + personView.call(p2), + friendView.call(p1, p2) + )) + .getDnf(); + var query = Query.of("SymbolViewRepresentative", (builder, p1) -> builder + .clause( + personView.call(p1), + new RepresentativeElectionLiteral(Connectivity.STRONG, subQuery, p1, p1) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var resultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 0), true); + friendInterpretation.put(Tuple.of(1, 2), true); + queryEngine.flushChanges(); + + assertThat(resultSet.size(), is(2)); + assertThat(resultSet.get(Tuple.of(2)), is(true)); + } + + @Test + void loopTest() { + var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder + .clause(v1 -> List.of( + new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p1, v1), + new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p2, v1) + ))); + + var store = ModelStore.builder() + .symbols(friend) + .with(QueryInterpreterAdapter.builder() + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var resultSet = queryEngine.getResultSet(query); + + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 2), true); + friendInterpretation.put(Tuple.of(2, 3), true); + friendInterpretation.put(Tuple.of(3, 0), true); + friendInterpretation.put(Tuple.of(3, 4), true); + queryEngine.flushChanges(); + + assertThat(resultSet.get(Tuple.of(0, 1)), is(true)); + assertThat(resultSet.get(Tuple.of(1, 2)), is(true)); + assertThat(resultSet.get(Tuple.of(2, 3)), is(true)); + assertThat(resultSet.get(Tuple.of(3, 0)), is(true)); + assertThat(resultSet.get(Tuple.of(3, 4)), is(false)); + + friendInterpretation.put(Tuple.of(2, 3), false); + queryEngine.flushChanges(); + + assertThat(resultSet.get(Tuple.of(0, 1)), is(false)); + assertThat(resultSet.get(Tuple.of(0, 2)), is(false)); + assertThat(resultSet.get(Tuple.of(0, 3)), is(false)); + assertThat(resultSet.get(Tuple.of(1, 2)), is(false)); + assertThat(resultSet.get(Tuple.of(2, 3)), is(false)); + assertThat(resultSet.get(Tuple.of(3, 0)), is(false)); + assertThat(resultSet.get(Tuple.of(3, 4)), is(false)); + } +} diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/WeaklyConnectedComponentsTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/WeaklyConnectedComponentsTest.java new file mode 100644 index 00000000..3fc85480 --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/WeaklyConnectedComponentsTest.java @@ -0,0 +1,188 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.query.interpreter; + +import org.junit.jupiter.api.Test; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.ModelQueryAdapter; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.literal.Connectivity; +import tools.refinery.store.query.literal.RepresentativeElectionLiteral; +import tools.refinery.store.query.view.AnySymbolView; +import tools.refinery.store.query.view.KeyOnlyView; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.tuple.Tuple; + +import java.util.List; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static tools.refinery.store.query.interpreter.tests.QueryAssertions.assertResults; + +class WeaklyConnectedComponentsTest { + private static final Symbol friend = Symbol.of("friend", 2); + private static final AnySymbolView friendView = new KeyOnlyView<>(friend); + + @Test + void symbolViewTest() { + var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder + .clause(v1 -> List.of( + new RepresentativeElectionLiteral(Connectivity.WEAK, friendView, p1, v1), + new RepresentativeElectionLiteral(Connectivity.WEAK, friendView, p2, v1) + ))); + + var store = ModelStore.builder() + .symbols(friend) + .with(QueryInterpreterAdapter.builder() + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var resultSet = queryEngine.getResultSet(query); + + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 0), true); + friendInterpretation.put(Tuple.of(2, 3), true); + queryEngine.flushChanges(); + + assertResults(Map.of( + Tuple.of(0, 0), true, + Tuple.of(0, 1), true, + Tuple.of(1, 0), true, + Tuple.of(1, 1), true, + Tuple.of(2, 2), true, + Tuple.of(2, 3), true, + Tuple.of(3, 2), true, + Tuple.of(3, 3), true + ), resultSet); + } + + @Test + void symbolViewUpdateTest() { + var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder + .clause(v1 -> List.of( + new RepresentativeElectionLiteral(Connectivity.WEAK, friendView, p1, v1), + new RepresentativeElectionLiteral(Connectivity.WEAK, friendView, p2, v1) + ))); + + var store = ModelStore.builder() + .symbols(friend) + .with(QueryInterpreterAdapter.builder() + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var resultSet = queryEngine.getResultSet(query); + + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 0), true); + friendInterpretation.put(Tuple.of(2, 3), true); + queryEngine.flushChanges(); + + friendInterpretation.put(Tuple.of(2, 3), false); + friendInterpretation.put(Tuple.of(1, 0), false); + friendInterpretation.put(Tuple.of(1, 2), true); + queryEngine.flushChanges(); + + assertResults(Map.of( + Tuple.of(0, 0), true, + Tuple.of(0, 1), true, + Tuple.of(0, 2), true, + Tuple.of(1, 0), true, + Tuple.of(1, 1), true, + Tuple.of(1, 2), true, + Tuple.of(2, 0), true, + Tuple.of(2, 1), true, + Tuple.of(2, 2), true + ), resultSet); + } + + @Test + void diagonalSymbolViewTest() { + var person = Symbol.of("Person", 1); + var personView = new KeyOnlyView<>(person); + + var query = Query.of("SymbolViewRepresentative", (builder, p1) -> builder + .clause( + personView.call(p1), + new RepresentativeElectionLiteral(Connectivity.WEAK, friendView, p1, p1) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var resultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + personInterpretation.put(Tuple.of(3), true); + + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 0), true); + friendInterpretation.put(Tuple.of(2, 3), true); + queryEngine.flushChanges(); + + assertThat(resultSet.size(), is(2)); + assertThat(resultSet.get(Tuple.of(2)), is(true)); + } + + @Test + void diagonalDnfTest() { + var person = Symbol.of("Person", 1); + var personView = new KeyOnlyView<>(person); + + var subQuery = Query.of("SubQuery", (builder, p1, p2) -> builder + .clause( + personView.call(p1), + personView.call(p2), + friendView.call(p1, p2) + )) + .getDnf(); + var query = Query.of("SymbolViewRepresentative", (builder, p1) -> builder + .clause( + personView.call(p1), + new RepresentativeElectionLiteral(Connectivity.WEAK, subQuery, p1, p1) + )); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(QueryInterpreterAdapter.builder() + .queries(query)) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQueryAdapter.class); + var resultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + personInterpretation.put(Tuple.of(3), true); + + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 0), true); + friendInterpretation.put(Tuple.of(2, 3), true); + queryEngine.flushChanges(); + + assertThat(resultSet.size(), is(2)); + assertThat(resultSet.get(Tuple.of(2)), is(true)); + } +} diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/internal/matcher/MatcherUtilsTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/internal/matcher/MatcherUtilsTest.java new file mode 100644 index 00000000..1c8044ea --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/internal/matcher/MatcherUtilsTest.java @@ -0,0 +1,239 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.query.interpreter.internal.matcher; + +import tools.refinery.interpreter.matchers.tuple.*; +import org.junit.jupiter.api.Test; +import tools.refinery.store.tuple.Tuple; +import tools.refinery.store.tuple.*; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class MatcherUtilsTest { + @Test + void toViatra0Test() { + var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of()); + assertThat(viatraTuple.getSize(), is(0)); + assertThat(viatraTuple, instanceOf(FlatTuple0.class)); + } + + @Test + void toViatra1Test() { + var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2)); + assertThat(viatraTuple.getSize(), is(1)); + assertThat(viatraTuple.get(0), is(Tuple.of(2))); + assertThat(viatraTuple, instanceOf(FlatTuple1.class)); + } + + @Test + void toViatra2Test() { + var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3)); + assertThat(viatraTuple.getSize(), is(2)); + assertThat(viatraTuple.get(0), is(Tuple.of(2))); + assertThat(viatraTuple.get(1), is(Tuple.of(3))); + assertThat(viatraTuple, instanceOf(FlatTuple2.class)); + } + + @Test + void toViatra3Test() { + var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3, 5)); + assertThat(viatraTuple.getSize(), is(3)); + assertThat(viatraTuple.get(0), is(Tuple.of(2))); + assertThat(viatraTuple.get(1), is(Tuple.of(3))); + assertThat(viatraTuple.get(2), is(Tuple.of(5))); + assertThat(viatraTuple, instanceOf(FlatTuple3.class)); + } + + @Test + void toViatra4Test() { + var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3, 5, 8)); + assertThat(viatraTuple.getSize(), is(4)); + assertThat(viatraTuple.get(0), is(Tuple.of(2))); + assertThat(viatraTuple.get(1), is(Tuple.of(3))); + assertThat(viatraTuple.get(2), is(Tuple.of(5))); + assertThat(viatraTuple.get(3), is(Tuple.of(8))); + assertThat(viatraTuple, instanceOf(FlatTuple4.class)); + } + + @Test + void toViatra5Test() { + var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3, 5, 8, 13)); + assertThat(viatraTuple.getSize(), is(5)); + assertThat(viatraTuple.get(0), is(Tuple.of(2))); + assertThat(viatraTuple.get(1), is(Tuple.of(3))); + assertThat(viatraTuple.get(2), is(Tuple.of(5))); + assertThat(viatraTuple.get(3), is(Tuple.of(8))); + assertThat(viatraTuple.get(4), is(Tuple.of(13))); + assertThat(viatraTuple, instanceOf(FlatTuple.class)); + } + + @Test + void toRefinery0Test() { + var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf()); + assertThat(refineryTuple.getSize(), is(0)); + assertThat(refineryTuple, instanceOf(Tuple0.class)); + } + + @Test + void toRefinery1Test() { + var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2))); + assertThat(refineryTuple.getSize(), is(1)); + assertThat(refineryTuple.get(0), is(2)); + assertThat(refineryTuple, instanceOf(Tuple1.class)); + } + + @Test + void toRefinery2Test() { + var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3))); + assertThat(refineryTuple.getSize(), is(2)); + assertThat(refineryTuple.get(0), is(2)); + assertThat(refineryTuple.get(1), is(3)); + assertThat(refineryTuple, instanceOf(Tuple2.class)); + } + + @Test + void toRefinery3Test() { + var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5))); + assertThat(refineryTuple.getSize(), is(3)); + assertThat(refineryTuple.get(0), is(2)); + assertThat(refineryTuple.get(1), is(3)); + assertThat(refineryTuple.get(2), is(5)); + assertThat(refineryTuple, instanceOf(Tuple3.class)); + } + + @Test + void toRefinery4Test() { + var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5), + Tuple.of(8))); + assertThat(refineryTuple.getSize(), is(4)); + assertThat(refineryTuple.get(0), is(2)); + assertThat(refineryTuple.get(1), is(3)); + assertThat(refineryTuple.get(2), is(5)); + assertThat(refineryTuple.get(3), is(8)); + assertThat(refineryTuple, instanceOf(Tuple4.class)); + } + + @Test + void toRefinery5Test() { + var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5), + Tuple.of(8), Tuple.of(13))); + assertThat(refineryTuple.getSize(), is(5)); + assertThat(refineryTuple.get(0), is(2)); + assertThat(refineryTuple.get(1), is(3)); + assertThat(refineryTuple.get(2), is(5)); + assertThat(refineryTuple.get(3), is(8)); + assertThat(refineryTuple.get(4), is(13)); + assertThat(refineryTuple, instanceOf(TupleN.class)); + } + + @Test + void toRefineryInvalidValueTest() { + var viatraTuple = Tuples.flatTupleOf(Tuple.of(2), -98); + assertThrows(IllegalArgumentException.class, () -> MatcherUtils.toRefineryTuple(viatraTuple)); + } + + @Test + void keyToRefinery0Test() { + var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(-99)); + assertThat(refineryTuple.getSize(), is(0)); + assertThat(refineryTuple, instanceOf(Tuple0.class)); + } + + @Test + void keyToRefinery1Test() { + var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), -99)); + assertThat(refineryTuple.getSize(), is(1)); + assertThat(refineryTuple.get(0), is(2)); + assertThat(refineryTuple, instanceOf(Tuple1.class)); + } + + @Test + void keyToRefinery2Test() { + var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), -99)); + assertThat(refineryTuple.getSize(), is(2)); + assertThat(refineryTuple.get(0), is(2)); + assertThat(refineryTuple.get(1), is(3)); + assertThat(refineryTuple, instanceOf(Tuple2.class)); + } + + @Test + void keyToRefinery3Test() { + var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5), + -99)); + assertThat(refineryTuple.getSize(), is(3)); + assertThat(refineryTuple.get(0), is(2)); + assertThat(refineryTuple.get(1), is(3)); + assertThat(refineryTuple.get(2), is(5)); + assertThat(refineryTuple, instanceOf(Tuple3.class)); + } + + @Test + void keyToRefinery4Test() { + var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5), + Tuple.of(8), -99)); + assertThat(refineryTuple.getSize(), is(4)); + assertThat(refineryTuple.get(0), is(2)); + assertThat(refineryTuple.get(1), is(3)); + assertThat(refineryTuple.get(2), is(5)); + assertThat(refineryTuple.get(3), is(8)); + assertThat(refineryTuple, instanceOf(Tuple4.class)); + } + + @Test + void keyToRefinery5Test() { + var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5), + Tuple.of(8), Tuple.of(13), -99)); + assertThat(refineryTuple.getSize(), is(5)); + assertThat(refineryTuple.get(0), is(2)); + assertThat(refineryTuple.get(1), is(3)); + assertThat(refineryTuple.get(2), is(5)); + assertThat(refineryTuple.get(3), is(8)); + assertThat(refineryTuple.get(4), is(13)); + assertThat(refineryTuple, instanceOf(TupleN.class)); + } + + @Test + void keyToRefineryTooShortTest() { + var viatraTuple = Tuples.flatTupleOf(); + assertThrows(IllegalArgumentException.class, () -> MatcherUtils.keyToRefineryTuple(viatraTuple)); + } + + @Test + void keyToRefineryInvalidValueTest() { + var viatraTuple = Tuples.flatTupleOf(Tuple.of(2), -98, -99); + assertThrows(IllegalArgumentException.class, () -> MatcherUtils.keyToRefineryTuple(viatraTuple)); + } + + @Test + void getSingleValueTest() { + var value = MatcherUtils.getSingleValue(List.of(Tuples.flatTupleOf(Tuple.of(2), -99))); + assertThat(value, is(-99)); + } + + // Static analysis accurately determines that the result is always {@code null}, but we check anyways. + @SuppressWarnings("ConstantValue") + @Test + void getSingleValueNullTest() { + var value = MatcherUtils.getSingleValue((Iterable) null); + assertThat(value, nullValue()); + } + + @Test + void getSingleValueEmptyTest() { + var value = MatcherUtils.getSingleValue(List.of()); + assertThat(value, nullValue()); + } + + @Test + void getSingleValueMultipleTest() { + var viatraTuples = List.of(Tuples.flatTupleOf(Tuple.of(2), -98), Tuples.flatTupleOf(Tuple.of(2), -99)); + assertThrows(IllegalStateException.class, () -> MatcherUtils.getSingleValue(viatraTuples)); + } +} diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryAssertions.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryAssertions.java new file mode 100644 index 00000000..c4659a98 --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryAssertions.java @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.query.interpreter.tests; + +import org.junit.jupiter.api.function.Executable; +import tools.refinery.store.query.resultset.ResultSet; +import tools.refinery.store.tuple.Tuple; + +import java.util.*; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertAll; + +public final class QueryAssertions { + private QueryAssertions() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); + } + + public static void assertNullableResults(Map> expected, ResultSet resultSet) { + var nullableValuesMap = new LinkedHashMap(expected.size()); + for (var entry : expected.entrySet()) { + nullableValuesMap.put(entry.getKey(), entry.getValue().orElse(null)); + } + assertResults(nullableValuesMap, resultSet); + } + + public static void assertResults(Map expected, ResultSet resultSet) { + var defaultValue = resultSet.getCanonicalQuery().defaultValue(); + var filteredExpected = new LinkedHashMap(); + var executables = new ArrayList(); + for (var entry : expected.entrySet()) { + var key = entry.getKey(); + var value = entry.getValue(); + if (!Objects.equals(value, defaultValue)) { + filteredExpected.put(key, value); + } + executables.add(() -> assertThat("value for key " + key,resultSet.get(key), is(value))); + } + executables.add(() -> assertThat("results size", resultSet.size(), is(filteredExpected.size()))); + + var actual = new LinkedHashMap(); + var cursor = resultSet.getAll(); + while (cursor.move()) { + var key = cursor.getKey(); + var previous = actual.put(key, cursor.getValue()); + assertThat("duplicate value for key " + key, previous, nullValue()); + } + executables.add(() -> assertThat("results cursor", actual, is(filteredExpected))); + + assertAll(executables); + } +} diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryBackendHint.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryBackendHint.java new file mode 100644 index 00000000..f9d5b219 --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryBackendHint.java @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.query.interpreter.tests; + +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; + +/** + * Overrides {@link QueryEvaluationHint#toString()} for pretty names in parametric test names. + */ +class QueryBackendHint extends QueryEvaluationHint { + public QueryBackendHint(BackendRequirement backendRequirementType) { + super(null, backendRequirementType); + } + + @Override + public String toString() { + return switch (getQueryBackendRequirementType()) { + case UNSPECIFIED -> "default"; + case DEFAULT_CACHING -> "incremental"; + case DEFAULT_SEARCH -> "localSearch"; + default -> throw new IllegalStateException("Unknown BackendRequirement"); + }; + } +} diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEngineTest.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEngineTest.java new file mode 100644 index 00000000..a5cc7e9c --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEngineTest.java @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.query.interpreter.tests; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@ParameterizedTest(name = "backend = {0}") +@ArgumentsSource(QueryEvaluationHintSource.class) +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface QueryEngineTest { +} diff --git a/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEvaluationHintSource.java b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEvaluationHintSource.java new file mode 100644 index 00000000..6503ff2f --- /dev/null +++ b/subprojects/store-query-interpreter/src/test/java/tools/refinery/store/query/interpreter/tests/QueryEvaluationHintSource.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.query.interpreter.tests; + +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; + +import java.util.stream.Stream; + +public class QueryEvaluationHintSource implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.UNSPECIFIED)), + Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.DEFAULT_CACHING)), + Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH)) + ); + } +} -- cgit v1.2.3-54-g00ecf