From 372058e54825ab58a66c25ae528e81a656c22659 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Tue, 7 Mar 2023 16:26:26 +0100 Subject: feat: terms and improved query evaluation * Implement data terms for computations in queries. * Function-like queries with computed results. * Improved query evaluation, including positive and negative diagonal cosntraints. * Preliminary local search support. * Changes to the DNF representation for count and aggregation support. feat: terms wip feat: query terms wip feat: query evaluation, diagonal constraints, local search wip fix reasoning compilation wip --- .../store/query/viatra/DiagonalQueryTest.java | 471 ++++++++++++++++ .../store/query/viatra/FunctionalQueryTest.java | 607 +++++++++++++++++++++ .../refinery/store/query/viatra/QueryTest.java | 428 +++++++++------ .../store/query/viatra/QueryTransactionTest.java | 384 ++++++++++++- .../store/query/viatra/tests/QueryAssertions.java | 52 ++ .../store/query/viatra/tests/QueryBackendHint.java | 22 + .../store/query/viatra/tests/QueryEngineTest.java | 16 + .../viatra/tests/QueryEvaluationHintSource.java | 19 + 8 files changed, 1810 insertions(+), 189 deletions(-) create mode 100644 subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java create mode 100644 subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java create mode 100644 subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java create mode 100644 subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java create mode 100644 subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java create mode 100644 subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java (limited to 'subprojects/store-query-viatra/src/test') diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java new file mode 100644 index 00000000..90229b73 --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java @@ -0,0 +1,471 @@ +package tools.refinery.store.query.viatra; + +import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.ModelQuery; +import tools.refinery.store.query.dnf.Dnf; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.viatra.tests.QueryEngineTest; +import tools.refinery.store.query.view.FunctionalRelationView; +import tools.refinery.store.query.view.KeyOnlyRelationView; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.tuple.Tuple; + +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.viatra.tests.QueryAssertions.assertNullableResults; +import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults; + +class DiagonalQueryTest { + @QueryEngineTest + void inputKeyNegationTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 4, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new KeyOnlyRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var query = Query.builder("Diagonal") + .parameter(p1) + .clause( + personView.call(p1), + not(symbolView.call(p1, p1, p2, p2)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 4, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new KeyOnlyRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var p3 = Variable.of("p3"); + var p4 = Variable.of("p4"); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, p2, p3, p4) + .clause( + personView.call(p1), + symbolView.call(p1, p2, p3, p4) + ) + .clause( + personView.call(p2), + symbolView.call(p1, p2, p3, p4) + ) + .build(); + var query = Query.builder("Diagonal") + .parameter(p1) + .clause( + personView.call(p1), + not(subQuery.call(p1, p1, p2, p2)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 4, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new KeyOnlyRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var x = Variable.of("x", Integer.class); + var query = Query.builder("Diagonal") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + x.assign(symbolView.count(p1, p1, p2, p2)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 4, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new KeyOnlyRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var p3 = Variable.of("p3"); + var p4 = Variable.of("p4"); + var x = Variable.of("x", Integer.class); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, p2, p3, p4) + .clause( + personView.call(p1), + symbolView.call(p1, p2, p3, p4) + ) + .clause( + personView.call(p2), + symbolView.call(p1, p2, p3, p4) + ) + .build(); + var query = Query.builder("Diagonal") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + x.assign(subQuery.count(p1, p1, p2, p2)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 4, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new FunctionalRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var x = Variable.of("x", Integer.class); + var y = Variable.of("y", Integer.class); + var query = Query.builder("Diagonal") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + x.assign(symbolView.aggregate(y, INT_SUM, p1, p1, p2, p2, y)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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), 1); + symbolInterpretation.put(Tuple.of(0, 0, 2, 2), 2); + symbolInterpretation.put(Tuple.of(0, 0, 1, 2), 10); + symbolInterpretation.put(Tuple.of(1, 1, 0, 1), 11); + symbolInterpretation.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 person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 4, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new FunctionalRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var p3 = Variable.of("p3"); + var p4 = Variable.of("p4"); + var x = Variable.of("x", Integer.class); + var y = Variable.of("y", Integer.class); + var z = Variable.of("z", Integer.class); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, p2, p3, p4, x, y) + .clause( + personView.call(p1), + symbolView.call(p1, p2, p3, p4, x), + y.assign(x) + ) + .clause( + personView.call(p2), + symbolView.call(p1, p2, p3, p4, x), + y.assign(x) + ) + .build(); + var query = Query.builder("Diagonal") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + x.assign(subQuery.aggregate(z, INT_SUM, p1, p1, p2, p2, z, z)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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), 1); + symbolInterpretation.put(Tuple.of(0, 0, 2, 2), 2); + symbolInterpretation.put(Tuple.of(0, 0, 1, 2), 10); + symbolInterpretation.put(Tuple.of(1, 1, 0, 1), 11); + symbolInterpretation.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 person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 2, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new KeyOnlyRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var query = Query.builder("Diagonal") + .parameter(p1) + .clause( + personView.call(p1), + symbolView.callTransitive(p1, p1) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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), true); + symbolInterpretation.put(Tuple.of(0, 1), true); + symbolInterpretation.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 person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 2, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new KeyOnlyRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, p2) + .clause( + personView.call(p1), + symbolView.call(p1, p2) + ) + .clause( + personView.call(p2), + symbolView.call(p1, p2) + ) + .build(); + var query = Query.builder("Diagonal") + .parameter(p1) + .clause( + personView.call(p1), + subQuery.callTransitive(p1, p1) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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), true); + symbolInterpretation.put(Tuple.of(0, 1), true); + symbolInterpretation.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-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java new file mode 100644 index 00000000..fa2a008f --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java @@ -0,0 +1,607 @@ +package tools.refinery.store.query.viatra; + +import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.store.map.Cursor; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.ModelQuery; +import tools.refinery.store.query.dnf.Dnf; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.viatra.tests.QueryEngineTest; +import tools.refinery.store.query.view.FilteredRelationView; +import tools.refinery.store.query.view.FunctionalRelationView; +import tools.refinery.store.query.view.KeyOnlyRelationView; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.representation.TruthValue; +import tools.refinery.store.tuple.Tuple; + +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.assume; +import static tools.refinery.store.query.term.int_.IntTerms.*; +import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults; +import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults; + +class FunctionalQueryTest { + @QueryEngineTest + void inputKeyTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var query = Query.builder("InputKey") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + ageView.call(p1, x) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, x) + .clause( + personView.call(p1), + ageView.call(p1, x) + ) + .build(); + var query = Query.builder("Predicate") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + subQuery.call(p1, x) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var y = Variable.of("y", Integer.class); + var query = Query.builder("Computation") + .parameter(p1) + .output(y) + .clause( + personView.call(p1), + ageView.call(p1, x), + y.assign(mul(x, constant(7))) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var x = Variable.of("x", Integer.class); + var query = Query.builder("Count") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + x.assign(friendMustView.count(p1, p2)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var x = Variable.of("x", Integer.class); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, p2) + .clause( + personView.call(p1), + personView.call(p2), + friendMustView.call(p1, p2) + ) + .build(); + var query = Query.builder("Count") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + x.assign(subQuery.count(p1, p2)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 age = new Symbol<>("age", 1, Integer.class, null); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var y = Variable.of("y", Integer.class); + var query = Query.builder("Aggregate") + .output(x) + .clause( + x.assign(ageView.aggregate(y, INT_SUM, p1, y)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var y = Variable.of("y", Integer.class); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, x) + .clause( + personView.call(p1), + ageView.call(p1, x) + ) + .build(); + var query = Query.builder("Aggregate") + .output(x) + .clause( + x.assign(subQuery.aggregate(y, INT_SUM, p1, y)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var x = Variable.of("x", Integer.class); + var y = Variable.of("y", Integer.class); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, x) + .clause( + personView.call(p1), + x.assign(friendMustView.count(p1, p2)) + ) + .build(); + var minQuery = Query.builder("Min") + .output(x) + .clause( + x.assign(subQuery.aggregate(y, INT_MIN, p1, y)) + ) + .build(); + var maxQuery = Query.builder("Max") + .output(x) + .clause( + x.assign(subQuery.aggregate(y, INT_MAX, p1, y)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(minQuery, maxQuery) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var y = Variable.of("y", Integer.class); + var query = Query.builder("InvalidComputation") + .parameter(p1) + .output(y) + .clause( + personView.call(p1), + ageView.call(p1, x), + y.assign(div(constant(120), x)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var query = Query.builder("InvalidComputation") + .parameter(p1) + .clause( + personView.call(p1), + ageView.call(p1, x), + assume(lessEq(div(constant(120), x), constant(5))) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 notFunctionalTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var x = Variable.of("x", Integer.class); + var query = Query.builder("NotFunctional") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + friendMustView.call(p1, p2), + ageView.call(p2, x) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age, friend) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .query(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(ModelQuery.ADAPTER); + 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-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java index 8b25419d..8a3f9d88 100644 --- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java @@ -1,33 +1,38 @@ package tools.refinery.store.query.viatra; +import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; import org.junit.jupiter.api.Test; -import tools.refinery.store.map.Cursor; import tools.refinery.store.model.ModelStore; -import tools.refinery.store.query.Dnf; import tools.refinery.store.query.ModelQuery; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.dnf.Dnf; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.viatra.tests.QueryEngineTest; import tools.refinery.store.query.view.FilteredRelationView; +import tools.refinery.store.query.view.FunctionalRelationView; import tools.refinery.store.query.view.KeyOnlyRelationView; import tools.refinery.store.representation.Symbol; import tools.refinery.store.representation.TruthValue; import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.tuple.TupleLike; -import java.util.HashSet; -import java.util.Set; +import java.util.Map; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static tools.refinery.store.query.literal.Literals.assume; 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.viatra.tests.QueryAssertions.assertResults; class QueryTest { - @Test - void typeConstraintTest() { + @QueryEngineTest + void typeConstraintTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var asset = new Symbol<>("Asset", 1, Boolean.class, false); var personView = new KeyOnlyRelationView<>(person); - var p1 = new Variable("p1"); - var predicate = Dnf.builder("TypeConstraint") + var p1 = Variable.of("p1"); + var predicate = Query.builder("TypeConstraint") .parameters(p1) .clause(personView.call(p1)) .build(); @@ -35,7 +40,8 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, asset) .with(ViatraModelQuery.ADAPTER) - .queries(predicate) + .defaultHint(hint) + .query(predicate) .build(); var model = store.createEmptyModel(); @@ -51,20 +57,23 @@ class QueryTest { assetInterpretation.put(Tuple.of(2), true); queryEngine.flushChanges(); - assertEquals(2, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0), Tuple.of(1))); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false + ), predicateResultSet); } - @Test - void relationConstraintTest() { + @QueryEngineTest + void relationConstraintTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var predicate = Dnf.builder("RelationConstraint") + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var predicate = Query.builder("RelationConstraint") .parameters(p1, p2) .clause( personView.call(p1), @@ -76,6 +85,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -85,8 +95,6 @@ class QueryTest { var queryEngine = model.getAdapter(ModelQuery.ADAPTER); var predicateResultSet = queryEngine.getResultSet(predicate); - assertEquals(0, predicateResultSet.countResults()); - personInterpretation.put(Tuple.of(0), true); personInterpretation.put(Tuple.of(1), true); personInterpretation.put(Tuple.of(2), true); @@ -94,83 +102,27 @@ class QueryTest { friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); - - assertEquals(0, predicateResultSet.countResults()); - assertFalse(predicateResultSet.hasResult(Tuple.of(0, 1))); - assertFalse(predicateResultSet.hasResult(Tuple.of(0, 2))); + friendInterpretation.put(Tuple.of(1, 3), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(3, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0), Tuple.of(1, 2))); - assertTrue(predicateResultSet.hasResult(Tuple.of(0, 1))); - assertFalse(predicateResultSet.hasResult(Tuple.of(0, 2))); + assertResults(Map.of( + Tuple.of(0, 1), true, + Tuple.of(1, 0), true, + Tuple.of(1, 2), true, + Tuple.of(2, 1), false + ), predicateResultSet); } - @Test - void andTest() { + @QueryEngineTest + void existTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var predicate = Dnf.builder("RelationConstraint") - .parameters(p1, p2) - .clause( - personView.call(p1), - personView.call(p2), - friendMustView.call(p1, p2), - friendMustView.call(p2, p1) - ) - .build(); - - var store = ModelStore.builder() - .symbols(person, friend) - .with(ViatraModelQuery.ADAPTER) - .queries(predicate) - .build(); - - var model = store.createEmptyModel(); - var personInterpretation = model.getInterpretation(person); - var friendInterpretation = model.getInterpretation(friend); - var queryEngine = model.getAdapter(ModelQuery.ADAPTER); - var predicateResultSet = queryEngine.getResultSet(predicate); - - assertEquals(0, predicateResultSet.countResults()); - - 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(); - assertEquals(0, predicateResultSet.countResults()); - - friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); - queryEngine.flushChanges(); - assertEquals(2, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0))); - - friendInterpretation.put(Tuple.of(2, 0), TruthValue.TRUE); - queryEngine.flushChanges(); - assertEquals(4, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0), Tuple.of(0, 2), - Tuple.of(2, 0))); - } - - @Test - void existTest() { - var person = new Symbol<>("Person", 1, Boolean.class, false); - var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); - var personView = new KeyOnlyRelationView<>(person); - var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var predicate = Dnf.builder("RelationConstraint") + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var predicate = Query.builder("RelationConstraint") .parameters(p1) .clause( personView.call(p1), @@ -182,6 +134,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -198,16 +151,19 @@ class QueryTest { friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); - - assertEquals(0, predicateResultSet.countResults()); + friendInterpretation.put(Tuple.of(3, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(2, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0), Tuple.of(1))); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); } - @Test - void orTest() { + @QueryEngineTest + void orTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var animal = new Symbol<>("Animal", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); @@ -215,9 +171,9 @@ class QueryTest { var animalView = new KeyOnlyRelationView<>(animal); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var predicate = Dnf.builder("Or") + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var predicate = Query.builder("Or") .parameters(p1, p2) .clause( personView.call(p1), @@ -234,6 +190,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, animal, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -256,18 +213,23 @@ class QueryTest { friendInterpretation.put(Tuple.of(3, 0), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(2, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(2, 3))); + 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); } - @Test - void equalityTest() { + @QueryEngineTest + void equalityTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var personView = new KeyOnlyRelationView<>(person); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var predicate = Dnf.builder("Equality") + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var predicate = Query.builder("Equality") .parameters(p1, p2) .clause( personView.call(p1), @@ -279,6 +241,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -292,21 +255,26 @@ class QueryTest { personInterpretation.put(Tuple.of(2), true); queryEngine.flushChanges(); - assertEquals(3, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 0), Tuple.of(1, 1), Tuple.of(2, 2))); + 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); } - @Test - void inequalityTest() { + @QueryEngineTest + void inequalityTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var p3 = new Variable("p3"); - var predicate = Dnf.builder("Inequality") + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var p3 = Variable.of("p3"); + var predicate = Query.builder("Inequality") .parameters(p1, p2, p3) .clause( personView.call(p1), @@ -320,6 +288,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -337,19 +306,22 @@ class QueryTest { friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(2, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1, 2), Tuple.of(1, 0, 2))); + assertResults(Map.of( + Tuple.of(0, 1, 2), true, + Tuple.of(1, 0, 2), true, + Tuple.of(0, 0, 2), false + ), predicateResultSet); } - @Test - void patternCallTest() { + @QueryEngineTest + void patternCallTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); var friendPredicate = Dnf.builder("RelationConstraint") .parameters(p1, p2) .clause( @@ -359,9 +331,9 @@ class QueryTest { ) .build(); - var p3 = new Variable("p3"); - var p4 = new Variable("p4"); - var predicate = Dnf.builder("PositivePatternCall") + var p3 = Variable.of("p3"); + var p4 = Variable.of("p4"); + var predicate = Query.builder("PositivePatternCall") .parameters(p3, p4) .clause( personView.call(p3), @@ -373,6 +345,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -391,19 +364,24 @@ class QueryTest { friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(3, predicateResultSet.countResults()); + assertResults(Map.of( + Tuple.of(0, 1), true, + Tuple.of(1, 0), true, + Tuple.of(1, 2), true, + Tuple.of(2, 1), false + ), predicateResultSet); } - @Test - void negativeRelationViewTest() { + @QueryEngineTest + void negativeRelationViewTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var predicate = Dnf.builder("NegativePatternCall") + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var predicate = Query.builder("NegativePatternCall") .parameters(p1, p2) .clause( personView.call(p1), @@ -415,6 +393,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -433,18 +412,29 @@ class QueryTest { friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(6, predicateResultSet.countResults()); + 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); } - @Test - void negativePatternCallTest() { + @QueryEngineTest + void negativePatternCallTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); var friendPredicate = Dnf.builder("RelationConstraint") .parameters(p1, p2) .clause( @@ -454,9 +444,9 @@ class QueryTest { ) .build(); - var p3 = new Variable("p3"); - var p4 = new Variable("p4"); - var predicate = Dnf.builder("NegativePatternCall") + var p3 = Variable.of("p3"); + var p4 = Variable.of("p4"); + var predicate = Query.builder("NegativePatternCall") .parameters(p3, p4) .clause( personView.call(p3), @@ -468,6 +458,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -486,20 +477,31 @@ class QueryTest { friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(6, predicateResultSet.countResults()); + 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); } - @Test - void negativeRelationViewWithQuantificationTest() { + @QueryEngineTest + void negativeRelationViewWithQuantificationTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); - var predicate = Dnf.builder("Count") + var predicate = Query.builder("Count") .parameters(p1) .clause( personView.call(p1), @@ -510,6 +512,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -527,18 +530,23 @@ class QueryTest { friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(2, predicateResultSet.countResults()); + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), true, + Tuple.of(2), true, + Tuple.of(3), false + ), predicateResultSet); } - @Test - void negativeWithQuantificationTest() { + @QueryEngineTest + void negativeWithQuantificationTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); var called = Dnf.builder("Called") .parameters(p1, p2) @@ -549,7 +557,7 @@ class QueryTest { ) .build(); - var predicate = Dnf.builder("Count") + var predicate = Query.builder("Count") .parameters(p1) .clause( personView.call(p1), @@ -560,6 +568,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -577,19 +586,24 @@ class QueryTest { friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(2, predicateResultSet.countResults()); + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), true, + Tuple.of(2), true, + Tuple.of(3), false + ), predicateResultSet); } - @Test - void transitiveRelationViewTest() { + @QueryEngineTest + void transitiveRelationViewTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var predicate = Dnf.builder("TransitivePatternCall") + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var predicate = Query.builder("TransitivePatternCall") .parameters(p1, p2) .clause( personView.call(p1), @@ -601,6 +615,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -618,18 +633,29 @@ class QueryTest { friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(3, predicateResultSet.countResults()); + 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 transitivePatternCallTest() { + @QueryEngineTest + void transitivePatternCallTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); var friendPredicate = Dnf.builder("RelationConstraint") .parameters(p1, p2) .clause( @@ -639,9 +665,9 @@ class QueryTest { ) .build(); - var p3 = new Variable("p3"); - var p4 = new Variable("p4"); - var predicate = Dnf.builder("TransitivePatternCall") + var p3 = Variable.of("p3"); + var p4 = Variable.of("p4"); + var predicate = Query.builder("TransitivePatternCall") .parameters(p3, p4) .clause( personView.call(p3), @@ -653,6 +679,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -670,15 +697,71 @@ class QueryTest { friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(3, predicateResultSet.countResults()); + 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 assumeTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var query = Query.builder("Constraint") + .parameter(p1) + .clause( + personView.call(p1), + ageView.call(p1, x), + assume(greaterEq(x, constant(18))) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); - var p1 = new Variable("p1"); - var predicate = Dnf.builder("AlwaysFalse").parameters(p1).build(); + var p1 = Variable.of("p1"); + var predicate = Query.builder("AlwaysFalse").parameters(p1).build(); var store = ModelStore.builder() .symbols(person) @@ -696,28 +779,19 @@ class QueryTest { personInterpretation.put(Tuple.of(2), true); queryEngine.flushChanges(); - assertEquals(0, predicateResultSet.countResults()); + assertResults(Map.of(), predicateResultSet); } @Test void alwaysTrueTest() { var person = new Symbol<>("Person", 1, Boolean.class, false); - var p1 = new Variable("p1"); - var predicate = Dnf.builder("AlwaysTrue").parameters(p1).clause().build(); + var p1 = Variable.of("p1"); + var predicate = Query.builder("AlwaysTrue").parameters(p1).clause().build(); var storeBuilder = ModelStore.builder().symbols(person); var queryBuilder = storeBuilder.with(ViatraModelQuery.ADAPTER); assertThrows(IllegalArgumentException.class, () -> queryBuilder.queries(predicate)); } - - private static void compareMatchSets(Cursor cursor, Set expected) { - Set translatedMatchSet = new HashSet<>(); - while (cursor.move()) { - var element = cursor.getKey(); - translatedMatchSet.add(element.toTuple()); - } - assertEquals(expected, translatedMatchSet); - } } diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java index 461685b5..abd49341 100644 --- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java @@ -1,25 +1,160 @@ package tools.refinery.store.query.viatra; +import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import tools.refinery.store.model.ModelStore; -import tools.refinery.store.query.Dnf; import tools.refinery.store.query.ModelQuery; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.view.FilteredRelationView; +import tools.refinery.store.query.view.FunctionalRelationView; import tools.refinery.store.query.view.KeyOnlyRelationView; import tools.refinery.store.representation.Symbol; import tools.refinery.store.tuple.Tuple; -import static org.junit.jupiter.api.Assertions.*; +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.viatra.tests.QueryAssertions.assertNullableResults; +import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults; class QueryTransactionTest { @Test void flushTest() { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + + var p1 = Variable.of("p1"); + var predicate = Query.builder("TypeConstraint") + .parameters(p1) + .clause(personView.call(p1)) + .build(); + + var store = ModelStore.builder() + .symbols(person) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + + var p1 = Variable.of("p1"); + var predicate = Query.builder("TypeConstraint") + .parameters(p1) + .clause(personView.call(p1)) + .build(); + + var store = ModelStore.builder() + .symbols(person) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(new QueryEvaluationHint(null, QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH)) + .queries(predicate) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); var asset = new Symbol<>("Asset", 1, Boolean.class, false); var personView = new KeyOnlyRelationView<>(person); - var p1 = new Variable("p1"); - var predicate = Dnf.builder("TypeConstraint") + var p1 = Variable.of("p1"); + var predicate = Query.builder("TypeConstraint") .parameters(p1) .clause(personView.call(p1)) .build(); @@ -36,7 +171,6 @@ class QueryTransactionTest { var queryEngine = model.getAdapter(ModelQuery.ADAPTER); var predicateResultSet = queryEngine.getResultSet(predicate); - assertEquals(0, predicateResultSet.countResults()); assertFalse(queryEngine.hasPendingChanges()); personInterpretation.put(Tuple.of(0), true); @@ -45,19 +179,245 @@ class QueryTransactionTest { assetInterpretation.put(Tuple.of(1), true); assetInterpretation.put(Tuple.of(2), true); - assertEquals(0, predicateResultSet.countResults()); + 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(); - assertEquals(2, predicateResultSet.countResults()); + 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()); - personInterpretation.put(Tuple.of(4), true); - assertEquals(2, predicateResultSet.countResults()); - assertTrue(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 person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var query = Query.builder() + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + ageView.call(p1, x) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .query(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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 person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var adultView = new FilteredRelationView<>(age, "adult", n -> n != null && n >= 18); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var query = Query.builder() + .parameter(p1) + .clause( + personView.call(p1), + adultView.call(p1) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .query(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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(); - assertEquals(3, predicateResultSet.countResults()); + assertResults(Map.of(Tuple.of(0), false), queryResultSet); + } + + @Disabled("TODO Fix DiffCursor") + @Test + void commitAfterFlushTest() { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + + var p1 = Variable.of("p1"); + var predicate = Query.builder("TypeConstraint") + .parameters(p1) + .clause(personView.call(p1)) + .build(); + + var store = ModelStore.builder() + .symbols(person) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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); + } + + @Disabled("TODO Fix DiffCursor") + @Test + void commitWithoutFlushTest() { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + + var p1 = Variable.of("p1"); + var predicate = Query.builder("TypeConstraint") + .parameters(p1) + .clause(personView.call(p1)) + .build(); + + var store = ModelStore.builder() + .symbols(person) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + 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-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java new file mode 100644 index 00000000..6f50ec73 --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java @@ -0,0 +1,52 @@ +package tools.refinery.store.query.viatra.tests; + +import org.junit.jupiter.api.function.Executable; +import tools.refinery.store.query.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.getQuery().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.toTuple(), 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-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java new file mode 100644 index 00000000..b1818a17 --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java @@ -0,0 +1,22 @@ +package tools.refinery.store.query.viatra.tests; + +import org.eclipse.viatra.query.runtime.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-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java new file mode 100644 index 00000000..f129520c --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java @@ -0,0 +1,16 @@ +package tools.refinery.store.query.viatra.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-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java new file mode 100644 index 00000000..a55762e2 --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java @@ -0,0 +1,19 @@ +package tools.refinery.store.query.viatra.tests; + +import org.eclipse.viatra.query.runtime.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-70-g09d2