aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store-query/src
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <marussy@mit.bme.hu>2023-03-31 17:31:46 +0200
committerLibravatar GitHub <noreply@github.com>2023-03-31 17:31:46 +0200
commit93f1d439f33a5139039fe93280bbfcae61a904ab (patch)
treee98eae681a866d2d0cd728885ed6c8f8fa65e9a2 /subprojects/store-query/src
parentrefactor: PartialInterpretation adapter naming (diff)
parentbuild: try to fix secret detection in workflow (diff)
downloadrefinery-93f1d439f33a5139039fe93280bbfcae61a904ab.tar.gz
refinery-93f1d439f33a5139039fe93280bbfcae61a904ab.tar.zst
refinery-93f1d439f33a5139039fe93280bbfcae61a904ab.zip
Merge pull request #24 from kris7t/partial-interpretation
Changes for supporting partial interpretation
Diffstat (limited to 'subprojects/store-query/src')
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/AnyResultSet.java11
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java65
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/EmptyResultSet.java34
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQuery.java11
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java19
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java25
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java11
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java194
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java110
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java23
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java19
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java103
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java46
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java71
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java93
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java8
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java48
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java80
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java113
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java44
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java53
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java53
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java102
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java32
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java34
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java83
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java52
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java20
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java26
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/CompositeSubstitution.java10
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java18
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitutions.java33
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java33
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryOperator.java26
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryTerm.java56
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryOperator.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryTerm.java51
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java8
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java93
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonOperator.java20
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonTerm.java63
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java52
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataSort.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java87
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java103
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeSort.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java48
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/OpaqueTerm.java80
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Sort.java11
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java23
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java20
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java68
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java76
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolConstantTerm.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolLogicBinaryTerm.java78
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java36
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/LogicBinaryOperator.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticBinaryTerm.java48
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticUnaryTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntComparisonTerm.java34
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntExtremeValueAggregator.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java35
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java81
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java36
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java36
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticBinaryTerm.java36
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticUnaryTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealComparisonTerm.java35
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealExtremeValueAggregator.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java85
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java81
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java11
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java23
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java26
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java49
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenRelationView.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java105
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java36
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayRelationView.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustRelationView.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java77
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java19
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java57
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/DnfBuilderTest.java220
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/DnfToDefinitionStringTest.java174
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java77
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java43
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java14
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java36
105 files changed, 4684 insertions, 0 deletions
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/AnyResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/AnyResultSet.java
new file mode 100644
index 00000000..6d411212
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/AnyResultSet.java
@@ -0,0 +1,11 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.query.dnf.AnyQuery;
4
5public sealed interface AnyResultSet permits ResultSet {
6 ModelQueryAdapter getAdapter();
7
8 AnyQuery getQuery();
9
10 int size();
11}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java
new file mode 100644
index 00000000..cec4c19f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java
@@ -0,0 +1,65 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.literal.*;
5import tools.refinery.store.query.term.*;
6
7import java.util.List;
8
9public interface Constraint {
10 String name();
11
12 List<Sort> getSorts();
13
14 default int arity() {
15 return getSorts().size();
16 }
17
18 default boolean invalidIndex(int i) {
19 return i < 0 || i >= arity();
20 }
21
22 default LiteralReduction getReduction() {
23 return LiteralReduction.NOT_REDUCIBLE;
24 }
25
26 default boolean equals(LiteralEqualityHelper helper, Constraint other) {
27 return equals(other);
28 }
29
30 String toReferenceString();
31
32 default CallLiteral call(CallPolarity polarity, List<Variable> arguments) {
33 return new CallLiteral(polarity, this, arguments);
34 }
35
36 default CallLiteral call(CallPolarity polarity, Variable... arguments) {
37 return call(polarity, List.of(arguments));
38 }
39
40 default CallLiteral call(Variable... arguments) {
41 return call(CallPolarity.POSITIVE, arguments);
42 }
43
44 default CallLiteral callTransitive(NodeVariable left, NodeVariable right) {
45 return call(CallPolarity.TRANSITIVE, List.of(left, right));
46 }
47
48 default AssignedValue<Integer> count(List<Variable> arguments) {
49 return targetVariable -> new CountLiteral(targetVariable, this, arguments);
50 }
51
52 default AssignedValue<Integer> count(Variable... arguments) {
53 return count(List.of(arguments));
54 }
55
56 default <R, T> AssignedValue<R> aggregate(DataVariable<T> inputVariable, Aggregator<R, T> aggregator,
57 List<Variable> arguments) {
58 return targetVariable -> new AggregationLiteral<>(targetVariable, aggregator, inputVariable, this, arguments);
59 }
60
61 default <R, T> AssignedValue<R> aggregate(DataVariable<T> inputVariable, Aggregator<R, T> aggregator,
62 Variable... arguments) {
63 return aggregate(inputVariable, aggregator, List.of(arguments));
64 }
65}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/EmptyResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/EmptyResultSet.java
new file mode 100644
index 00000000..9af73bdd
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/EmptyResultSet.java
@@ -0,0 +1,34 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.map.Cursor;
4import tools.refinery.store.map.Cursors;
5import tools.refinery.store.query.dnf.Query;
6import tools.refinery.store.tuple.TupleLike;
7
8public record EmptyResultSet<T>(ModelQueryAdapter adapter, Query<T> query) implements ResultSet<T> {
9 @Override
10 public ModelQueryAdapter getAdapter() {
11 return adapter;
12 }
13
14 @Override
15 public Query<T> getQuery() {
16 return query;
17 }
18
19 @Override
20 public T get(TupleLike parameters) {
21 return query.defaultValue();
22 }
23
24
25 @Override
26 public Cursor<TupleLike, T> getAll() {
27 return Cursors.empty();
28 }
29
30 @Override
31 public int size() {
32 return 0;
33 }
34}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQuery.java
new file mode 100644
index 00000000..6a1aeabb
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQuery.java
@@ -0,0 +1,11 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.adapter.ModelAdapterType;
4
5public final class ModelQuery extends ModelAdapterType<ModelQueryAdapter, ModelQueryStoreAdapter, ModelQueryBuilder> {
6 public static final ModelQuery ADAPTER = new ModelQuery();
7
8 private ModelQuery() {
9 super(ModelQueryAdapter.class, ModelQueryStoreAdapter.class, ModelQueryBuilder.class);
10 }
11}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java
new file mode 100644
index 00000000..2e30fec4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java
@@ -0,0 +1,19 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.adapter.ModelAdapter;
4import tools.refinery.store.query.dnf.AnyQuery;
5import tools.refinery.store.query.dnf.Query;
6
7public interface ModelQueryAdapter extends ModelAdapter {
8 ModelQueryStoreAdapter getStoreAdapter();
9
10 default AnyResultSet getResultSet(AnyQuery query) {
11 return getResultSet((Query<?>) query);
12 }
13
14 <T> ResultSet<T> getResultSet(Query<T> query);
15
16 boolean hasPendingChanges();
17
18 void flushChanges();
19}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java
new file mode 100644
index 00000000..4fdc9210
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java
@@ -0,0 +1,25 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.adapter.ModelAdapterBuilder;
4import tools.refinery.store.model.ModelStore;
5import tools.refinery.store.query.dnf.AnyQuery;
6
7import java.util.Collection;
8import java.util.List;
9
10@SuppressWarnings("UnusedReturnValue")
11public interface ModelQueryBuilder extends ModelAdapterBuilder {
12 default ModelQueryBuilder queries(AnyQuery... queries) {
13 return queries(List.of(queries));
14 }
15
16 default ModelQueryBuilder queries(Collection<? extends AnyQuery> queries) {
17 queries.forEach(this::query);
18 return this;
19 }
20
21 ModelQueryBuilder query(AnyQuery query);
22
23 @Override
24 ModelQueryStoreAdapter createStoreAdapter(ModelStore store);
25}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java
new file mode 100644
index 00000000..514e582b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java
@@ -0,0 +1,17 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.adapter.ModelStoreAdapter;
4import tools.refinery.store.model.Model;
5import tools.refinery.store.query.dnf.AnyQuery;
6import tools.refinery.store.query.view.AnyRelationView;
7
8import java.util.Collection;
9
10public interface ModelQueryStoreAdapter extends ModelStoreAdapter {
11 Collection<AnyRelationView> getRelationViews();
12
13 Collection<AnyQuery> getQueries();
14
15 @Override
16 ModelQueryAdapter createModelAdapter(Model model);
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java
new file mode 100644
index 00000000..3f6bc06f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java
@@ -0,0 +1,13 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.map.Cursor;
4import tools.refinery.store.query.dnf.Query;
5import tools.refinery.store.tuple.TupleLike;
6
7public non-sealed interface ResultSet<T> extends AnyResultSet {
8 Query<T> getQuery();
9
10 T get(TupleLike parameters);
11
12 Cursor<TupleLike, T> getAll();
13}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java
new file mode 100644
index 00000000..d0a2367f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java
@@ -0,0 +1,11 @@
1package tools.refinery.store.query.dnf;
2
3public sealed interface AnyQuery permits Query {
4 String name();
5
6 int arity();
7
8 Class<?> valueType();
9
10 Dnf getDnf();
11}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java
new file mode 100644
index 00000000..1b7759c7
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java
@@ -0,0 +1,194 @@
1package tools.refinery.store.query.dnf;
2
3import tools.refinery.store.query.equality.DnfEqualityChecker;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.Constraint;
6import tools.refinery.store.query.literal.LiteralReduction;
7import tools.refinery.store.query.term.Sort;
8import tools.refinery.store.query.term.Variable;
9
10import java.util.Collection;
11import java.util.HashSet;
12import java.util.List;
13import java.util.Set;
14
15public final class Dnf implements Constraint {
16 private static final String INDENTATION = " ";
17
18 private final String name;
19
20 private final String uniqueName;
21
22 private final List<Variable> parameters;
23
24 private final List<FunctionalDependency<Variable>> functionalDependencies;
25
26 private final List<DnfClause> clauses;
27
28 Dnf(String name, List<Variable> parameters, List<FunctionalDependency<Variable>> functionalDependencies,
29 List<DnfClause> clauses) {
30 validateFunctionalDependencies(parameters, functionalDependencies);
31 this.name = name;
32 this.uniqueName = DnfUtils.generateUniqueName(name);
33 this.parameters = parameters;
34 this.functionalDependencies = functionalDependencies;
35 this.clauses = clauses;
36 }
37
38 private static void validateFunctionalDependencies(
39 Collection<Variable> parameters, Collection<FunctionalDependency<Variable>> functionalDependencies) {
40 var parameterSet = new HashSet<>(parameters);
41 for (var functionalDependency : functionalDependencies) {
42 validateParameters(parameters, parameterSet, functionalDependency.forEach(), functionalDependency);
43 validateParameters(parameters, parameterSet, functionalDependency.unique(), functionalDependency);
44 }
45 }
46
47 private static void validateParameters(Collection<Variable> parameters, Set<Variable> parameterSet,
48 Collection<Variable> toValidate,
49 FunctionalDependency<Variable> functionalDependency) {
50 for (var variable : toValidate) {
51 if (!parameterSet.contains(variable)) {
52 throw new IllegalArgumentException(
53 "Variable %s of functional dependency %s does not appear in the parameter list %s"
54 .formatted(variable, functionalDependency, parameters));
55 }
56 }
57 }
58
59 @Override
60 public String name() {
61 return name == null ? uniqueName : name;
62 }
63
64 public boolean isExplicitlyNamed() {
65 return name == null;
66 }
67
68 public String getUniqueName() {
69 return uniqueName;
70 }
71
72 public List<Variable> getParameters() {
73 return parameters;
74 }
75
76 @Override
77 public List<Sort> getSorts() {
78 return parameters.stream().map(Variable::getSort).toList();
79 }
80
81 public List<FunctionalDependency<Variable>> getFunctionalDependencies() {
82 return functionalDependencies;
83 }
84
85 @Override
86 public int arity() {
87 return parameters.size();
88 }
89
90 public List<DnfClause> getClauses() {
91 return clauses;
92 }
93
94 public RelationalQuery asRelation() {
95 return new RelationalQuery(this);
96 }
97
98 public <T> FunctionalQuery<T> asFunction(Class<T> type) {
99 return new FunctionalQuery<>(this, type);
100 }
101
102 @Override
103 public LiteralReduction getReduction() {
104 if (clauses.isEmpty()) {
105 return LiteralReduction.ALWAYS_FALSE;
106 }
107 for (var clause : clauses) {
108 if (clause.literals().isEmpty()) {
109 return LiteralReduction.ALWAYS_TRUE;
110 }
111 }
112 return LiteralReduction.NOT_REDUCIBLE;
113 }
114
115 public boolean equalsWithSubstitution(DnfEqualityChecker callEqualityChecker, Dnf other) {
116 if (arity() != other.arity()) {
117 return false;
118 }
119 int numClauses = clauses.size();
120 if (numClauses != other.clauses.size()) {
121 return false;
122 }
123 for (int i = 0; i < numClauses; i++) {
124 var literalEqualityHelper = new LiteralEqualityHelper(callEqualityChecker, parameters, other.parameters);
125 if (!clauses.get(i).equalsWithSubstitution(literalEqualityHelper, other.clauses.get(i))) {
126 return false;
127 }
128 }
129 return true;
130 }
131
132 @Override
133 public boolean equals(LiteralEqualityHelper helper, Constraint other) {
134 if (other instanceof Dnf otherDnf) {
135 return helper.dnfEqual(this, otherDnf);
136 }
137 return false;
138 }
139
140 @Override
141 public String toString() {
142 return "%s/%d".formatted(name, arity());
143 }
144
145 @Override
146 public String toReferenceString() {
147 return "@Dnf " + name;
148 }
149
150 public String toDefinitionString() {
151 var builder = new StringBuilder();
152 builder.append("pred ").append(name()).append("(");
153 var parameterIterator = parameters.iterator();
154 if (parameterIterator.hasNext()) {
155 builder.append(parameterIterator.next());
156 while (parameterIterator.hasNext()) {
157 builder.append(", ").append(parameterIterator.next());
158 }
159 }
160 builder.append(") <->");
161 var clauseIterator = clauses.iterator();
162 if (clauseIterator.hasNext()) {
163 appendClause(clauseIterator.next(), builder);
164 while (clauseIterator.hasNext()) {
165 builder.append("\n;");
166 appendClause(clauseIterator.next(), builder);
167 }
168 } else {
169 builder.append("\n").append(INDENTATION).append("<no clauses>");
170 }
171 builder.append(".\n");
172 return builder.toString();
173 }
174
175 private static void appendClause(DnfClause clause, StringBuilder builder) {
176 var iterator = clause.literals().iterator();
177 if (!iterator.hasNext()) {
178 builder.append("\n").append(INDENTATION).append("<empty>");
179 return;
180 }
181 builder.append("\n").append(INDENTATION).append(iterator.next());
182 while (iterator.hasNext()) {
183 builder.append(",\n").append(INDENTATION).append(iterator.next());
184 }
185 }
186
187 public static DnfBuilder builder() {
188 return builder(null);
189 }
190
191 public static DnfBuilder builder(String name) {
192 return new DnfBuilder(name);
193 }
194}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java
new file mode 100644
index 00000000..aad5a85f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java
@@ -0,0 +1,110 @@
1package tools.refinery.store.query.dnf;
2
3import tools.refinery.store.query.literal.Literal;
4import tools.refinery.store.query.term.DataVariable;
5import tools.refinery.store.query.term.Variable;
6
7import java.util.*;
8
9@SuppressWarnings("UnusedReturnValue")
10public final class DnfBuilder {
11 private final String name;
12
13 private final List<Variable> parameters = new ArrayList<>();
14
15 private final List<FunctionalDependency<Variable>> functionalDependencies = new ArrayList<>();
16
17 private final List<List<Literal>> clauses = new ArrayList<>();
18
19 DnfBuilder(String name) {
20 this.name = name;
21 }
22
23 public DnfBuilder parameter(Variable variable) {
24 if (parameters.contains(variable)) {
25 throw new IllegalArgumentException("Duplicate parameter: " + variable);
26 }
27 parameters.add(variable);
28 return this;
29 }
30
31 public DnfBuilder parameters(Variable... variables) {
32 return parameters(List.of(variables));
33 }
34
35 public DnfBuilder parameters(Collection<? extends Variable> variables) {
36 parameters.addAll(variables);
37 return this;
38 }
39
40 public DnfBuilder functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) {
41 this.functionalDependencies.addAll(functionalDependencies);
42 return this;
43 }
44
45 public DnfBuilder functionalDependency(FunctionalDependency<Variable> functionalDependency) {
46 functionalDependencies.add(functionalDependency);
47 return this;
48 }
49
50 public DnfBuilder functionalDependency(Set<? extends Variable> forEach, Set<? extends Variable> unique) {
51 return functionalDependency(new FunctionalDependency<>(Set.copyOf(forEach), Set.copyOf(unique)));
52 }
53
54 public DnfBuilder clause(Literal... literals) {
55 clause(List.of(literals));
56 return this;
57 }
58
59 public DnfBuilder clause(Collection<? extends Literal> literals) {
60 // Remove duplicates by using a hashed data structure.
61 var filteredLiterals = new LinkedHashSet<Literal>(literals.size());
62 for (var literal : literals) {
63 var reduction = literal.getReduction();
64 switch (reduction) {
65 case NOT_REDUCIBLE -> filteredLiterals.add(literal);
66 case ALWAYS_TRUE -> {
67 // Literals reducible to {@code true} can be omitted, because the model is always assumed to have at
68 // least on object.
69 }
70 case ALWAYS_FALSE -> {
71 // Clauses with {@code false} literals can be omitted entirely.
72 return this;
73 }
74 default -> throw new IllegalArgumentException("Invalid reduction: " + reduction);
75 }
76 }
77 clauses.add(List.copyOf(filteredLiterals));
78 return this;
79 }
80
81 public Dnf build() {
82 var postProcessedClauses = postProcessClauses();
83 return new Dnf(name, Collections.unmodifiableList(parameters),
84 Collections.unmodifiableList(functionalDependencies),
85 Collections.unmodifiableList(postProcessedClauses));
86 }
87
88 <T> void output(DataVariable<T> outputVariable) {
89 functionalDependency(Set.copyOf(parameters), Set.of(outputVariable));
90 parameter(outputVariable);
91 }
92
93 private List<DnfClause> postProcessClauses() {
94 var postProcessedClauses = new ArrayList<DnfClause>(clauses.size());
95 for (var literals : clauses) {
96 if (literals.isEmpty()) {
97 // Predicate will always match, the other clauses are irrelevant.
98 return List.of(new DnfClause(Set.of(), List.of()));
99 }
100 var variables = new HashSet<Variable>();
101 for (var literal : literals) {
102 variables.addAll(literal.getBoundVariables());
103 }
104 parameters.forEach(variables::remove);
105 postProcessedClauses.add(new DnfClause(Collections.unmodifiableSet(variables),
106 Collections.unmodifiableList(literals)));
107 }
108 return postProcessedClauses;
109 }
110}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java
new file mode 100644
index 00000000..01830af1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java
@@ -0,0 +1,23 @@
1package tools.refinery.store.query.dnf;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.literal.Literal;
5import tools.refinery.store.query.term.Variable;
6
7import java.util.List;
8import java.util.Set;
9
10public record DnfClause(Set<Variable> boundVariables, List<Literal> literals) {
11 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, DnfClause other) {
12 int size = literals.size();
13 if (size != other.literals.size()) {
14 return false;
15 }
16 for (int i = 0; i < size; i++) {
17 if (!literals.get(i).equalsWithSubstitution(helper, other.literals.get(i))) {
18 return false;
19 }
20 }
21 return true;
22 }
23}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java
new file mode 100644
index 00000000..9bcf944c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java
@@ -0,0 +1,19 @@
1package tools.refinery.store.query.dnf;
2
3import java.util.UUID;
4
5public final class DnfUtils {
6 private DnfUtils() {
7 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
8 }
9
10 public static String generateUniqueName(String originalName) {
11 UUID uuid = UUID.randomUUID();
12 String uniqueString = "_" + uuid.toString().replace('-', '_');
13 if (originalName == null) {
14 return uniqueString;
15 } else {
16 return originalName + uniqueString;
17 }
18 }
19}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java
new file mode 100644
index 00000000..f4cd109f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java
@@ -0,0 +1,15 @@
1package tools.refinery.store.query.dnf;
2
3import java.util.HashSet;
4import java.util.Set;
5
6public record FunctionalDependency<T>(Set<T> forEach, Set<T> unique) {
7 public FunctionalDependency {
8 var uniqueForEach = new HashSet<>(unique);
9 uniqueForEach.retainAll(forEach);
10 if (!uniqueForEach.isEmpty()) {
11 throw new IllegalArgumentException("Variables %s appear on both sides of the functional dependency"
12 .formatted(uniqueForEach));
13 }
14 }
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java
new file mode 100644
index 00000000..5bf6f8c5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java
@@ -0,0 +1,103 @@
1package tools.refinery.store.query.dnf;
2
3import tools.refinery.store.query.literal.CallPolarity;
4import tools.refinery.store.query.term.*;
5
6import java.util.ArrayList;
7import java.util.List;
8import java.util.Objects;
9
10public final class FunctionalQuery<T> implements Query<T> {
11 private final Dnf dnf;
12 private final Class<T> type;
13
14 FunctionalQuery(Dnf dnf, Class<T> type) {
15 var parameters = dnf.getParameters();
16 int outputIndex = dnf.arity() - 1;
17 for (int i = 0; i < outputIndex; i++) {
18 var parameter = parameters.get(i);
19 if (!(parameter instanceof NodeVariable)) {
20 throw new IllegalArgumentException("Expected parameter %s of %s to be of sort %s, but got %s instead"
21 .formatted(parameter, dnf, NodeSort.INSTANCE, parameter.getSort()));
22 }
23 }
24 var outputParameter = parameters.get(outputIndex);
25 if (!(outputParameter instanceof DataVariable<?> dataOutputParameter) ||
26 !dataOutputParameter.getType().equals(type)) {
27 throw new IllegalArgumentException("Expected parameter %s of %s to be of sort %s, but got %s instead"
28 .formatted(outputParameter, dnf, type, outputParameter.getSort()));
29 }
30 this.dnf = dnf;
31 this.type = type;
32 }
33
34 @Override
35 public String name() {
36 return dnf.name();
37 }
38
39 @Override
40 public int arity() {
41 return dnf.arity() - 1;
42 }
43
44 @Override
45 public Class<T> valueType() {
46 return type;
47 }
48
49 @Override
50 public T defaultValue() {
51 return null;
52 }
53
54 @Override
55 public Dnf getDnf() {
56 return dnf;
57 }
58
59 public AssignedValue<T> call(List<NodeVariable> arguments) {
60 return targetVariable -> {
61 var argumentsWithTarget = new ArrayList<Variable>(arguments.size() + 1);
62 argumentsWithTarget.addAll(arguments);
63 argumentsWithTarget.add(targetVariable);
64 return dnf.call(CallPolarity.POSITIVE, argumentsWithTarget);
65 };
66 }
67
68 public AssignedValue<T> call(NodeVariable... arguments) {
69 return call(List.of(arguments));
70 }
71
72 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, List<NodeVariable> arguments) {
73 return targetVariable -> {
74 var placeholderVariable = Variable.of(type);
75 var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1);
76 argumentsWithPlaceholder.addAll(arguments);
77 argumentsWithPlaceholder.add(placeholderVariable);
78 return dnf.aggregate(placeholderVariable, aggregator, argumentsWithPlaceholder).toLiteral(targetVariable);
79 };
80 }
81
82 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, NodeVariable... arguments) {
83 return aggregate(aggregator, List.of(arguments));
84 }
85
86 @Override
87 public boolean equals(Object o) {
88 if (this == o) return true;
89 if (o == null || getClass() != o.getClass()) return false;
90 FunctionalQuery<?> that = (FunctionalQuery<?>) o;
91 return dnf.equals(that.dnf) && type.equals(that.type);
92 }
93
94 @Override
95 public int hashCode() {
96 return Objects.hash(dnf, type);
97 }
98
99 @Override
100 public String toString() {
101 return dnf.toString();
102 }
103}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java
new file mode 100644
index 00000000..ca2bc006
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java
@@ -0,0 +1,46 @@
1package tools.refinery.store.query.dnf;
2
3import tools.refinery.store.query.literal.Literal;
4import tools.refinery.store.query.term.Variable;
5
6import java.util.Collection;
7import java.util.Set;
8
9public final class FunctionalQueryBuilder<T> {
10 private final DnfBuilder dnfBuilder;
11 private final Class<T> type;
12
13 FunctionalQueryBuilder(DnfBuilder dnfBuilder, Class<T> type) {
14 this.dnfBuilder = dnfBuilder;
15 this.type = type;
16 }
17
18 public FunctionalQueryBuilder<T> functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) {
19 dnfBuilder.functionalDependencies(functionalDependencies);
20 return this;
21 }
22
23 public FunctionalQueryBuilder<T> functionalDependency(FunctionalDependency<Variable> functionalDependency) {
24 dnfBuilder.functionalDependency(functionalDependency);
25 return this;
26 }
27
28 public FunctionalQueryBuilder<T> functionalDependency(Set<? extends Variable> forEach, Set<? extends Variable> unique) {
29 dnfBuilder.functionalDependency(forEach, unique);
30 return this;
31 }
32
33 public FunctionalQueryBuilder<T> clause(Literal... literals) {
34 dnfBuilder.clause(literals);
35 return this;
36 }
37
38 public FunctionalQueryBuilder<T> clause(Collection<? extends Literal> literals) {
39 dnfBuilder.clause(literals);
40 return this;
41 }
42
43 public FunctionalQuery<T> build() {
44 return dnfBuilder.build().asFunction(type);
45 }
46}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java
new file mode 100644
index 00000000..32e33052
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java
@@ -0,0 +1,16 @@
1package tools.refinery.store.query.dnf;
2
3public sealed interface Query<T> extends AnyQuery permits RelationalQuery, FunctionalQuery {
4 @Override
5 Class<T> valueType();
6
7 T defaultValue();
8
9 static QueryBuilder builder() {
10 return new QueryBuilder();
11 }
12
13 static QueryBuilder builder(String name) {
14 return new QueryBuilder(name);
15 }
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java
new file mode 100644
index 00000000..ed253cc9
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java
@@ -0,0 +1,71 @@
1package tools.refinery.store.query.dnf;
2
3import tools.refinery.store.query.literal.Literal;
4import tools.refinery.store.query.term.DataVariable;
5import tools.refinery.store.query.term.NodeVariable;
6import tools.refinery.store.query.term.Variable;
7
8import java.util.Collection;
9import java.util.List;
10import java.util.Set;
11
12public final class QueryBuilder {
13 private final DnfBuilder dnfBuilder;
14
15 QueryBuilder(String name) {
16 dnfBuilder = Dnf.builder(name);
17 }
18
19 QueryBuilder() {
20 dnfBuilder = Dnf.builder();
21 }
22
23 public QueryBuilder parameter(NodeVariable variable) {
24 dnfBuilder.parameter(variable);
25 return this;
26 }
27
28 public QueryBuilder parameters(NodeVariable... variables) {
29 dnfBuilder.parameters(variables);
30 return this;
31 }
32
33 public QueryBuilder parameters(List<NodeVariable> variables) {
34 dnfBuilder.parameters(variables);
35 return this;
36 }
37
38 public <T> FunctionalQueryBuilder<T> output(DataVariable<T> outputVariable) {
39 dnfBuilder.output(outputVariable);
40 return new FunctionalQueryBuilder<>(dnfBuilder, outputVariable.getType());
41 }
42
43 public QueryBuilder functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) {
44 dnfBuilder.functionalDependencies(functionalDependencies);
45 return this;
46 }
47
48 public QueryBuilder functionalDependency(FunctionalDependency<Variable> functionalDependency) {
49 dnfBuilder.functionalDependency(functionalDependency);
50 return this;
51 }
52
53 public QueryBuilder functionalDependency(Set<? extends Variable> forEach, Set<? extends Variable> unique) {
54 dnfBuilder.functionalDependency(forEach, unique);
55 return this;
56 }
57
58 public QueryBuilder clause(Literal... literals) {
59 dnfBuilder.clause(literals);
60 return this;
61 }
62
63 public QueryBuilder clause(Collection<? extends Literal> literals) {
64 dnfBuilder.clause(literals);
65 return this;
66 }
67
68 public RelationalQuery build() {
69 return dnfBuilder.build().asRelation();
70 }
71}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java
new file mode 100644
index 00000000..5307e509
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java
@@ -0,0 +1,93 @@
1package tools.refinery.store.query.dnf;
2
3import tools.refinery.store.query.literal.CallLiteral;
4import tools.refinery.store.query.literal.CallPolarity;
5import tools.refinery.store.query.term.AssignedValue;
6import tools.refinery.store.query.term.NodeSort;
7import tools.refinery.store.query.term.NodeVariable;
8
9import java.util.Collections;
10import java.util.List;
11import java.util.Objects;
12
13public final class RelationalQuery implements Query<Boolean> {
14 private final Dnf dnf;
15
16 RelationalQuery(Dnf dnf) {
17 for (var parameter : dnf.getParameters()) {
18 if (!(parameter instanceof NodeVariable)) {
19 throw new IllegalArgumentException("Expected parameter %s of %s to be of sort %s, but got %s instead"
20 .formatted(parameter, dnf, NodeSort.INSTANCE, parameter.getSort()));
21 }
22 }
23 this.dnf = dnf;
24 }
25
26 @Override
27 public String name() {
28 return dnf.name();
29 }
30
31 @Override
32 public int arity() {
33 return dnf.arity();
34 }
35
36 @Override
37 public Class<Boolean> valueType() {
38 return Boolean.class;
39 }
40
41 @Override
42 public Boolean defaultValue() {
43 return false;
44 }
45
46 @Override
47 public Dnf getDnf() {
48 return dnf;
49 }
50
51 public CallLiteral call(CallPolarity polarity, List<NodeVariable> arguments) {
52 return dnf.call(polarity, Collections.unmodifiableList(arguments));
53 }
54
55 public CallLiteral call(CallPolarity polarity, NodeVariable... arguments) {
56 return dnf.call(polarity, arguments);
57 }
58
59 public CallLiteral call(NodeVariable... arguments) {
60 return dnf.call(arguments);
61 }
62
63 public CallLiteral callTransitive(NodeVariable left, NodeVariable right) {
64 return dnf.callTransitive(left, right);
65 }
66
67 public AssignedValue<Integer> count(List<NodeVariable> arguments) {
68 return dnf.count(Collections.unmodifiableList(arguments));
69 }
70
71 public AssignedValue<Integer> count(NodeVariable... arguments) {
72 return dnf.count(arguments);
73 }
74
75
76 @Override
77 public boolean equals(Object o) {
78 if (this == o) return true;
79 if (o == null || getClass() != o.getClass()) return false;
80 RelationalQuery that = (RelationalQuery) o;
81 return dnf.equals(that.dnf);
82 }
83
84 @Override
85 public int hashCode() {
86 return Objects.hash(dnf);
87 }
88
89 @Override
90 public String toString() {
91 return dnf.toString();
92 }
93}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java
new file mode 100644
index 00000000..c3bc3ea3
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java
@@ -0,0 +1,31 @@
1package tools.refinery.store.query.equality;
2
3import tools.refinery.store.query.dnf.Dnf;
4import tools.refinery.store.util.CycleDetectingMapper;
5
6import java.util.List;
7
8public class DeepDnfEqualityChecker implements DnfEqualityChecker {
9 private final CycleDetectingMapper<Pair, Boolean> mapper = new CycleDetectingMapper<>(Pair::toString,
10 this::doCheckEqual);
11
12 @Override
13 public boolean dnfEqual(Dnf left, Dnf right) {
14 return mapper.map(new Pair(left, right));
15 }
16
17 protected boolean doCheckEqual(Pair pair) {
18 return pair.left.equalsWithSubstitution(this, pair.right);
19 }
20
21 protected List<Pair> getInProgress() {
22 return mapper.getInProgress();
23 }
24
25 protected record Pair(Dnf left, Dnf right) {
26 @Override
27 public String toString() {
28 return "(%s, %s)".formatted(left.name(), right.name());
29 }
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java
new file mode 100644
index 00000000..6b1f2076
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java
@@ -0,0 +1,8 @@
1package tools.refinery.store.query.equality;
2
3import tools.refinery.store.query.dnf.Dnf;
4
5@FunctionalInterface
6public interface DnfEqualityChecker {
7 boolean dnfEqual(Dnf left, Dnf right);
8}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java
new file mode 100644
index 00000000..07d261ea
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java
@@ -0,0 +1,48 @@
1package tools.refinery.store.query.equality;
2
3import tools.refinery.store.query.dnf.Dnf;
4import tools.refinery.store.query.term.Variable;
5
6import java.util.HashMap;
7import java.util.List;
8import java.util.Map;
9
10public class LiteralEqualityHelper {
11 private final DnfEqualityChecker dnfEqualityChecker;
12 private final Map<Variable, Variable> leftToRight;
13 private final Map<Variable, Variable> rightToLeft;
14
15 public LiteralEqualityHelper(DnfEqualityChecker dnfEqualityChecker, List<Variable> leftParameters,
16 List<Variable> rightParameters) {
17 this.dnfEqualityChecker = dnfEqualityChecker;
18 var arity = leftParameters.size();
19 if (arity != rightParameters.size()) {
20 throw new IllegalArgumentException("Parameter lists have unequal length");
21 }
22 leftToRight = new HashMap<>(arity);
23 rightToLeft = new HashMap<>(arity);
24 for (int i = 0; i < arity; i++) {
25 if (!variableEqual(leftParameters.get(i), rightParameters.get(i))) {
26 throw new IllegalArgumentException("Parameter lists cannot be unified: duplicate parameter " + i);
27 }
28 }
29 }
30
31 public boolean dnfEqual(Dnf left, Dnf right) {
32 return dnfEqualityChecker.dnfEqual(left, right);
33 }
34
35 public boolean variableEqual(Variable left, Variable right) {
36 if (checkMapping(leftToRight, left, right) && checkMapping(rightToLeft, right, left)) {
37 leftToRight.put(left, right);
38 rightToLeft.put(right, left);
39 return true;
40 }
41 return false;
42 }
43
44 private static boolean checkMapping(Map<Variable, Variable> map, Variable key, Variable expectedValue) {
45 var currentValue = map.get(key);
46 return currentValue == null || currentValue.equals(expectedValue);
47 }
48}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java
new file mode 100644
index 00000000..657ca26b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java
@@ -0,0 +1,80 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.Constraint;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.substitution.Substitution;
6import tools.refinery.store.query.term.Variable;
7
8import java.util.List;
9import java.util.Objects;
10
11public abstract class AbstractCallLiteral implements Literal {
12 private final Constraint target;
13 private final List<Variable> arguments;
14
15 protected AbstractCallLiteral(Constraint target, List<Variable> arguments) {
16 int arity = target.arity();
17 if (arguments.size() != arity) {
18 throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(),
19 target.arity(), arguments.size()));
20 }
21 this.target = target;
22 this.arguments = arguments;
23 var sorts = target.getSorts();
24 for (int i = 0; i < arity; i++) {
25 var argument = arguments.get(i);
26 var sort = sorts.get(i);
27 if (!sort.isInstance(argument)) {
28 throw new IllegalArgumentException("Required argument %d of %s to be of sort %s, but got %s instead"
29 .formatted(i, target, sort, argument.getSort()));
30 }
31 }
32 }
33
34 public Constraint getTarget() {
35 return target;
36 }
37
38 public List<Variable> getArguments() {
39 return arguments;
40 }
41
42 @Override
43 public Literal substitute(Substitution substitution) {
44 var substitutedArguments = arguments.stream().map(substitution::getSubstitute).toList();
45 return doSubstitute(substitution, substitutedArguments);
46 }
47
48 protected abstract Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments);
49
50 @Override
51 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
52 if (other == null || getClass() != other.getClass()) {
53 return false;
54 }
55 var otherCallLiteral = (AbstractCallLiteral) other;
56 var arity = arguments.size();
57 if (arity != otherCallLiteral.arguments.size()) {
58 return false;
59 }
60 for (int i = 0; i < arity; i++) {
61 if (!helper.variableEqual(arguments.get(i), otherCallLiteral.arguments.get(i))) {
62 return false;
63 }
64 }
65 return target.equals(helper, otherCallLiteral.target);
66 }
67
68 @Override
69 public boolean equals(Object o) {
70 if (this == o) return true;
71 if (o == null || getClass() != o.getClass()) return false;
72 AbstractCallLiteral that = (AbstractCallLiteral) o;
73 return target.equals(that.target) && arguments.equals(that.arguments);
74 }
75
76 @Override
77 public int hashCode() {
78 return Objects.hash(target, arguments);
79 }
80}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java
new file mode 100644
index 00000000..df64839c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java
@@ -0,0 +1,113 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.Constraint;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.substitution.Substitution;
6import tools.refinery.store.query.term.Aggregator;
7import tools.refinery.store.query.term.DataVariable;
8import tools.refinery.store.query.term.Variable;
9
10import java.util.List;
11import java.util.Objects;
12import java.util.Set;
13
14public class AggregationLiteral<R, T> extends AbstractCallLiteral {
15 private final DataVariable<R> resultVariable;
16 private final DataVariable<T> inputVariable;
17 private final Aggregator<R, T> aggregator;
18
19 public AggregationLiteral(DataVariable<R> resultVariable, Aggregator<R, T> aggregator,
20 DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) {
21 super(target, arguments);
22 if (!inputVariable.getType().equals(aggregator.getInputType())) {
23 throw new IllegalArgumentException("Input variable %s must of type %s, got %s instead".formatted(
24 inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName()));
25 }
26 if (!resultVariable.getType().equals(aggregator.getResultType())) {
27 throw new IllegalArgumentException("Result variable %s must of type %s, got %s instead".formatted(
28 resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName()));
29 }
30 if (!arguments.contains(inputVariable)) {
31 throw new IllegalArgumentException("Input variable %s must appear in the argument list".formatted(
32 inputVariable));
33 }
34 if (arguments.contains(resultVariable)) {
35 throw new IllegalArgumentException("Result variable %s must not appear in the argument list".formatted(
36 resultVariable));
37 }
38 this.resultVariable = resultVariable;
39 this.inputVariable = inputVariable;
40 this.aggregator = aggregator;
41 }
42
43 public DataVariable<R> getResultVariable() {
44 return resultVariable;
45 }
46
47 public DataVariable<T> getInputVariable() {
48 return inputVariable;
49 }
50
51 public Aggregator<R, T> getAggregator() {
52 return aggregator;
53 }
54
55 @Override
56 public Set<Variable> getBoundVariables() {
57 return Set.of(resultVariable);
58 }
59
60 @Override
61 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
62 return new AggregationLiteral<>(substitution.getTypeSafeSubstitute(resultVariable), aggregator,
63 substitution.getTypeSafeSubstitute(inputVariable), getTarget(), substitutedArguments);
64 }
65
66 @Override
67 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
68 if (!super.equalsWithSubstitution(helper, other)) {
69 return false;
70 }
71 var otherAggregationLiteral = (AggregationLiteral<?, ?>) other;
72 return helper.variableEqual(resultVariable, otherAggregationLiteral.resultVariable) &&
73 aggregator.equals(otherAggregationLiteral.aggregator) &&
74 helper.variableEqual(inputVariable, otherAggregationLiteral.inputVariable);
75 }
76
77 @Override
78 public boolean equals(Object o) {
79 if (this == o) return true;
80 if (o == null || getClass() != o.getClass()) return false;
81 if (!super.equals(o)) return false;
82 AggregationLiteral<?, ?> that = (AggregationLiteral<?, ?>) o;
83 return resultVariable.equals(that.resultVariable) && inputVariable.equals(that.inputVariable) &&
84 aggregator.equals(that.aggregator);
85 }
86
87 @Override
88 public int hashCode() {
89 return Objects.hash(super.hashCode(), resultVariable, inputVariable, aggregator);
90 }
91
92 @Override
93 public String toString() {
94 var builder = new StringBuilder();
95 builder.append(resultVariable);
96 builder.append(" is ");
97 builder.append(getTarget().toReferenceString());
98 builder.append("(");
99 var argumentIterator = getArguments().iterator();
100 if (argumentIterator.hasNext()) {
101 var argument = argumentIterator.next();
102 if (inputVariable.equals(argument)) {
103 builder.append("@Aggregate(\"").append(aggregator).append("\") ");
104 }
105 builder.append(argument);
106 while (argumentIterator.hasNext()) {
107 builder.append(", ").append(argumentIterator.next());
108 }
109 }
110 builder.append(")");
111 return builder.toString();
112 }
113}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java
new file mode 100644
index 00000000..52ac42d7
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java
@@ -0,0 +1,44 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.term.DataVariable;
6import tools.refinery.store.query.term.Term;
7import tools.refinery.store.query.term.Variable;
8
9import java.util.Set;
10
11public record AssignLiteral<T>(DataVariable<T> variable, Term<T> term) implements Literal {
12 public AssignLiteral {
13 if (!term.getType().equals(variable.getType())) {
14 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted(
15 term, variable.getType().getName(), term.getType().getName()));
16 }
17 }
18
19 @Override
20 public Set<Variable> getBoundVariables() {
21 return Set.of(variable);
22 }
23
24 @Override
25 public Literal substitute(Substitution substitution) {
26 return new AssignLiteral<>(substitution.getTypeSafeSubstitute(variable), term.substitute(substitution));
27 }
28
29 @Override
30 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
31 if (other == null || getClass() != other.getClass()) {
32 return false;
33 }
34 var otherLetLiteral = (AssignLiteral<?>) other;
35 return helper.variableEqual(variable, otherLetLiteral.variable) && term.equalsWithSubstitution(helper,
36 otherLetLiteral.term);
37 }
38
39
40 @Override
41 public String toString() {
42 return "%s is (%s)".formatted(variable, term);
43 }
44}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java
new file mode 100644
index 00000000..0b4267b4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java
@@ -0,0 +1,53 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.term.Term;
6import tools.refinery.store.query.term.Variable;
7import tools.refinery.store.query.term.bool.BoolConstantTerm;
8
9import java.util.Set;
10
11public record AssumeLiteral(Term<Boolean> term) implements Literal {
12 public AssumeLiteral {
13 if (!term.getType().equals(Boolean.class)) {
14 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted(
15 term, Boolean.class.getName(), term.getType().getName()));
16 }
17 }
18
19 @Override
20 public Set<Variable> getBoundVariables() {
21 return Set.of();
22 }
23
24 @Override
25 public Literal substitute(Substitution substitution) {
26 return new AssumeLiteral(term.substitute(substitution));
27 }
28
29 @Override
30 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
31 if (other == null || getClass() != other.getClass()) {
32 return false;
33 }
34 var otherAssumeLiteral = (AssumeLiteral) other;
35 return term.equalsWithSubstitution(helper, otherAssumeLiteral.term);
36 }
37
38 @Override
39 public LiteralReduction getReduction() {
40 if (BoolConstantTerm.TRUE.equals(term)) {
41 return LiteralReduction.ALWAYS_TRUE;
42 } else if (BoolConstantTerm.FALSE.equals(term)) {
43 return LiteralReduction.ALWAYS_FALSE;
44 } else {
45 return LiteralReduction.NOT_REDUCIBLE;
46 }
47 }
48
49 @Override
50 public String toString() {
51 return "(%s)".formatted(term);
52 }
53}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java
new file mode 100644
index 00000000..38be61a4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java
@@ -0,0 +1,53 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.term.Variable;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.substitution.Substitution;
6
7import java.util.Set;
8
9public enum BooleanLiteral implements CanNegate<BooleanLiteral> {
10 TRUE(true),
11 FALSE(false);
12
13 private final boolean value;
14
15 BooleanLiteral(boolean value) {
16 this.value = value;
17 }
18
19 @Override
20 public Set<Variable> getBoundVariables() {
21 return Set.of();
22 }
23
24 @Override
25 public Literal substitute(Substitution substitution) {
26 // No variables to substitute.
27 return this;
28 }
29
30 @Override
31 public LiteralReduction getReduction() {
32 return value ? LiteralReduction.ALWAYS_TRUE : LiteralReduction.ALWAYS_FALSE;
33 }
34
35 @Override
36 public BooleanLiteral negate() {
37 return fromBoolean(!value);
38 }
39
40 @Override
41 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
42 return equals(other);
43 }
44
45 @Override
46 public String toString() {
47 return Boolean.toString(value);
48 }
49
50 public static BooleanLiteral fromBoolean(boolean value) {
51 return value ? TRUE : FALSE;
52 }
53}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java
new file mode 100644
index 00000000..78fae7f5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java
@@ -0,0 +1,102 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.Constraint;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.substitution.Substitution;
6import tools.refinery.store.query.term.NodeSort;
7import tools.refinery.store.query.term.Variable;
8
9import java.util.List;
10import java.util.Objects;
11import java.util.Set;
12
13public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> {
14 private final CallPolarity polarity;
15
16 public CallLiteral(CallPolarity polarity, Constraint target, List<Variable> arguments) {
17 super(target, arguments);
18 if (polarity.isTransitive()) {
19 if (target.arity() != 2) {
20 throw new IllegalArgumentException("Transitive closures can only take binary relations");
21 }
22 var sorts = target.getSorts();
23 if (!sorts.get(0).equals(NodeSort.INSTANCE) || !sorts.get(1).equals(NodeSort.INSTANCE)) {
24 throw new IllegalArgumentException("Transitive closures can only be computed over nodes");
25 }
26 }
27 this.polarity = polarity;
28 }
29
30 public CallPolarity getPolarity() {
31 return polarity;
32 }
33
34 @Override
35 public Set<Variable> getBoundVariables() {
36 return polarity.isPositive() ? Set.copyOf(getArguments()) : Set.of();
37 }
38
39 @Override
40 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
41 return new CallLiteral(polarity, getTarget(), substitutedArguments);
42 }
43
44 @Override
45 public LiteralReduction getReduction() {
46 var reduction = getTarget().getReduction();
47 return polarity.isPositive() ? reduction : reduction.negate();
48 }
49
50 @Override
51 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
52 if (!super.equalsWithSubstitution(helper, other)) {
53 return false;
54 }
55 var otherCallLiteral = (CallLiteral) other;
56 return polarity.equals(otherCallLiteral.polarity);
57 }
58
59 @Override
60 public CallLiteral negate() {
61 return new CallLiteral(polarity.negate(), getTarget(), getArguments());
62 }
63
64 @Override
65 public boolean equals(Object o) {
66 if (this == o) return true;
67 if (o == null || getClass() != o.getClass()) return false;
68 if (!super.equals(o)) return false;
69 CallLiteral that = (CallLiteral) o;
70 return polarity == that.polarity;
71 }
72
73 @Override
74 public int hashCode() {
75 return Objects.hash(super.hashCode(), polarity);
76 }
77
78 @Override
79 public String toString() {
80 var builder = new StringBuilder();
81 if (!polarity.isPositive()) {
82 builder.append("!(");
83 }
84 builder.append(getTarget().toReferenceString());
85 if (polarity.isTransitive()) {
86 builder.append("+");
87 }
88 builder.append("(");
89 var argumentIterator = getArguments().iterator();
90 if (argumentIterator.hasNext()) {
91 builder.append(argumentIterator.next());
92 while (argumentIterator.hasNext()) {
93 builder.append(", ").append(argumentIterator.next());
94 }
95 }
96 builder.append(")");
97 if (!polarity.isPositive()) {
98 builder.append(")");
99 }
100 return builder.toString();
101 }
102}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java
new file mode 100644
index 00000000..84b4b771
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java
@@ -0,0 +1,32 @@
1package tools.refinery.store.query.literal;
2
3public enum CallPolarity {
4 POSITIVE(true, false),
5 NEGATIVE(false, false),
6 TRANSITIVE(true, true);
7
8 private final boolean positive;
9
10 private final boolean transitive;
11
12 CallPolarity(boolean positive, boolean transitive) {
13 this.positive = positive;
14 this.transitive = transitive;
15 }
16
17 public boolean isPositive() {
18 return positive;
19 }
20
21 public boolean isTransitive() {
22 return transitive;
23 }
24
25 public CallPolarity negate() {
26 return switch (this) {
27 case POSITIVE -> NEGATIVE;
28 case NEGATIVE -> POSITIVE;
29 case TRANSITIVE -> throw new IllegalArgumentException("Transitive polarity cannot be negated");
30 };
31 }
32}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java
new file mode 100644
index 00000000..3e159c43
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java
@@ -0,0 +1,5 @@
1package tools.refinery.store.query.literal;
2
3public interface CanNegate<T extends CanNegate<T>> extends Literal {
4 T negate();
5}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java
new file mode 100644
index 00000000..93fa3df0
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java
@@ -0,0 +1,34 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.term.NodeVariable;
6import tools.refinery.store.query.term.Variable;
7
8import java.util.Set;
9
10public record ConstantLiteral(NodeVariable variable, int nodeId) implements Literal {
11 @Override
12 public Set<Variable> getBoundVariables() {
13 return Set.of(variable);
14 }
15
16 @Override
17 public ConstantLiteral substitute(Substitution substitution) {
18 return new ConstantLiteral(substitution.getTypeSafeSubstitute(variable), nodeId);
19 }
20
21 @Override
22 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
23 if (other.getClass() != getClass()) {
24 return false;
25 }
26 var otherConstantLiteral = (ConstantLiteral) other;
27 return helper.variableEqual(variable, otherConstantLiteral.variable) && nodeId == otherConstantLiteral.nodeId;
28 }
29
30 @Override
31 public String toString() {
32 return "%s === @Constant %d".formatted(variable, nodeId);
33 }
34}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java
new file mode 100644
index 00000000..32e7ba3a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java
@@ -0,0 +1,83 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.Constraint;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.substitution.Substitution;
6import tools.refinery.store.query.term.DataVariable;
7import tools.refinery.store.query.term.Variable;
8
9import java.util.List;
10import java.util.Objects;
11import java.util.Set;
12
13public class CountLiteral extends AbstractCallLiteral {
14 private final DataVariable<Integer> resultVariable;
15
16 public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) {
17 super(target, arguments);
18 if (!resultVariable.getType().equals(Integer.class)) {
19 throw new IllegalArgumentException("Count result variable %s must be of type %s, got %s instead".formatted(
20 resultVariable, Integer.class.getName(), resultVariable.getType().getName()));
21 }
22 if (arguments.contains(resultVariable)) {
23 throw new IllegalArgumentException("Count result variable %s must not appear in the argument list"
24 .formatted(resultVariable));
25 }
26 this.resultVariable = resultVariable;
27 }
28
29 public DataVariable<Integer> getResultVariable() {
30 return resultVariable;
31 }
32
33 @Override
34 public Set<Variable> getBoundVariables() {
35 return Set.of(resultVariable);
36 }
37
38 @Override
39 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
40 return new CountLiteral(substitution.getTypeSafeSubstitute(resultVariable), getTarget(), substitutedArguments);
41 }
42
43 @Override
44 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
45 if (!super.equalsWithSubstitution(helper, other)) {
46 return false;
47 }
48 var otherCountLiteral = (CountLiteral) other;
49 return helper.variableEqual(resultVariable, otherCountLiteral.resultVariable);
50 }
51
52 @Override
53 public boolean equals(Object o) {
54 if (this == o) return true;
55 if (o == null || getClass() != o.getClass()) return false;
56 if (!super.equals(o)) return false;
57 CountLiteral that = (CountLiteral) o;
58 return resultVariable.equals(that.resultVariable);
59 }
60
61 @Override
62 public int hashCode() {
63 return Objects.hash(super.hashCode(), resultVariable);
64 }
65
66 @Override
67 public String toString() {
68 var builder = new StringBuilder();
69 builder.append(resultVariable);
70 builder.append(" is count ");
71 builder.append(getTarget().toReferenceString());
72 builder.append("(");
73 var argumentIterator = getArguments().iterator();
74 if (argumentIterator.hasNext()) {
75 builder.append(argumentIterator.next());
76 while (argumentIterator.hasNext()) {
77 builder.append(", ").append(argumentIterator.next());
78 }
79 }
80 builder.append(")");
81 return builder.toString();
82 }
83}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java
new file mode 100644
index 00000000..4dc86b98
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java
@@ -0,0 +1,52 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.term.NodeVariable;
4import tools.refinery.store.query.term.Variable;
5import tools.refinery.store.query.equality.LiteralEqualityHelper;
6import tools.refinery.store.query.substitution.Substitution;
7
8import java.util.Set;
9
10public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariable right)
11 implements CanNegate<EquivalenceLiteral> {
12 @Override
13 public Set<Variable> getBoundVariables() {
14 // If one side of a {@code positive} equivalence is bound, it may bind its other side, but we under-approximate
15 // this behavior by not binding any of the sides by default.
16 return Set.of();
17 }
18
19 @Override
20 public EquivalenceLiteral negate() {
21 return new EquivalenceLiteral(!positive, left, right);
22 }
23
24 @Override
25 public EquivalenceLiteral substitute(Substitution substitution) {
26 return new EquivalenceLiteral(positive, substitution.getTypeSafeSubstitute(left),
27 substitution.getTypeSafeSubstitute(right));
28 }
29
30 @Override
31 public LiteralReduction getReduction() {
32 if (left.equals(right)) {
33 return positive ? LiteralReduction.ALWAYS_TRUE : LiteralReduction.ALWAYS_FALSE;
34 }
35 return LiteralReduction.NOT_REDUCIBLE;
36 }
37
38 @Override
39 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
40 if (other.getClass() != getClass()) {
41 return false;
42 }
43 var otherEquivalenceLiteral = (EquivalenceLiteral) other;
44 return helper.variableEqual(left, otherEquivalenceLiteral.left) && helper.variableEqual(right,
45 otherEquivalenceLiteral.right);
46 }
47
48 @Override
49 public String toString() {
50 return "%s %s %s".formatted(left, positive ? "===" : "!==", right);
51 }
52}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java
new file mode 100644
index 00000000..6347410e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java
@@ -0,0 +1,20 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.term.Variable;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.substitution.Substitution;
6
7import java.util.Set;
8
9public interface Literal {
10 Set<Variable> getBoundVariables();
11
12 Literal substitute(Substitution substitution);
13
14 default LiteralReduction getReduction() {
15 return LiteralReduction.NOT_REDUCIBLE;
16 }
17
18 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
19 boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other);
20}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java
new file mode 100644
index 00000000..146089f6
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java
@@ -0,0 +1,26 @@
1package tools.refinery.store.query.literal;
2
3public enum LiteralReduction {
4 /**
5 * Signifies that a literal should be preserved in the clause.
6 */
7 NOT_REDUCIBLE,
8
9 /**
10 * Signifies that the literal may be omitted from the cause (if the model being queried is nonempty).
11 */
12 ALWAYS_TRUE,
13
14 /**
15 * Signifies that the clause with the literal may be omitted entirely.
16 */
17 ALWAYS_FALSE;
18
19 public LiteralReduction negate() {
20 return switch (this) {
21 case NOT_REDUCIBLE -> NOT_REDUCIBLE;
22 case ALWAYS_TRUE -> ALWAYS_FALSE;
23 case ALWAYS_FALSE -> ALWAYS_TRUE;
24 };
25 }
26}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java
new file mode 100644
index 00000000..89039352
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java
@@ -0,0 +1,17 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.term.Term;
4
5public final class Literals {
6 private Literals() {
7 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
8 }
9
10 public static <T extends CanNegate<T>> T not(CanNegate<T> literal) {
11 return literal.negate();
12 }
13
14 public static AssumeLiteral assume(Term<Boolean> term) {
15 return new AssumeLiteral(term);
16 }
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/CompositeSubstitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/CompositeSubstitution.java
new file mode 100644
index 00000000..f8064ca2
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/CompositeSubstitution.java
@@ -0,0 +1,10 @@
1package tools.refinery.store.query.substitution;
2
3import tools.refinery.store.query.term.Variable;
4
5public record CompositeSubstitution(Substitution first, Substitution second) implements Substitution {
6 @Override
7 public Variable getSubstitute(Variable variable) {
8 return second.getSubstitute(first.getSubstitute(variable));
9 }
10}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java
new file mode 100644
index 00000000..c7754619
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java
@@ -0,0 +1,13 @@
1package tools.refinery.store.query.substitution;
2
3import tools.refinery.store.query.term.Variable;
4
5import java.util.Map;
6
7public record MapBasedSubstitution(Map<Variable, Variable> map, Substitution fallback) implements Substitution {
8 @Override
9 public Variable getSubstitute(Variable variable) {
10 var value = map.get(variable);
11 return value == null ? fallback.getSubstitute(variable) : value;
12 }
13}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java
new file mode 100644
index 00000000..7847e582
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java
@@ -0,0 +1,15 @@
1package tools.refinery.store.query.substitution;
2
3import tools.refinery.store.query.term.Variable;
4
5import java.util.HashMap;
6import java.util.Map;
7
8public class RenewingSubstitution implements Substitution {
9 private final Map<Variable, Variable> alreadyRenewed = new HashMap<>();
10
11 @Override
12 public Variable getSubstitute(Variable variable) {
13 return alreadyRenewed.computeIfAbsent(variable, Variable::renew);
14 }
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java
new file mode 100644
index 00000000..eed414d9
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java
@@ -0,0 +1,18 @@
1package tools.refinery.store.query.substitution;
2
3import tools.refinery.store.query.term.Variable;
4
5public enum StatelessSubstitution implements Substitution {
6 FAILING {
7 @Override
8 public Variable getSubstitute(Variable variable) {
9 throw new IllegalArgumentException("No substitute for " + variable);
10 }
11 },
12 IDENTITY {
13 @Override
14 public Variable getSubstitute(Variable variable) {
15 return variable;
16 }
17 }
18}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java
new file mode 100644
index 00000000..99f84b9e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java
@@ -0,0 +1,29 @@
1package tools.refinery.store.query.substitution;
2
3import tools.refinery.store.query.term.AnyDataVariable;
4import tools.refinery.store.query.term.DataVariable;
5import tools.refinery.store.query.term.NodeVariable;
6import tools.refinery.store.query.term.Variable;
7
8@FunctionalInterface
9public interface Substitution {
10 Variable getSubstitute(Variable variable);
11
12 default NodeVariable getTypeSafeSubstitute(NodeVariable variable) {
13 var substitute = getSubstitute(variable);
14 return substitute.asNodeVariable();
15 }
16
17 default AnyDataVariable getTypeSafeSubstitute(AnyDataVariable variable) {
18 return getTypeSafeSubstitute((DataVariable<?>) variable);
19 }
20
21 default <T> DataVariable<T> getTypeSafeSubstitute(DataVariable<T> variable) {
22 var substitute = getSubstitute(variable);
23 return substitute.asDataVariable(variable.getType());
24 }
25
26 default Substitution andThen(Substitution second) {
27 return new CompositeSubstitution(this, second);
28 }
29}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitutions.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitutions.java
new file mode 100644
index 00000000..5d4654da
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitutions.java
@@ -0,0 +1,33 @@
1package tools.refinery.store.query.substitution;
2
3import org.jetbrains.annotations.NotNull;
4import org.jetbrains.annotations.Nullable;
5import tools.refinery.store.query.term.Variable;
6
7import java.util.Map;
8
9public final class Substitutions {
10 private Substitutions() {
11 throw new IllegalStateException("This is a static utility class and should not be instantiate directly");
12 }
13
14 public static Substitution total(Map<Variable, Variable> map) {
15 return new MapBasedSubstitution(map, StatelessSubstitution.FAILING);
16 }
17
18 public static Substitution partial(Map<Variable, Variable> map) {
19 return new MapBasedSubstitution(map, StatelessSubstitution.IDENTITY);
20 }
21
22 public static Substitution renewing(Map<Variable, Variable> map) {
23 return new MapBasedSubstitution(map, renewing());
24 }
25
26 public static Substitution renewing() {
27 return new RenewingSubstitution();
28 }
29
30 public static Substitution compose(@Nullable Substitution first, @NotNull Substitution second) {
31 return first == null ? second : first.andThen(second);
32 }
33}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java
new file mode 100644
index 00000000..47421a94
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java
@@ -0,0 +1,13 @@
1package tools.refinery.store.query.term;
2
3import java.util.stream.Stream;
4
5public interface Aggregator<R, T> {
6 Class<R> getResultType();
7
8 Class<T> getInputType();
9
10 R aggregateStream(Stream<T> stream);
11
12 R getEmptyResult();
13}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java
new file mode 100644
index 00000000..ecfefcf9
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java
@@ -0,0 +1,33 @@
1package tools.refinery.store.query.term;
2
3import org.jetbrains.annotations.Nullable;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5
6import java.util.Set;
7
8public abstract sealed class AnyDataVariable extends Variable implements AnyTerm permits DataVariable {
9 protected AnyDataVariable(String name) {
10 super(name);
11 }
12
13 @Override
14 public NodeVariable asNodeVariable() {
15 throw new IllegalStateException("%s is a data variable".formatted(this));
16 }
17
18 @Override
19 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
20 return other instanceof AnyDataVariable dataVariable && helper.variableEqual(this, dataVariable);
21 }
22
23 @Override
24 public Set<AnyDataVariable> getInputVariables() {
25 return Set.of(this);
26 }
27
28 @Override
29 public abstract AnyDataVariable renew(@Nullable String name);
30
31 @Override
32 public abstract AnyDataVariable renew();
33}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java
new file mode 100644
index 00000000..8f998d45
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java
@@ -0,0 +1,16 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5
6import java.util.Set;
7
8public sealed interface AnyTerm permits AnyDataVariable, Term {
9 Class<?> getType();
10
11 AnyTerm substitute(Substitution substitution);
12
13 boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other);
14
15 Set<AnyDataVariable> getInputVariables();
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryOperator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryOperator.java
new file mode 100644
index 00000000..8706a046
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryOperator.java
@@ -0,0 +1,26 @@
1package tools.refinery.store.query.term;
2
3public enum ArithmeticBinaryOperator {
4 ADD("+", true),
5 SUB("-", true),
6 MUL("*", true),
7 DIV("/", true),
8 POW("**", true),
9 MIN("min", false),
10 MAX("max", false);
11
12 private final String text;
13 private final boolean infix;
14
15 ArithmeticBinaryOperator(String text, boolean infix) {
16 this.text = text;
17 this.infix = infix;
18 }
19
20 public String formatString(String left, String right) {
21 if (infix) {
22 return "(%s) %s (%s)".formatted(left, text, right);
23 }
24 return "%s(%s, %s)".formatted(text, left, right);
25 }
26}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryTerm.java
new file mode 100644
index 00000000..887a1e6e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryTerm.java
@@ -0,0 +1,56 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4
5import java.util.Objects;
6
7public abstract class ArithmeticBinaryTerm<T> extends BinaryTerm<T, T, T> {
8 private final ArithmeticBinaryOperator operator;
9
10 protected ArithmeticBinaryTerm(ArithmeticBinaryOperator operator, Term<T> left, Term<T> right) {
11 super(left, right);
12 this.operator = operator;
13 }
14
15 @Override
16 public Class<T> getLeftType() {
17 return getType();
18 }
19
20 @Override
21 public Class<T> getRightType() {
22 return getType();
23 }
24
25 public ArithmeticBinaryOperator getOperator() {
26 return operator;
27 }
28
29 @Override
30 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
31 if (!super.equalsWithSubstitution(helper, other)) {
32 return false;
33 }
34 var otherArithmeticBinaryTerm = (ArithmeticBinaryTerm<?>) other;
35 return operator == otherArithmeticBinaryTerm.operator;
36 }
37
38 @Override
39 public String toString() {
40 return operator.formatString(getLeft().toString(), getRight().toString());
41 }
42
43 @Override
44 public boolean equals(Object o) {
45 if (this == o) return true;
46 if (o == null || getClass() != o.getClass()) return false;
47 if (!super.equals(o)) return false;
48 ArithmeticBinaryTerm<?> that = (ArithmeticBinaryTerm<?>) o;
49 return operator == that.operator;
50 }
51
52 @Override
53 public int hashCode() {
54 return Objects.hash(super.hashCode(), operator);
55 }
56}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryOperator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryOperator.java
new file mode 100644
index 00000000..6a7c25db
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryOperator.java
@@ -0,0 +1,16 @@
1package tools.refinery.store.query.term;
2
3public enum ArithmeticUnaryOperator {
4 PLUS("+"),
5 MINUS("-");
6
7 private final String prefix;
8
9 ArithmeticUnaryOperator(String prefix) {
10 this.prefix = prefix;
11 }
12
13 public String formatString(String body) {
14 return "%s(%s)".formatted(prefix, body);
15 }
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryTerm.java
new file mode 100644
index 00000000..b78239c7
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryTerm.java
@@ -0,0 +1,51 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4
5import java.util.Objects;
6
7public abstract class ArithmeticUnaryTerm<T> extends UnaryTerm<T, T> {
8 private final ArithmeticUnaryOperator operator;
9
10 protected ArithmeticUnaryTerm(ArithmeticUnaryOperator operator, Term<T> body) {
11 super(body);
12 this.operator = operator;
13 }
14
15 @Override
16 public Class<T> getBodyType() {
17 return getType();
18 }
19
20 public ArithmeticUnaryOperator getOperator() {
21 return operator;
22 }
23
24 @Override
25 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
26 if (!super.equalsWithSubstitution(helper, other)) {
27 return false;
28 }
29 var otherArithmeticUnaryTerm = (ArithmeticUnaryTerm<?>) other;
30 return operator == otherArithmeticUnaryTerm.operator;
31 }
32
33 @Override
34 public String toString() {
35 return operator.formatString(getBody().toString());
36 }
37
38 @Override
39 public boolean equals(Object o) {
40 if (this == o) return true;
41 if (o == null || getClass() != o.getClass()) return false;
42 if (!super.equals(o)) return false;
43 ArithmeticUnaryTerm<?> that = (ArithmeticUnaryTerm<?>) o;
44 return operator == that.operator;
45 }
46
47 @Override
48 public int hashCode() {
49 return Objects.hash(super.hashCode(), operator);
50 }
51}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java
new file mode 100644
index 00000000..465e690f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java
@@ -0,0 +1,8 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.literal.Literal;
4
5@FunctionalInterface
6public interface AssignedValue<T> {
7 Literal toLiteral(DataVariable<T> targetVariable);
8}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java
new file mode 100644
index 00000000..34f48ccc
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java
@@ -0,0 +1,93 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.valuation.Valuation;
6
7import java.util.Collections;
8import java.util.HashSet;
9import java.util.Objects;
10import java.util.Set;
11
12public abstract class BinaryTerm<R, T1, T2> implements Term<R> {
13 private final Term<T1> left;
14 private final Term<T2> right;
15
16 protected BinaryTerm(Term<T1> left, Term<T2> right) {
17 if (!left.getType().equals(getLeftType())) {
18 throw new IllegalArgumentException("Expected left %s to be of type %s, got %s instead".formatted(left,
19 getLeftType().getName(), left.getType().getName()));
20 }
21 if (!right.getType().equals(getRightType())) {
22 throw new IllegalArgumentException("Expected right %s to be of type %s, got %s instead".formatted(right,
23 getRightType().getName(), right.getType().getName()));
24 }
25 this.left = left;
26 this.right = right;
27 }
28
29 public abstract Class<T1> getLeftType();
30
31 public abstract Class<T2> getRightType();
32
33 public Term<T1> getLeft() {
34 return left;
35 }
36
37 public Term<T2> getRight() {
38 return right;
39 }
40
41 @Override
42 public R evaluate(Valuation valuation) {
43 var leftValue = left.evaluate(valuation);
44 if (leftValue == null) {
45 return null;
46 }
47 var rightValue = right.evaluate(valuation);
48 if (rightValue == null) {
49 return null;
50 }
51 return doEvaluate(leftValue, rightValue);
52 }
53
54 protected abstract R doEvaluate(T1 leftValue, T2 rightValue);
55
56 @Override
57 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
58 if (getClass() != other.getClass()) {
59 return false;
60 }
61 var otherBinaryTerm = (BinaryTerm<?, ?, ?>) other;
62 return left.equalsWithSubstitution(helper, otherBinaryTerm.left) && right.equalsWithSubstitution(helper,
63 otherBinaryTerm.right);
64 }
65
66 @Override
67 public Term<R> substitute(Substitution substitution) {
68 return doSubstitute(substitution, left.substitute(substitution), right.substitute(substitution));
69 }
70
71 public abstract Term<R> doSubstitute(Substitution substitution, Term<T1> substitutedLeft,
72 Term<T2> substitutedRight);
73
74 @Override
75 public Set<AnyDataVariable> getInputVariables() {
76 var inputVariables = new HashSet<>(left.getInputVariables());
77 inputVariables.addAll(right.getInputVariables());
78 return Collections.unmodifiableSet(inputVariables);
79 }
80
81 @Override
82 public boolean equals(Object o) {
83 if (this == o) return true;
84 if (o == null || getClass() != o.getClass()) return false;
85 BinaryTerm<?, ?, ?> that = (BinaryTerm<?, ?, ?>) o;
86 return left.equals(that.left) && right.equals(that.right);
87 }
88
89 @Override
90 public int hashCode() {
91 return Objects.hash(getClass(), left, right);
92 }
93}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonOperator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonOperator.java
new file mode 100644
index 00000000..44dcce10
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonOperator.java
@@ -0,0 +1,20 @@
1package tools.refinery.store.query.term;
2
3public enum ComparisonOperator {
4 EQ("=="),
5 NOT_EQ("!="),
6 LESS("<"),
7 LESS_EQ("<="),
8 GREATER(">"),
9 GREATER_EQ(">=");
10
11 private final String text;
12
13 ComparisonOperator(String text) {
14 this.text = text;
15 }
16
17 public String formatString(String left, String right) {
18 return "(%s) %s (%s)".formatted(left, text, right);
19 }
20}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonTerm.java
new file mode 100644
index 00000000..320d42df
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonTerm.java
@@ -0,0 +1,63 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4
5import java.util.Objects;
6
7public abstract class ComparisonTerm<T> extends BinaryTerm<Boolean, T, T> {
8 private final ComparisonOperator operator;
9
10 protected ComparisonTerm(ComparisonOperator operator, Term<T> left, Term<T> right) {
11 super(left, right);
12 this.operator = operator;
13 }
14
15 @Override
16 public Class<Boolean> getType() {
17 return Boolean.class;
18 }
19
20 public abstract Class<T> getOperandType();
21
22 @Override
23 public Class<T> getLeftType() {
24 return getOperandType();
25 }
26
27 @Override
28 public Class<T> getRightType() {
29 return getOperandType();
30 }
31
32 public ComparisonOperator getOperator() {
33 return operator;
34 }
35
36 @Override
37 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
38 if (!super.equalsWithSubstitution(helper, other)) {
39 return false;
40 }
41 var otherComparisonTerm = (ComparisonTerm<?>) other;
42 return operator == otherComparisonTerm.operator;
43 }
44
45 @Override
46 public String toString() {
47 return operator.formatString(getLeft().toString(), getRight().toString());
48 }
49
50 @Override
51 public boolean equals(Object o) {
52 if (this == o) return true;
53 if (o == null || getClass() != o.getClass()) return false;
54 if (!super.equals(o)) return false;
55 ComparisonTerm<?> that = (ComparisonTerm<?>) o;
56 return operator == that.operator;
57 }
58
59 @Override
60 public int hashCode() {
61 return Objects.hash(super.hashCode(), operator);
62 }
63}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java
new file mode 100644
index 00000000..2185fe37
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java
@@ -0,0 +1,52 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.valuation.Valuation;
6
7import java.util.Set;
8
9public record ConstantTerm<T>(Class<T> type, T value) implements Term<T> {
10 public ConstantTerm {
11 if (value == null) {
12 throw new IllegalArgumentException("value should not be null");
13 }
14 if (!type.isInstance(value)) {
15 throw new IllegalArgumentException("value %s is not an instance of %s".formatted(value, type.getName()));
16 }
17 }
18
19 @Override
20 public Class<T> getType() {
21 return type;
22 }
23
24 public T getValue() {
25 return value;
26 }
27
28 @Override
29 public T evaluate(Valuation valuation) {
30 return getValue();
31 }
32
33 @Override
34 public Term<T> substitute(Substitution substitution) {
35 return this;
36 }
37
38 @Override
39 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
40 return equals(other);
41 }
42
43 @Override
44 public Set<AnyDataVariable> getInputVariables() {
45 return Set.of();
46 }
47
48 @Override
49 public String toString() {
50 return getValue().toString();
51 }
52}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataSort.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataSort.java
new file mode 100644
index 00000000..4fb44492
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataSort.java
@@ -0,0 +1,29 @@
1package tools.refinery.store.query.term;
2
3import org.jetbrains.annotations.Nullable;
4
5public record DataSort<T>(Class<T> type) implements Sort {
6 public static final DataSort<Integer> INT = new DataSort<>(Integer.class);
7
8 public static final DataSort<Boolean> BOOL = new DataSort<>(Boolean.class);
9
10 @Override
11 public boolean isInstance(Variable variable) {
12 return variable instanceof DataVariable<?> dataVariable && type.equals(dataVariable.getType());
13 }
14
15 @Override
16 public DataVariable<T> newInstance(@Nullable String name) {
17 return Variable.of(name, type);
18 }
19
20 @Override
21 public DataVariable<T> newInstance() {
22 return newInstance(null);
23 }
24
25 @Override
26 public String toString() {
27 return type.getName();
28 }
29}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java
new file mode 100644
index 00000000..af070ca7
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java
@@ -0,0 +1,87 @@
1package tools.refinery.store.query.term;
2
3import org.jetbrains.annotations.Nullable;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.literal.Literal;
6import tools.refinery.store.query.substitution.Substitution;
7import tools.refinery.store.query.valuation.Valuation;
8
9import java.util.Objects;
10
11public final class DataVariable<T> extends AnyDataVariable implements Term<T> {
12 private final Class<T> type;
13
14 DataVariable(String name, Class<T> type) {
15 super(name);
16 this.type = type;
17 }
18
19 @Override
20 public DataSort<T> getSort() {
21 return new DataSort<>(getType());
22 }
23
24 @Override
25 public Class<T> getType() {
26 return type;
27 }
28
29 @Override
30 public DataVariable<T> renew(@Nullable String name) {
31 return new DataVariable<>(name, type);
32 }
33
34 @Override
35 public DataVariable<T> renew() {
36 return renew(getExplicitName());
37 }
38
39 @Override
40 public NodeVariable asNodeVariable() {
41 throw new IllegalStateException("%s is a data variable".formatted(this));
42 }
43
44 @Override
45 public <U> DataVariable<U> asDataVariable(Class<U> newType) {
46 if (!getType().equals(newType)) {
47 throw new IllegalStateException("%s is not of type %s but of type %s".formatted(this, newType.getName(),
48 getType().getName()));
49 }
50 @SuppressWarnings("unchecked")
51 var result = (DataVariable<U>) this;
52 return result;
53 }
54
55 @Override
56 public T evaluate(Valuation valuation) {
57 return valuation.getValue(this);
58 }
59
60 @Override
61 public Term<T> substitute(Substitution substitution) {
62 return substitution.getTypeSafeSubstitute(this);
63 }
64
65 @Override
66 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
67 return other instanceof DataVariable<?> dataVariable && helper.variableEqual(this, dataVariable);
68 }
69
70 public Literal assign(AssignedValue<T> value) {
71 return value.toLiteral(this);
72 }
73
74 @Override
75 public boolean equals(Object o) {
76 if (this == o) return true;
77 if (o == null || getClass() != o.getClass()) return false;
78 if (!super.equals(o)) return false;
79 DataVariable<?> that = (DataVariable<?>) o;
80 return type.equals(that.type);
81 }
82
83 @Override
84 public int hashCode() {
85 return Objects.hash(super.hashCode(), type);
86 }
87}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java
new file mode 100644
index 00000000..57ff597c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java
@@ -0,0 +1,103 @@
1package tools.refinery.store.query.term;
2
3import java.util.Comparator;
4import java.util.Objects;
5import java.util.SortedMap;
6import java.util.TreeMap;
7
8public class ExtremeValueAggregator<T> implements StatefulAggregator<T, T> {
9 private final Class<T> type;
10 private final T emptyResult;
11 private final Comparator<T> comparator;
12
13 public ExtremeValueAggregator(Class<T> type, T emptyResult) {
14 this(type, emptyResult, null);
15 }
16
17 public ExtremeValueAggregator(Class<T> type, T emptyResult, Comparator<T> comparator) {
18 this.type = type;
19 this.emptyResult = emptyResult;
20 this.comparator = comparator;
21 }
22
23 @Override
24 public Class<T> getResultType() {
25 return getInputType();
26 }
27
28 @Override
29 public Class<T> getInputType() {
30 return type;
31 }
32
33 @Override
34 public StatefulAggregate<T, T> createEmptyAggregate() {
35 return new Aggregate();
36 }
37
38 @Override
39 public T getEmptyResult() {
40 return emptyResult;
41 }
42
43 @Override
44 public boolean equals(Object o) {
45 if (this == o) return true;
46 if (o == null || getClass() != o.getClass()) return false;
47 ExtremeValueAggregator<?> that = (ExtremeValueAggregator<?>) o;
48 return type.equals(that.type) && Objects.equals(emptyResult, that.emptyResult) && Objects.equals(comparator,
49 that.comparator);
50 }
51
52 @Override
53 public int hashCode() {
54 return Objects.hash(type, emptyResult, comparator);
55 }
56
57 private class Aggregate implements StatefulAggregate<T, T> {
58 private final SortedMap<T, Integer> values;
59
60 private Aggregate() {
61 values = new TreeMap<>(comparator);
62 }
63
64 private Aggregate(Aggregate other) {
65 values = new TreeMap<>(other.values);
66 }
67
68 @Override
69 public void add(T value) {
70 values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1);
71 }
72
73 @Override
74 public void remove(T value) {
75 values.compute(value, (theValue, currentCount) -> {
76 if (currentCount == null || currentCount <= 0) {
77 throw new IllegalStateException("Invalid count %d for value %s".formatted(currentCount, theValue));
78 }
79 return currentCount.equals(1) ? null : currentCount - 1;
80 });
81 }
82
83 @Override
84 public T getResult() {
85 return isEmpty() ? emptyResult : values.firstKey();
86 }
87
88 @Override
89 public boolean isEmpty() {
90 return values.isEmpty();
91 }
92
93 @Override
94 public StatefulAggregate<T, T> deepCopy() {
95 return new Aggregate(this);
96 }
97
98 @Override
99 public boolean contains(T value) {
100 return StatefulAggregate.super.contains(value);
101 }
102 }
103}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeSort.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeSort.java
new file mode 100644
index 00000000..1a4b2d4b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeSort.java
@@ -0,0 +1,30 @@
1package tools.refinery.store.query.term;
2
3import org.jetbrains.annotations.Nullable;
4
5public final class NodeSort implements Sort {
6 public static final NodeSort INSTANCE = new NodeSort();
7
8 private NodeSort() {
9 }
10
11 @Override
12 public boolean isInstance(Variable variable) {
13 return variable instanceof NodeVariable;
14 }
15
16 @Override
17 public NodeVariable newInstance(@Nullable String name) {
18 return new NodeVariable(name);
19 }
20
21 @Override
22 public NodeVariable newInstance() {
23 return newInstance(null);
24 }
25
26 @Override
27 public String toString() {
28 return "<node>";
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java
new file mode 100644
index 00000000..7419aaad
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java
@@ -0,0 +1,48 @@
1package tools.refinery.store.query.term;
2
3import org.jetbrains.annotations.Nullable;
4import tools.refinery.store.query.literal.ConstantLiteral;
5import tools.refinery.store.query.literal.EquivalenceLiteral;
6
7public final class NodeVariable extends Variable {
8 NodeVariable(@Nullable String name) {
9 super(name);
10 }
11
12 @Override
13 public NodeSort getSort() {
14 return NodeSort.INSTANCE;
15 }
16
17 @Override
18 public NodeVariable renew(@Nullable String name) {
19 return Variable.of(name);
20 }
21
22 @Override
23 public NodeVariable renew() {
24 return renew(getExplicitName());
25 }
26
27 @Override
28 public NodeVariable asNodeVariable() {
29 return this;
30 }
31
32 @Override
33 public <T> DataVariable<T> asDataVariable(Class<T> type) {
34 throw new IllegalStateException("%s is a node variable".formatted(this));
35 }
36
37 public ConstantLiteral isConstant(int value) {
38 return new ConstantLiteral(this, value);
39 }
40
41 public EquivalenceLiteral isEquivalent(NodeVariable other) {
42 return new EquivalenceLiteral(true, this, other);
43 }
44
45 public EquivalenceLiteral notEquivalent(NodeVariable other) {
46 return new EquivalenceLiteral(false, this, other);
47 }
48}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/OpaqueTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/OpaqueTerm.java
new file mode 100644
index 00000000..8faa9c75
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/OpaqueTerm.java
@@ -0,0 +1,80 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.substitution.Substitutions;
6import tools.refinery.store.query.valuation.Valuation;
7
8import java.util.Objects;
9import java.util.Set;
10import java.util.function.Function;
11import java.util.stream.Collectors;
12
13public final class OpaqueTerm<T> implements Term<T> {
14 private final Class<T> type;
15 private final Function<? super Valuation, ? extends T> evaluator;
16 private final Set<AnyDataVariable> variables;
17 private final Substitution substitution;
18
19 public OpaqueTerm(Class<T> type, Function<? super Valuation, ? extends T> evaluator,
20 Set<? extends AnyDataVariable> variables) {
21 this(type, evaluator, variables, null);
22 }
23
24 private OpaqueTerm(Class<T> type, Function<? super Valuation, ? extends T> evaluator,
25 Set<? extends AnyDataVariable> variables, Substitution substitution) {
26 this.type = type;
27 this.evaluator = evaluator;
28 this.variables = Set.copyOf(variables);
29 this.substitution = substitution;
30 }
31
32 @Override
33 public Class<T> getType() {
34 return type;
35 }
36
37 @Override
38 public Set<AnyDataVariable> getInputVariables() {
39 return variables;
40 }
41
42 @Override
43 public T evaluate(Valuation valuation) {
44 return evaluator.apply(valuation.substitute(substitution));
45 }
46
47 @Override
48 public Term<T> substitute(Substitution newSubstitution) {
49 var substitutedVariables = variables.stream()
50 .map(newSubstitution::getTypeSafeSubstitute)
51 .collect(Collectors.toUnmodifiableSet());
52 return new OpaqueTerm<>(type, evaluator, substitutedVariables,
53 Substitutions.compose(substitution, newSubstitution));
54 }
55
56 @Override
57 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
58 // Cannot inspect the opaque evaluator for deep equality.
59 return equals(other);
60 }
61
62 @Override
63 public String toString() {
64 return "<opaque>";
65 }
66
67 @Override
68 public boolean equals(Object o) {
69 if (this == o) return true;
70 if (o == null || getClass() != o.getClass()) return false;
71 OpaqueTerm<?> that = (OpaqueTerm<?>) o;
72 return type.equals(that.type) && evaluator.equals(that.evaluator) && Objects.equals(substitution,
73 that.substitution);
74 }
75
76 @Override
77 public int hashCode() {
78 return Objects.hash(type, evaluator, substitution);
79 }
80}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Sort.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Sort.java
new file mode 100644
index 00000000..622bcfce
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Sort.java
@@ -0,0 +1,11 @@
1package tools.refinery.store.query.term;
2
3import org.jetbrains.annotations.Nullable;
4
5public sealed interface Sort permits DataSort, NodeSort {
6 boolean isInstance(Variable variable);
7
8 Variable newInstance(@Nullable String name);
9
10 Variable newInstance();
11}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java
new file mode 100644
index 00000000..7ce91305
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java
@@ -0,0 +1,17 @@
1package tools.refinery.store.query.term;
2
3public interface StatefulAggregate<R, T> {
4 void add(T value);
5
6 void remove(T value);
7
8 R getResult();
9
10 boolean isEmpty();
11
12 StatefulAggregate<R, T> deepCopy();
13
14 default boolean contains(T value) {
15 throw new UnsupportedOperationException();
16 }
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java
new file mode 100644
index 00000000..c215a511
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java
@@ -0,0 +1,23 @@
1package tools.refinery.store.query.term;
2
3import java.util.stream.Stream;
4
5public interface StatefulAggregator<R, T> extends Aggregator<R, T> {
6 StatefulAggregate<R, T> createEmptyAggregate();
7
8 @Override
9 default R aggregateStream(Stream<T> stream) {
10 var accumulator = createEmptyAggregate();
11 var iterator = stream.iterator();
12 while (iterator.hasNext()) {
13 var value = iterator.next();
14 accumulator.add(value);
15 }
16 return accumulator.getResult();
17 }
18
19 @Override
20 default R getEmptyResult() {
21 return createEmptyAggregate().getResult();
22 }
23}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java
new file mode 100644
index 00000000..74dbd335
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java
@@ -0,0 +1,20 @@
1package tools.refinery.store.query.term;
2
3import java.util.stream.Stream;
4
5public interface StatelessAggregator<R, T> extends Aggregator<R, T> {
6 R add(R current, T value);
7
8 R remove(R current, T value);
9
10 @Override
11 default R aggregateStream(Stream<T> stream) {
12 var accumulator = getEmptyResult();
13 var iterator = stream.iterator();
14 while (iterator.hasNext()) {
15 var value = iterator.next();
16 accumulator = add(accumulator, value);
17 }
18 return accumulator;
19 }
20}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java
new file mode 100644
index 00000000..95434db2
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java
@@ -0,0 +1,21 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.literal.AssignLiteral;
4import tools.refinery.store.query.literal.Literal;
5import tools.refinery.store.query.substitution.Substitution;
6import tools.refinery.store.query.valuation.Valuation;
7
8public non-sealed interface Term<T> extends AnyTerm, AssignedValue<T> {
9 @Override
10 Class<T> getType();
11
12 T evaluate(Valuation valuation);
13
14 @Override
15 Term<T> substitute(Substitution substitution);
16
17 @Override
18 default Literal toLiteral(DataVariable<T> targetVariable) {
19 return new AssignLiteral<>(targetVariable, this);
20 }
21}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java
new file mode 100644
index 00000000..4083111a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java
@@ -0,0 +1,68 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.valuation.Valuation;
6
7import java.util.Objects;
8import java.util.Set;
9
10public abstract class UnaryTerm<R, T> implements Term<R> {
11 private final Term<T> body;
12
13 protected UnaryTerm(Term<T> body) {
14 if (!body.getType().equals(getBodyType())) {
15 throw new IllegalArgumentException("Expected body %s to be of type %s, got %s instead".formatted(body,
16 getBodyType().getName(), body.getType().getName()));
17 }
18 this.body = body;
19 }
20
21 public abstract Class<T> getBodyType();
22
23 public Term<T> getBody() {
24 return body;
25 }
26
27 @Override
28 public R evaluate(Valuation valuation) {
29 var bodyValue = body.evaluate(valuation);
30 return bodyValue == null ? null : doEvaluate(bodyValue);
31 }
32
33 protected abstract R doEvaluate(T bodyValue);
34
35 @Override
36 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
37 if (getClass() != other.getClass()) {
38 return false;
39 }
40 var otherUnaryTerm = (UnaryTerm<?, ?>) other;
41 return body.equalsWithSubstitution(helper, otherUnaryTerm.body);
42 }
43
44 @Override
45 public Term<R> substitute(Substitution substitution) {
46 return doSubstitute(substitution, body.substitute(substitution));
47 }
48
49 protected abstract Term<R> doSubstitute(Substitution substitution, Term<T> substitutedBody);
50
51 @Override
52 public Set<AnyDataVariable> getInputVariables() {
53 return body.getInputVariables();
54 }
55
56 @Override
57 public boolean equals(Object o) {
58 if (this == o) return true;
59 if (o == null || getClass() != o.getClass()) return false;
60 UnaryTerm<?, ?> unaryTerm = (UnaryTerm<?, ?>) o;
61 return body.equals(unaryTerm.body);
62 }
63
64 @Override
65 public int hashCode() {
66 return Objects.hash(getClass(), body);
67 }
68}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java
new file mode 100644
index 00000000..957e10f8
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java
@@ -0,0 +1,76 @@
1package tools.refinery.store.query.term;
2
3import org.jetbrains.annotations.Nullable;
4import tools.refinery.store.query.dnf.DnfUtils;
5
6import java.util.Objects;
7
8public abstract sealed class Variable permits AnyDataVariable, NodeVariable {
9 private final String explicitName;
10 private final String uniqueName;
11
12 protected Variable(String name) {
13 this.explicitName = name;
14 uniqueName = DnfUtils.generateUniqueName(name);
15 }
16
17 public abstract Sort getSort();
18
19 public String getName() {
20 return explicitName == null ? uniqueName : explicitName;
21 }
22
23 protected String getExplicitName() {
24 return explicitName;
25 }
26
27 public boolean isExplicitlyNamed() {
28 return explicitName != null;
29 }
30
31 public String getUniqueName() {
32 return uniqueName;
33 }
34
35 public abstract Variable renew(@Nullable String name);
36
37 public abstract Variable renew();
38
39 public abstract NodeVariable asNodeVariable();
40
41 public abstract <T> DataVariable<T> asDataVariable(Class<T> type);
42
43 @Override
44 public String toString() {
45 return getName();
46 }
47
48 @Override
49 public boolean equals(Object o) {
50 if (this == o) return true;
51 if (o == null || getClass() != o.getClass()) return false;
52 Variable variable = (Variable) o;
53 return Objects.equals(uniqueName, variable.uniqueName);
54 }
55
56 @Override
57 public int hashCode() {
58 return Objects.hash(uniqueName);
59 }
60
61 public static NodeVariable of(@Nullable String name) {
62 return new NodeVariable(name);
63 }
64
65 public static NodeVariable of() {
66 return of((String) null);
67 }
68
69 public static <T> DataVariable<T> of(@Nullable String name, Class<T> type) {
70 return new DataVariable<>(name, type);
71 }
72
73 public static <T> DataVariable<T> of(Class<T> type) {
74 return of(null, type);
75 }
76}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolConstantTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolConstantTerm.java
new file mode 100644
index 00000000..5079f1ce
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolConstantTerm.java
@@ -0,0 +1,16 @@
1package tools.refinery.store.query.term.bool;
2
3import tools.refinery.store.query.term.ConstantTerm;
4
5public final class BoolConstantTerm {
6 public static final ConstantTerm<Boolean> TRUE = new ConstantTerm<>(Boolean.class, true);
7 public static final ConstantTerm<Boolean> FALSE = new ConstantTerm<>(Boolean.class, false);
8
9 private BoolConstantTerm() {
10 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
11 }
12
13 public static ConstantTerm<Boolean> valueOf(boolean boolValue) {
14 return boolValue ? TRUE : FALSE;
15 }
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolLogicBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolLogicBinaryTerm.java
new file mode 100644
index 00000000..d85f864d
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolLogicBinaryTerm.java
@@ -0,0 +1,78 @@
1package tools.refinery.store.query.term.bool;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.term.*;
6
7import java.util.Objects;
8
9public class BoolLogicBinaryTerm extends BinaryTerm<Boolean, Boolean, Boolean> {
10 private final LogicBinaryOperator operator;
11
12 protected BoolLogicBinaryTerm(LogicBinaryOperator operator, Term<Boolean> left, Term<Boolean> right) {
13 super(left, right);
14 this.operator = operator;
15 }
16
17 @Override
18 public Class<Boolean> getType() {
19 return Boolean.class;
20 }
21
22 @Override
23 public Class<Boolean> getLeftType() {
24 return getType();
25 }
26
27 @Override
28 public Class<Boolean> getRightType() {
29 return getType();
30 }
31
32 public LogicBinaryOperator getOperator() {
33 return operator;
34 }
35
36 @Override
37 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
38 if (!super.equalsWithSubstitution(helper, other)) {
39 return false;
40 }
41 var otherBoolLogicBinaryTerm = (BoolLogicBinaryTerm) other;
42 return operator == otherBoolLogicBinaryTerm.operator;
43 }
44
45 @Override
46 public Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedLeft,
47 Term<Boolean> substitutedRight) {
48 return new BoolLogicBinaryTerm(getOperator(), substitutedLeft, substitutedRight);
49 }
50
51 @Override
52 protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) {
53 return switch (getOperator()) {
54 case AND -> leftValue && rightValue;
55 case OR -> leftValue || rightValue;
56 case XOR -> leftValue ^ rightValue;
57 };
58 }
59
60 @Override
61 public String toString() {
62 return operator.formatString(getLeft().toString(), getRight().toString());
63 }
64
65 @Override
66 public boolean equals(Object o) {
67 if (this == o) return true;
68 if (o == null || getClass() != o.getClass()) return false;
69 if (!super.equals(o)) return false;
70 BoolLogicBinaryTerm that = (BoolLogicBinaryTerm) o;
71 return operator == that.operator;
72 }
73
74 @Override
75 public int hashCode() {
76 return Objects.hash(super.hashCode(), operator);
77 }
78}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java
new file mode 100644
index 00000000..855139b5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java
@@ -0,0 +1,36 @@
1package tools.refinery.store.query.term.bool;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.Term;
5import tools.refinery.store.query.term.UnaryTerm;
6
7public class BoolNotTerm extends UnaryTerm<Boolean, Boolean> {
8 protected BoolNotTerm(Term<Boolean> body) {
9 super(body);
10 }
11
12 @Override
13 public Class<Boolean> getType() {
14 return Boolean.class;
15 }
16
17 @Override
18 public Class<Boolean> getBodyType() {
19 return getType();
20 }
21
22 @Override
23 protected Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedBody) {
24 return new BoolNotTerm(substitutedBody);
25 }
26
27 @Override
28 protected Boolean doEvaluate(Boolean bodyValue) {
29 return !bodyValue;
30 }
31
32 @Override
33 public String toString() {
34 return "!(%s)".formatted(getBody());
35 }
36}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java
new file mode 100644
index 00000000..3d6c8d9d
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java
@@ -0,0 +1,30 @@
1package tools.refinery.store.query.term.bool;
2
3import tools.refinery.store.query.term.ConstantTerm;
4import tools.refinery.store.query.term.Term;
5
6public final class BoolTerms {
7 private BoolTerms() {
8 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
9 }
10
11 public static ConstantTerm<Boolean> constant(boolean value) {
12 return BoolConstantTerm.valueOf(value);
13 }
14
15 public static BoolNotTerm not(Term<Boolean> body) {
16 return new BoolNotTerm(body);
17 }
18
19 public static BoolLogicBinaryTerm and(Term<Boolean> left, Term<Boolean> right) {
20 return new BoolLogicBinaryTerm(LogicBinaryOperator.AND, left, right);
21 }
22
23 public static BoolLogicBinaryTerm or(Term<Boolean> left, Term<Boolean> right) {
24 return new BoolLogicBinaryTerm(LogicBinaryOperator.OR, left, right);
25 }
26
27 public static BoolLogicBinaryTerm xor(Term<Boolean> left, Term<Boolean> right) {
28 return new BoolLogicBinaryTerm(LogicBinaryOperator.XOR, left, right);
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/LogicBinaryOperator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/LogicBinaryOperator.java
new file mode 100644
index 00000000..ca9ac66e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/LogicBinaryOperator.java
@@ -0,0 +1,17 @@
1package tools.refinery.store.query.term.bool;
2
3public enum LogicBinaryOperator {
4 AND("&&"),
5 OR("||"),
6 XOR("^^");
7
8 private final String text;
9
10 LogicBinaryOperator(String text) {
11 this.text = text;
12 }
13
14 public String formatString(String left, String right) {
15 return "(%s) %s (%s)".formatted(left, text, right);
16 }
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticBinaryTerm.java
new file mode 100644
index 00000000..32e41718
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticBinaryTerm.java
@@ -0,0 +1,48 @@
1package tools.refinery.store.query.term.int_;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.ArithmeticBinaryOperator;
5import tools.refinery.store.query.term.ArithmeticBinaryTerm;
6import tools.refinery.store.query.term.Term;
7
8public class IntArithmeticBinaryTerm extends ArithmeticBinaryTerm<Integer> {
9 public IntArithmeticBinaryTerm(ArithmeticBinaryOperator operator, Term<Integer> left, Term<Integer> right) {
10 super(operator, left, right);
11 }
12
13 @Override
14 public Class<Integer> getType() {
15 return Integer.class;
16 }
17
18 @Override
19 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
20 Term<Integer> substitutedRight) {
21 return new IntArithmeticBinaryTerm(getOperator(), substitutedLeft, substitutedRight);
22 }
23
24 @Override
25 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
26 return switch (getOperator()) {
27 case ADD -> leftValue + rightValue;
28 case SUB -> leftValue - rightValue;
29 case MUL -> leftValue * rightValue;
30 case DIV -> rightValue == 0 ? null : leftValue / rightValue;
31 case POW -> rightValue < 0 ? null : power(leftValue, rightValue);
32 case MIN -> Math.min(leftValue, rightValue);
33 case MAX -> Math.max(leftValue, rightValue);
34 };
35 }
36
37 private static int power(int base, int exponent) {
38 int accum = 1;
39 while (exponent > 0) {
40 if (exponent % 2 == 1) {
41 accum = accum * base;
42 }
43 base = base * base;
44 exponent = exponent / 2;
45 }
46 return accum;
47 }
48}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticUnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticUnaryTerm.java
new file mode 100644
index 00000000..1e769259
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticUnaryTerm.java
@@ -0,0 +1,30 @@
1package tools.refinery.store.query.term.int_;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.Term;
5import tools.refinery.store.query.term.ArithmeticUnaryOperator;
6import tools.refinery.store.query.term.ArithmeticUnaryTerm;
7
8public class IntArithmeticUnaryTerm extends ArithmeticUnaryTerm<Integer> {
9 public IntArithmeticUnaryTerm(ArithmeticUnaryOperator operation, Term<Integer> body) {
10 super(operation, body);
11 }
12
13 @Override
14 public Class<Integer> getType() {
15 return Integer.class;
16 }
17
18 @Override
19 protected Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) {
20 return new IntArithmeticUnaryTerm(getOperator(), substitutedBody);
21 }
22
23 @Override
24 protected Integer doEvaluate(Integer bodyValue) {
25 return switch(getOperator()) {
26 case PLUS -> bodyValue;
27 case MINUS -> -bodyValue;
28 };
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntComparisonTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntComparisonTerm.java
new file mode 100644
index 00000000..322d2b80
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntComparisonTerm.java
@@ -0,0 +1,34 @@
1package tools.refinery.store.query.term.int_;
2import tools.refinery.store.query.substitution.Substitution;
3import tools.refinery.store.query.term.ComparisonOperator;
4import tools.refinery.store.query.term.ComparisonTerm;
5import tools.refinery.store.query.term.Term;
6
7public class IntComparisonTerm extends ComparisonTerm<Integer> {
8 public IntComparisonTerm(ComparisonOperator operator, Term<Integer> left, Term<Integer> right) {
9 super(operator, left, right);
10 }
11
12 @Override
13 public Class<Integer> getOperandType() {
14 return Integer.class;
15 }
16
17 @Override
18 public Term<Boolean> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
19 Term<Integer> substitutedRight) {
20 return new IntComparisonTerm(getOperator(), substitutedLeft, substitutedRight);
21 }
22
23 @Override
24 protected Boolean doEvaluate(Integer leftValue, Integer rightValue) {
25 return switch (getOperator()) {
26 case EQ -> leftValue.equals(rightValue);
27 case NOT_EQ -> !leftValue.equals(rightValue);
28 case LESS -> leftValue < rightValue;
29 case LESS_EQ -> leftValue <= rightValue;
30 case GREATER -> leftValue > rightValue;
31 case GREATER_EQ -> leftValue >= rightValue;
32 };
33 }
34}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntExtremeValueAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntExtremeValueAggregator.java
new file mode 100644
index 00000000..d5a6add0
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntExtremeValueAggregator.java
@@ -0,0 +1,17 @@
1package tools.refinery.store.query.term.int_;
2
3import tools.refinery.store.query.term.ExtremeValueAggregator;
4
5import java.util.Comparator;
6
7public final class IntExtremeValueAggregator {
8 public static final ExtremeValueAggregator<Integer> MINIMUM = new ExtremeValueAggregator<>(Integer.class,
9 Integer.MAX_VALUE);
10
11 public static final ExtremeValueAggregator<Integer> MAXIMUM = new ExtremeValueAggregator<>(Integer.class,
12 Integer.MIN_VALUE, Comparator.reverseOrder());
13
14 private IntExtremeValueAggregator() {
15 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
16 }
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java
new file mode 100644
index 00000000..65024f52
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java
@@ -0,0 +1,35 @@
1package tools.refinery.store.query.term.int_;
2
3import tools.refinery.store.query.term.StatelessAggregator;
4
5public final class IntSumAggregator implements StatelessAggregator<Integer, Integer> {
6 public static final IntSumAggregator INSTANCE = new IntSumAggregator();
7
8 private IntSumAggregator() {
9 }
10
11 @Override
12 public Class<Integer> getResultType() {
13 return Integer.class;
14 }
15
16 @Override
17 public Class<Integer> getInputType() {
18 return Integer.class;
19 }
20
21 @Override
22 public Integer getEmptyResult() {
23 return 0;
24 }
25
26 @Override
27 public Integer add(Integer current, Integer value) {
28 return current + value;
29 }
30
31 @Override
32 public Integer remove(Integer current, Integer value) {
33 return current - value;
34 }
35}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java
new file mode 100644
index 00000000..86594deb
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java
@@ -0,0 +1,81 @@
1package tools.refinery.store.query.term.int_;
2
3import tools.refinery.store.query.term.*;
4
5public final class IntTerms {
6 public static final Aggregator<Integer, Integer> INT_SUM = IntSumAggregator.INSTANCE;
7 public static final Aggregator<Integer, Integer> INT_MIN = IntExtremeValueAggregator.MINIMUM;
8 public static final Aggregator<Integer, Integer> INT_MAX = IntExtremeValueAggregator.MAXIMUM;
9
10 private IntTerms() {
11 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
12 }
13
14 public static ConstantTerm<Integer> constant(int value) {
15 return new ConstantTerm<>(Integer.class, value);
16 }
17
18 public static IntArithmeticUnaryTerm plus(Term<Integer> body) {
19 return new IntArithmeticUnaryTerm(ArithmeticUnaryOperator.PLUS, body);
20 }
21
22 public static IntArithmeticUnaryTerm minus(Term<Integer> body) {
23 return new IntArithmeticUnaryTerm(ArithmeticUnaryOperator.MINUS, body);
24 }
25
26 public static IntArithmeticBinaryTerm add(Term<Integer> left, Term<Integer> right) {
27 return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.ADD, left, right);
28 }
29
30 public static IntArithmeticBinaryTerm sub(Term<Integer> left, Term<Integer> right) {
31 return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.SUB, left, right);
32 }
33
34 public static IntArithmeticBinaryTerm mul(Term<Integer> left, Term<Integer> right) {
35 return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.MUL, left, right);
36 }
37
38 public static IntArithmeticBinaryTerm div(Term<Integer> left, Term<Integer> right) {
39 return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.DIV, left, right);
40 }
41
42 public static IntArithmeticBinaryTerm pow(Term<Integer> left, Term<Integer> right) {
43 return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.POW, left, right);
44 }
45
46 public static IntArithmeticBinaryTerm min(Term<Integer> left, Term<Integer> right) {
47 return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.MIN, left, right);
48 }
49
50 public static IntArithmeticBinaryTerm max(Term<Integer> left, Term<Integer> right) {
51 return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.MAX, left, right);
52 }
53
54 public static IntComparisonTerm eq(Term<Integer> left, Term<Integer> right) {
55 return new IntComparisonTerm(ComparisonOperator.EQ, left, right);
56 }
57
58 public static IntComparisonTerm notEq(Term<Integer> left, Term<Integer> right) {
59 return new IntComparisonTerm(ComparisonOperator.NOT_EQ, left, right);
60 }
61
62 public static IntComparisonTerm less(Term<Integer> left, Term<Integer> right) {
63 return new IntComparisonTerm(ComparisonOperator.LESS, left, right);
64 }
65
66 public static IntComparisonTerm lessEq(Term<Integer> left, Term<Integer> right) {
67 return new IntComparisonTerm(ComparisonOperator.LESS_EQ, left, right);
68 }
69
70 public static IntComparisonTerm greater(Term<Integer> left, Term<Integer> right) {
71 return new IntComparisonTerm(ComparisonOperator.GREATER, left, right);
72 }
73
74 public static IntComparisonTerm greaterEq(Term<Integer> left, Term<Integer> right) {
75 return new IntComparisonTerm(ComparisonOperator.GREATER_EQ, left, right);
76 }
77
78 public static RealToIntTerm asInt(Term<Double> body) {
79 return new RealToIntTerm(body);
80 }
81}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java
new file mode 100644
index 00000000..53875ddc
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java
@@ -0,0 +1,36 @@
1package tools.refinery.store.query.term.int_;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.Term;
5import tools.refinery.store.query.term.UnaryTerm;
6
7public class RealToIntTerm extends UnaryTerm<Integer, Double> {
8 protected RealToIntTerm(Term<Double> body) {
9 super(body);
10 }
11
12 @Override
13 public Class<Integer> getType() {
14 return Integer.class;
15 }
16
17 @Override
18 public Class<Double> getBodyType() {
19 return Double.class;
20 }
21
22 @Override
23 protected Integer doEvaluate(Double bodyValue) {
24 return bodyValue.intValue();
25 }
26
27 @Override
28 protected Term<Integer> doSubstitute(Substitution substitution, Term<Double> substitutedBody) {
29 return new RealToIntTerm(substitutedBody);
30 }
31
32 @Override
33 public String toString() {
34 return "(%s) as int".formatted(getBody());
35 }
36}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java
new file mode 100644
index 00000000..55590824
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java
@@ -0,0 +1,36 @@
1package tools.refinery.store.query.term.real;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.Term;
5import tools.refinery.store.query.term.UnaryTerm;
6
7public class IntToRealTerm extends UnaryTerm<Double, Integer> {
8 protected IntToRealTerm(Term<Integer> body) {
9 super(body);
10 }
11
12 @Override
13 public Class<Double> getType() {
14 return Double.class;
15 }
16
17 @Override
18 public Class<Integer> getBodyType() {
19 return Integer.class;
20 }
21
22 @Override
23 protected Term<Double> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) {
24 return new IntToRealTerm(substitutedBody);
25 }
26
27 @Override
28 protected Double doEvaluate(Integer bodyValue) {
29 return bodyValue.doubleValue();
30 }
31
32 @Override
33 public String toString() {
34 return "(%s) as real".formatted(getBody());
35 }
36}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticBinaryTerm.java
new file mode 100644
index 00000000..57bcbe5e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticBinaryTerm.java
@@ -0,0 +1,36 @@
1package tools.refinery.store.query.term.real;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.ArithmeticBinaryOperator;
5import tools.refinery.store.query.term.ArithmeticBinaryTerm;
6import tools.refinery.store.query.term.Term;
7
8public class RealArithmeticBinaryTerm extends ArithmeticBinaryTerm<Double> {
9 public RealArithmeticBinaryTerm(ArithmeticBinaryOperator operator, Term<Double> left, Term<Double> right) {
10 super(operator, left, right);
11 }
12
13 @Override
14 public Class<Double> getType() {
15 return Double.class;
16 }
17
18 @Override
19 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
20 Term<Double> substitutedRight) {
21 return new RealArithmeticBinaryTerm(getOperator(), substitutedLeft, substitutedRight);
22 }
23
24 @Override
25 protected Double doEvaluate(Double leftValue, Double rightValue) {
26 return switch (getOperator()) {
27 case ADD -> leftValue + rightValue;
28 case SUB -> leftValue - rightValue;
29 case MUL -> leftValue * rightValue;
30 case DIV -> leftValue / rightValue;
31 case POW -> Math.pow(leftValue, rightValue);
32 case MIN -> Math.min(leftValue, rightValue);
33 case MAX -> Math.max(leftValue, rightValue);
34 };
35 }
36}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticUnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticUnaryTerm.java
new file mode 100644
index 00000000..632e68bf
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticUnaryTerm.java
@@ -0,0 +1,30 @@
1package tools.refinery.store.query.term.real;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.ArithmeticUnaryOperator;
5import tools.refinery.store.query.term.ArithmeticUnaryTerm;
6import tools.refinery.store.query.term.Term;
7
8public class RealArithmeticUnaryTerm extends ArithmeticUnaryTerm<Double> {
9 public RealArithmeticUnaryTerm(ArithmeticUnaryOperator operation, Term<Double> body) {
10 super(operation, body);
11 }
12
13 @Override
14 public Class<Double> getType() {
15 return Double.class;
16 }
17
18 @Override
19 protected Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedBody) {
20 return new RealArithmeticUnaryTerm(getOperator(), substitutedBody);
21 }
22
23 @Override
24 protected Double doEvaluate(Double bodyValue) {
25 return switch(getOperator()) {
26 case PLUS -> bodyValue;
27 case MINUS -> -bodyValue;
28 };
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealComparisonTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealComparisonTerm.java
new file mode 100644
index 00000000..75d97adb
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealComparisonTerm.java
@@ -0,0 +1,35 @@
1package tools.refinery.store.query.term.real;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.ComparisonOperator;
5import tools.refinery.store.query.term.ComparisonTerm;
6import tools.refinery.store.query.term.Term;
7
8public class RealComparisonTerm extends ComparisonTerm<Double> {
9 public RealComparisonTerm(ComparisonOperator operator, Term<Double> left, Term<Double> right) {
10 super(operator, left, right);
11 }
12
13 @Override
14 public Class<Double> getOperandType() {
15 return Double.class;
16 }
17
18 @Override
19 public Term<Boolean> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
20 Term<Double> substitutedRight) {
21 return new RealComparisonTerm(getOperator(), substitutedLeft, substitutedRight);
22 }
23
24 @Override
25 protected Boolean doEvaluate(Double leftValue, Double rightValue) {
26 return switch (getOperator()) {
27 case EQ -> leftValue.equals(rightValue);
28 case NOT_EQ -> !leftValue.equals(rightValue);
29 case LESS -> leftValue < rightValue;
30 case LESS_EQ -> leftValue <= rightValue;
31 case GREATER -> leftValue > rightValue;
32 case GREATER_EQ -> leftValue >= rightValue;
33 };
34 }
35}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealExtremeValueAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealExtremeValueAggregator.java
new file mode 100644
index 00000000..23384530
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealExtremeValueAggregator.java
@@ -0,0 +1,17 @@
1package tools.refinery.store.query.term.real;
2
3import tools.refinery.store.query.term.ExtremeValueAggregator;
4
5import java.util.Comparator;
6
7public final class RealExtremeValueAggregator {
8 public static final ExtremeValueAggregator<Double> MINIMUM = new ExtremeValueAggregator<>(Double.class,
9 Double.POSITIVE_INFINITY);
10
11 public static final ExtremeValueAggregator<Double> MAXIMUM = new ExtremeValueAggregator<>(Double.class,
12 Double.NEGATIVE_INFINITY, Comparator.reverseOrder());
13
14 private RealExtremeValueAggregator() {
15 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
16 }
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java
new file mode 100644
index 00000000..d5888664
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java
@@ -0,0 +1,85 @@
1package tools.refinery.store.query.term.real;
2
3import tools.refinery.store.query.term.StatefulAggregate;
4import tools.refinery.store.query.term.StatefulAggregator;
5
6import java.util.Map;
7import java.util.TreeMap;
8
9public final class RealSumAggregator implements StatefulAggregator<Double, Double> {
10 public static final RealSumAggregator INSTANCE = new RealSumAggregator();
11
12 private RealSumAggregator() {
13 }
14
15 @Override
16 public Class<Double> getResultType() {
17 return null;
18 }
19
20 @Override
21 public Class<Double> getInputType() {
22 return null;
23 }
24
25 @Override
26 public StatefulAggregate<Double, Double> createEmptyAggregate() {
27 return new Aggregate();
28 }
29
30 @Override
31 public Double getEmptyResult() {
32 return 0d;
33 }
34
35 private static class Aggregate implements StatefulAggregate<Double, Double> {
36 private final Map<Double, Integer> values;
37
38 public Aggregate() {
39 values = new TreeMap<>();
40 }
41
42 private Aggregate(Aggregate other) {
43 values = new TreeMap<>(other.values);
44 }
45
46 @Override
47 public void add(Double value) {
48 values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1);
49 }
50
51 @Override
52 public void remove(Double value) {
53 values.compute(value, (theValue, currentCount) -> {
54 if (currentCount == null || currentCount <= 0) {
55 throw new IllegalStateException("Invalid count %d for value %f".formatted(currentCount, theValue));
56 }
57 return currentCount.equals(1) ? null : currentCount - 1;
58 });
59 }
60
61 @Override
62 public Double getResult() {
63 return values.entrySet()
64 .stream()
65 .mapToDouble(entry -> entry.getKey() * entry.getValue())
66 .reduce(Double::sum)
67 .orElse(0d);
68 }
69
70 @Override
71 public boolean isEmpty() {
72 return values.isEmpty();
73 }
74
75 @Override
76 public StatefulAggregate<Double, Double> deepCopy() {
77 return new Aggregate(this);
78 }
79
80 @Override
81 public boolean contains(Double value) {
82 return values.containsKey(value);
83 }
84 }
85}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java
new file mode 100644
index 00000000..a8117842
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java
@@ -0,0 +1,81 @@
1package tools.refinery.store.query.term.real;
2
3import tools.refinery.store.query.term.*;
4
5public final class RealTerms {
6 public static final Aggregator<Double, Double> REAL_SUM = RealSumAggregator.INSTANCE;
7 public static final Aggregator<Double, Double> REAL_MIN = RealExtremeValueAggregator.MINIMUM;
8 public static final Aggregator<Double, Double> REAL_MAX = RealExtremeValueAggregator.MAXIMUM;
9
10 private RealTerms() {
11 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
12 }
13
14 public static ConstantTerm<Double> constant(double value) {
15 return new ConstantTerm<>(Double.class, value);
16 }
17
18 public static RealArithmeticUnaryTerm plus(Term<Double> body) {
19 return new RealArithmeticUnaryTerm(ArithmeticUnaryOperator.PLUS, body);
20 }
21
22 public static RealArithmeticUnaryTerm minus(Term<Double> body) {
23 return new RealArithmeticUnaryTerm(ArithmeticUnaryOperator.MINUS, body);
24 }
25
26 public static RealArithmeticBinaryTerm add(Term<Double> left, Term<Double> right) {
27 return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.ADD, left, right);
28 }
29
30 public static RealArithmeticBinaryTerm sub(Term<Double> left, Term<Double> right) {
31 return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.SUB, left, right);
32 }
33
34 public static RealArithmeticBinaryTerm mul(Term<Double> left, Term<Double> right) {
35 return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.MUL, left, right);
36 }
37
38 public static RealArithmeticBinaryTerm div(Term<Double> left, Term<Double> right) {
39 return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.DIV, left, right);
40 }
41
42 public static RealArithmeticBinaryTerm pow(Term<Double> left, Term<Double> right) {
43 return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.POW, left, right);
44 }
45
46 public static RealArithmeticBinaryTerm min(Term<Double> left, Term<Double> right) {
47 return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.MIN, left, right);
48 }
49
50 public static RealArithmeticBinaryTerm max(Term<Double> left, Term<Double> right) {
51 return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.MAX, left, right);
52 }
53
54 public static RealComparisonTerm eq(Term<Double> left, Term<Double> right) {
55 return new RealComparisonTerm(ComparisonOperator.EQ, left, right);
56 }
57
58 public static RealComparisonTerm notEq(Term<Double> left, Term<Double> right) {
59 return new RealComparisonTerm(ComparisonOperator.NOT_EQ, left, right);
60 }
61
62 public static RealComparisonTerm less(Term<Double> left, Term<Double> right) {
63 return new RealComparisonTerm(ComparisonOperator.LESS, left, right);
64 }
65
66 public static RealComparisonTerm lessEq(Term<Double> left, Term<Double> right) {
67 return new RealComparisonTerm(ComparisonOperator.LESS_EQ, left, right);
68 }
69
70 public static RealComparisonTerm greater(Term<Double> left, Term<Double> right) {
71 return new RealComparisonTerm(ComparisonOperator.GREATER, left, right);
72 }
73
74 public static RealComparisonTerm greaterEq(Term<Double> left, Term<Double> right) {
75 return new RealComparisonTerm(ComparisonOperator.GREATER_EQ, left, right);
76 }
77
78 public static IntToRealTerm asReal(Term<Integer> body) {
79 return new IntToRealTerm(body);
80 }
81}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java
new file mode 100644
index 00000000..fb512d88
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java
@@ -0,0 +1,16 @@
1package tools.refinery.store.query.valuation;
2
3import tools.refinery.store.query.term.AnyDataVariable;
4import tools.refinery.store.query.term.DataVariable;
5
6import java.util.Set;
7
8public record RestrictedValuation(Valuation valuation, Set<AnyDataVariable> allowedVariables) implements Valuation {
9 @Override
10 public <T> T getValue(DataVariable<T> variable) {
11 if (!allowedVariables.contains(variable)) {
12 throw new IllegalArgumentException("Variable %s is not in scope".formatted(variable));
13 }
14 return valuation.getValue(variable);
15 }
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java
new file mode 100644
index 00000000..8e79663c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java
@@ -0,0 +1,11 @@
1package tools.refinery.store.query.valuation;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.DataVariable;
5
6public record SubstitutedValuation(Valuation originalValuation, Substitution substitution) implements Valuation {
7 @Override
8 public <T> T getValue(DataVariable<T> variable) {
9 return originalValuation.getValue(substitution.getTypeSafeSubstitute(variable));
10 }
11}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java
new file mode 100644
index 00000000..3ba9a6b8
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java
@@ -0,0 +1,23 @@
1package tools.refinery.store.query.valuation;
2
3import org.jetbrains.annotations.Nullable;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.term.AnyDataVariable;
6import tools.refinery.store.query.term.DataVariable;
7
8import java.util.Set;
9
10public interface Valuation {
11 <T> T getValue(DataVariable<T> variable);
12
13 default Valuation substitute(@Nullable Substitution substitution) {
14 if (substitution == null) {
15 return this;
16 }
17 return new SubstitutedValuation(this, substitution);
18 }
19
20 default Valuation restrict(Set<? extends AnyDataVariable> allowedVariables) {
21 return new RestrictedValuation(this, Set.copyOf(allowedVariables));
22 }
23}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java
new file mode 100644
index 00000000..6ae410f2
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java
@@ -0,0 +1,26 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.query.dnf.FunctionalDependency;
5import tools.refinery.store.representation.AnySymbol;
6import tools.refinery.store.query.Constraint;
7
8import java.util.Set;
9
10public sealed interface AnyRelationView extends Constraint permits RelationView {
11 AnySymbol getSymbol();
12
13 String getViewName();
14
15 default Set<FunctionalDependency<Integer>> getFunctionalDependencies() {
16 return Set.of();
17 }
18
19 default Set<RelationViewImplication> getImpliedRelationViews() {
20 return Set.of();
21 }
22
23 boolean get(Model model, Object[] tuple);
24
25 Iterable<Object[]> getAll(Model model);
26}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java
new file mode 100644
index 00000000..64c601bb
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java
@@ -0,0 +1,49 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.tuple.Tuple;
4import tools.refinery.store.representation.Symbol;
5
6import java.util.Objects;
7import java.util.function.BiPredicate;
8import java.util.function.Predicate;
9
10public class FilteredRelationView<T> extends TuplePreservingRelationView<T> {
11 private final BiPredicate<Tuple, T> predicate;
12
13 public FilteredRelationView(Symbol<T> symbol, String name, BiPredicate<Tuple, T> predicate) {
14 super(symbol, name);
15 this.predicate = predicate;
16 }
17
18 public FilteredRelationView(Symbol<T> symbol, BiPredicate<Tuple, T> predicate) {
19 super(symbol);
20 this.predicate = predicate;
21 }
22
23 public FilteredRelationView(Symbol<T> symbol, String name, Predicate<T> predicate) {
24 this(symbol, name, (k, v) -> predicate.test(v));
25 }
26
27 public FilteredRelationView(Symbol<T> symbol, Predicate<T> predicate) {
28 this(symbol, (k, v) -> predicate.test(v));
29 }
30
31 @Override
32 public boolean filter(Tuple key, T value) {
33 return this.predicate.test(key, value);
34 }
35
36 @Override
37 public boolean equals(Object o) {
38 if (this == o) return true;
39 if (o == null || getClass() != o.getClass()) return false;
40 if (!super.equals(o)) return false;
41 FilteredRelationView<?> that = (FilteredRelationView<?>) o;
42 return Objects.equals(predicate, that.predicate);
43 }
44
45 @Override
46 public int hashCode() {
47 return Objects.hash(super.hashCode(), predicate);
48 }
49}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenRelationView.java
new file mode 100644
index 00000000..050b9496
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenRelationView.java
@@ -0,0 +1,16 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.representation.Symbol;
4import tools.refinery.store.representation.TruthValue;
5import tools.refinery.store.tuple.Tuple;
6
7public class ForbiddenRelationView extends TuplePreservingRelationView<TruthValue> {
8 public ForbiddenRelationView(Symbol<TruthValue> symbol) {
9 super(symbol, "forbidden");
10 }
11
12 @Override
13 public boolean filter(Tuple key, TruthValue value) {
14 return !value.may();
15 }
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java
new file mode 100644
index 00000000..7ec9e7ac
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java
@@ -0,0 +1,105 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.query.dnf.FunctionalDependency;
5import tools.refinery.store.query.term.DataSort;
6import tools.refinery.store.query.term.NodeSort;
7import tools.refinery.store.query.term.Sort;
8import tools.refinery.store.representation.Symbol;
9import tools.refinery.store.tuple.Tuple;
10import tools.refinery.store.tuple.Tuple1;
11
12import java.util.List;
13import java.util.Objects;
14import java.util.Set;
15import java.util.stream.Collectors;
16import java.util.stream.IntStream;
17
18public final class FunctionalRelationView<T> extends RelationView<T> {
19 private final T defaultValue;
20
21 public FunctionalRelationView(Symbol<T> symbol, String name) {
22 super(symbol, name);
23 defaultValue = symbol.defaultValue();
24 }
25
26 public FunctionalRelationView(Symbol<T> symbol) {
27 super(symbol);
28 defaultValue = symbol.defaultValue();
29 }
30
31 @Override
32 public Set<FunctionalDependency<Integer>> getFunctionalDependencies() {
33 var arity = getSymbol().arity();
34 var forEach = IntStream.range(0, arity).boxed().collect(Collectors.toUnmodifiableSet());
35 var unique = Set.of(arity);
36 return Set.of(new FunctionalDependency<>(forEach, unique));
37 }
38
39 @Override
40 public Set<RelationViewImplication> getImpliedRelationViews() {
41 var symbol = getSymbol();
42 var impliedIndices = IntStream.range(0, symbol.arity()).boxed().toList();
43 var keyOnlyRelationView = new KeyOnlyRelationView<>(symbol);
44 return Set.of(new RelationViewImplication(this, keyOnlyRelationView, impliedIndices));
45 }
46
47 @Override
48 public boolean filter(Tuple key, T value) {
49 return !Objects.equals(defaultValue, value);
50 }
51
52 @Override
53 public Object[] forwardMap(Tuple key, T value) {
54 int size = key.getSize();
55 Object[] result = new Object[size + 1];
56 for (int i = 0; i < size; i++) {
57 result[i] = Tuple.of(key.get(i));
58 }
59 result[key.getSize()] = value;
60 return result;
61 }
62
63 @Override
64 public boolean get(Model model, Object[] tuple) {
65 int[] content = new int[tuple.length - 1];
66 for (int i = 0; i < tuple.length - 1; i++) {
67 content[i] = ((Tuple1) tuple[i]).value0();
68 }
69 Tuple key = Tuple.of(content);
70 @SuppressWarnings("unchecked")
71 T valueInTuple = (T) tuple[tuple.length - 1];
72 T valueInMap = model.getInterpretation(getSymbol()).get(key);
73 return valueInTuple.equals(valueInMap);
74 }
75
76 @Override
77 public int arity() {
78 return getSymbol().arity() + 1;
79 }
80
81 @Override
82 public List<Sort> getSorts() {
83 var sorts = new Sort[arity()];
84 int valueIndex = sorts.length - 1;
85 for (int i = 0; i < valueIndex; i++) {
86 sorts[i] = NodeSort.INSTANCE;
87 }
88 sorts[valueIndex] = new DataSort<>(getSymbol().valueType());
89 return List.of(sorts);
90 }
91
92 @Override
93 public boolean equals(Object o) {
94 if (this == o) return true;
95 if (o == null || getClass() != o.getClass()) return false;
96 if (!super.equals(o)) return false;
97 FunctionalRelationView<?> that = (FunctionalRelationView<?>) o;
98 return Objects.equals(defaultValue, that.defaultValue);
99 }
100
101 @Override
102 public int hashCode() {
103 return Objects.hash(super.hashCode(), defaultValue);
104 }
105}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java
new file mode 100644
index 00000000..e1b2e45b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java
@@ -0,0 +1,36 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.representation.Symbol;
4import tools.refinery.store.tuple.Tuple;
5
6import java.util.Objects;
7
8public final class KeyOnlyRelationView<T> extends TuplePreservingRelationView<T> {
9 public static final String VIEW_NAME = "key";
10
11 private final T defaultValue;
12
13 public KeyOnlyRelationView(Symbol<T> symbol) {
14 super(symbol, VIEW_NAME);
15 defaultValue = symbol.defaultValue();
16 }
17
18 @Override
19 public boolean filter(Tuple key, T value) {
20 return !Objects.equals(value, defaultValue);
21 }
22
23 @Override
24 public boolean equals(Object o) {
25 if (this == o) return true;
26 if (o == null || getClass() != o.getClass()) return false;
27 if (!super.equals(o)) return false;
28 KeyOnlyRelationView<?> that = (KeyOnlyRelationView<?>) o;
29 return Objects.equals(defaultValue, that.defaultValue);
30 }
31
32 @Override
33 public int hashCode() {
34 return Objects.hash(super.hashCode(), defaultValue);
35 }
36}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayRelationView.java
new file mode 100644
index 00000000..a2a84b3c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayRelationView.java
@@ -0,0 +1,16 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.representation.Symbol;
4import tools.refinery.store.representation.TruthValue;
5import tools.refinery.store.tuple.Tuple;
6
7public class MayRelationView extends TuplePreservingRelationView<TruthValue> {
8 public MayRelationView(Symbol<TruthValue> symbol) {
9 super(symbol, "may");
10 }
11
12 @Override
13 public boolean filter(Tuple key, TruthValue value) {
14 return value.may();
15 }
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustRelationView.java
new file mode 100644
index 00000000..72ac0ca3
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustRelationView.java
@@ -0,0 +1,16 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.representation.Symbol;
4import tools.refinery.store.representation.TruthValue;
5import tools.refinery.store.tuple.Tuple;
6
7public class MustRelationView extends TuplePreservingRelationView<TruthValue> {
8 public MustRelationView(Symbol<TruthValue> symbol) {
9 super(symbol, "must");
10 }
11
12 @Override
13 public boolean filter(Tuple key, TruthValue value) {
14 return value.must();
15 }
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java
new file mode 100644
index 00000000..d7164b3b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java
@@ -0,0 +1,77 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.map.CursorAsIterator;
4import tools.refinery.store.model.Model;
5import tools.refinery.store.representation.Symbol;
6import tools.refinery.store.tuple.Tuple;
7
8import java.util.Objects;
9import java.util.UUID;
10
11/**
12 * Represents a view of a {@link Symbol} that can be queried.
13 *
14 * @param <T>
15 * @author Oszkar Semerath
16 */
17public abstract non-sealed class RelationView<T> implements AnyRelationView {
18 private final Symbol<T> symbol;
19
20 private final String viewName;
21
22 protected RelationView(Symbol<T> symbol, String viewName) {
23 this.symbol = symbol;
24 this.viewName = viewName;
25 }
26
27 protected RelationView(Symbol<T> representation) {
28 this(representation, UUID.randomUUID().toString());
29 }
30
31 @Override
32 public Symbol<T> getSymbol() {
33 return symbol;
34 }
35
36 @Override
37 public String getViewName() {
38 return viewName;
39 }
40
41 @Override
42 public String name() {
43 return symbol.name() + "#" + viewName;
44 }
45
46 public abstract boolean filter(Tuple key, T value);
47
48 public abstract Object[] forwardMap(Tuple key, T value);
49
50 @Override
51 public Iterable<Object[]> getAll(Model model) {
52 return (() -> new CursorAsIterator<>(model.getInterpretation(symbol).getAll(), this::forwardMap, this::filter));
53 }
54
55 @Override
56 public String toString() {
57 return name();
58 }
59
60 @Override
61 public String toReferenceString() {
62 return "@RelationView(\"%s\") %s".formatted(viewName, symbol.name());
63 }
64
65 @Override
66 public boolean equals(Object o) {
67 if (this == o) return true;
68 if (o == null || getClass() != o.getClass()) return false;
69 RelationView<?> that = (RelationView<?>) o;
70 return Objects.equals(symbol, that.symbol) && Objects.equals(viewName, that.viewName);
71 }
72
73 @Override
74 public int hashCode() {
75 return Objects.hash(symbol, viewName);
76 }
77}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java
new file mode 100644
index 00000000..2ba1fcc4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java
@@ -0,0 +1,19 @@
1package tools.refinery.store.query.view;
2
3import java.util.List;
4
5public record RelationViewImplication(AnyRelationView implyingRelationView, AnyRelationView impliedRelationView,
6 List<Integer> impliedIndices) {
7 public RelationViewImplication {
8 if (impliedIndices.size() != impliedRelationView.arity()) {
9 throw new IllegalArgumentException("Expected %d implied indices for %s, but %d are provided"
10 .formatted(impliedRelationView.arity(), impliedRelationView, impliedIndices.size()));
11 }
12 for (var index : impliedIndices) {
13 if (impliedRelationView.invalidIndex(index)) {
14 throw new IllegalArgumentException("%d is not a valid index for %s".formatted(index,
15 implyingRelationView));
16 }
17 }
18 }
19}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java
new file mode 100644
index 00000000..234b3a9a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java
@@ -0,0 +1,57 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.query.term.NodeSort;
5import tools.refinery.store.query.term.Sort;
6import tools.refinery.store.tuple.Tuple;
7import tools.refinery.store.tuple.Tuple1;
8import tools.refinery.store.representation.Symbol;
9
10import java.util.Arrays;
11import java.util.List;
12
13public abstract class TuplePreservingRelationView<T> extends RelationView<T> {
14 protected TuplePreservingRelationView(Symbol<T> symbol, String name) {
15 super(symbol, name);
16 }
17
18 protected TuplePreservingRelationView(Symbol<T> symbol) {
19 super(symbol);
20 }
21
22 public Object[] forwardMap(Tuple key) {
23 Object[] result = new Object[key.getSize()];
24 for (int i = 0; i < key.getSize(); i++) {
25 result[i] = Tuple.of(key.get(i));
26 }
27 return result;
28 }
29
30 @Override
31 public Object[] forwardMap(Tuple key, T value) {
32 return forwardMap(key);
33 }
34
35 @Override
36 public boolean get(Model model, Object[] tuple) {
37 int[] content = new int[tuple.length];
38 for (int i = 0; i < tuple.length; i++) {
39 content[i] = ((Tuple1) tuple[i]).value0();
40 }
41 Tuple key = Tuple.of(content);
42 T value = model.getInterpretation(getSymbol()).get(key);
43 return filter(key, value);
44 }
45
46 @Override
47 public int arity() {
48 return this.getSymbol().arity();
49 }
50
51 @Override
52 public List<Sort> getSorts() {
53 var sorts = new Sort[arity()];
54 Arrays.fill(sorts, NodeSort.INSTANCE);
55 return List.of(sorts);
56 }
57}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfBuilderTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfBuilderTest.java
new file mode 100644
index 00000000..ceb46d6f
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfBuilderTest.java
@@ -0,0 +1,220 @@
1package tools.refinery.store.query;
2
3import org.junit.jupiter.api.Test;
4import tools.refinery.store.query.dnf.Dnf;
5import tools.refinery.store.query.literal.BooleanLiteral;
6import tools.refinery.store.query.term.Variable;
7import tools.refinery.store.query.view.KeyOnlyRelationView;
8import tools.refinery.store.representation.Symbol;
9
10import static org.hamcrest.MatcherAssert.assertThat;
11import static tools.refinery.store.query.literal.Literals.not;
12import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
13
14class DnfBuilderTest {
15 @Test
16 void eliminateTrueTest() {
17 var p = Variable.of("p");
18 var q = Variable.of("q");
19 var friend = new Symbol<>("friend", 2, Boolean.class, false);
20 var friendView = new KeyOnlyRelationView<>(friend);
21
22 var actual = Dnf.builder()
23 .parameters(p, q)
24 .clause(BooleanLiteral.TRUE, friendView.call(p, q))
25 .build();
26 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
27
28 assertThat(actual, structurallyEqualTo(expected));
29 }
30
31 @Test
32 void eliminateFalseTest() {
33 var p = Variable.of("p");
34 var q = Variable.of("q");
35 var friend = new Symbol<>("friend", 2, Boolean.class, false);
36 var friendView = new KeyOnlyRelationView<>(friend);
37
38 var actual = Dnf.builder()
39 .parameters(p, q)
40 .clause(friendView.call(p, q))
41 .clause(friendView.call(q, p), BooleanLiteral.FALSE)
42 .build();
43 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
44
45 assertThat(actual, structurallyEqualTo(expected));
46 }
47
48 @Test
49 void alwaysTrueTest() {
50 var p = Variable.of("p");
51 var q = Variable.of("q");
52 var friend = new Symbol<>("friend", 2, Boolean.class, false);
53 var friendView = new KeyOnlyRelationView<>(friend);
54
55 var actual = Dnf.builder()
56 .parameters(p, q)
57 .clause(friendView.call(p, q))
58 .clause(BooleanLiteral.TRUE)
59 .build();
60 var expected = Dnf.builder().parameters(p, q).clause().build();
61
62 assertThat(actual, structurallyEqualTo(expected));
63 }
64
65 @Test
66 void alwaysFalseTest() {
67 var p = Variable.of("p");
68 var q = Variable.of("q");
69 var friend = new Symbol<>("friend", 2, Boolean.class, false);
70 var friendView = new KeyOnlyRelationView<>(friend);
71
72 var actual = Dnf.builder()
73 .parameters(p, q)
74 .clause(friendView.call(p, q), BooleanLiteral.FALSE)
75 .build();
76 var expected = Dnf.builder().parameters(p, q).build();
77
78 assertThat(actual, structurallyEqualTo(expected));
79 }
80
81 @Test
82 void eliminateTrueDnfTest() {
83 var p = Variable.of("p");
84 var q = Variable.of("q");
85 var friend = new Symbol<>("friend", 2, Boolean.class, false);
86 var friendView = new KeyOnlyRelationView<>(friend);
87 var trueDnf = Dnf.builder().parameter(p).clause().build();
88
89 var actual = Dnf.builder()
90 .parameters(p, q)
91 .clause(trueDnf.call(q), friendView.call(p, q))
92 .build();
93 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
94
95 assertThat(actual, structurallyEqualTo(expected));
96 }
97
98 @Test
99 void eliminateFalseDnfTest() {
100 var p = Variable.of("p");
101 var q = Variable.of("q");
102 var friend = new Symbol<>("friend", 2, Boolean.class, false);
103 var friendView = new KeyOnlyRelationView<>(friend);
104 var falseDnf = Dnf.builder().parameter(p).build();
105
106 var actual = Dnf.builder()
107 .parameters(p, q)
108 .clause(friendView.call(p, q))
109 .clause(friendView.call(q, p), falseDnf.call(q))
110 .build();
111 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
112
113 assertThat(actual, structurallyEqualTo(expected));
114 }
115
116 @Test
117 void alwaysTrueDnfTest() {
118 var p = Variable.of("p");
119 var q = Variable.of("q");
120 var friend = new Symbol<>("friend", 2, Boolean.class, false);
121 var friendView = new KeyOnlyRelationView<>(friend);
122 var trueDnf = Dnf.builder().parameter(p).clause().build();
123
124 var actual = Dnf.builder()
125 .parameters(p, q)
126 .clause(friendView.call(p, q))
127 .clause(trueDnf.call(q))
128 .build();
129 var expected = Dnf.builder().parameters(p, q).clause().build();
130
131 assertThat(actual, structurallyEqualTo(expected));
132 }
133
134 @Test
135 void alwaysFalseDnfTest() {
136 var p = Variable.of("p");
137 var q = Variable.of("q");
138 var friend = new Symbol<>("friend", 2, Boolean.class, false);
139 var friendView = new KeyOnlyRelationView<>(friend);
140 var falseDnf = Dnf.builder().parameter(p).build();
141
142 var actual = Dnf.builder()
143 .parameters(p, q)
144 .clause(friendView.call(p, q), falseDnf.call(q))
145 .build();
146 var expected = Dnf.builder().parameters(p, q).build();
147
148 assertThat(actual, structurallyEqualTo(expected));
149 }
150
151 @Test
152 void eliminateNotFalseDnfTest() {
153 var p = Variable.of("p");
154 var q = Variable.of("q");
155 var friend = new Symbol<>("friend", 2, Boolean.class, false);
156 var friendView = new KeyOnlyRelationView<>(friend);
157 var falseDnf = Dnf.builder().parameter(p).build();
158
159 var actual = Dnf.builder()
160 .parameters(p, q)
161 .clause(not(falseDnf.call(q)), friendView.call(p, q))
162 .build();
163 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
164
165 assertThat(actual, structurallyEqualTo(expected));
166 }
167
168 @Test
169 void eliminateNotTrueDnfTest() {
170 var p = Variable.of("p");
171 var q = Variable.of("q");
172 var friend = new Symbol<>("friend", 2, Boolean.class, false);
173 var friendView = new KeyOnlyRelationView<>(friend);
174 var trueDnf = Dnf.builder().parameter(p).clause().build();
175
176 var actual = Dnf.builder()
177 .parameters(p, q)
178 .clause(friendView.call(p, q))
179 .clause(friendView.call(q, p), not(trueDnf.call(q)))
180 .build();
181 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
182
183 assertThat(actual, structurallyEqualTo(expected));
184 }
185
186 @Test
187 void alwaysNotFalseDnfTest() {
188 var p = Variable.of("p");
189 var q = Variable.of("q");
190 var friend = new Symbol<>("friend", 2, Boolean.class, false);
191 var friendView = new KeyOnlyRelationView<>(friend);
192 var falseDnf = Dnf.builder().parameter(p).build();
193
194 var actual = Dnf.builder()
195 .parameters(p, q)
196 .clause(friendView.call(p, q))
197 .clause(not(falseDnf.call(q)))
198 .build();
199 var expected = Dnf.builder().parameters(p, q).clause().build();
200
201 assertThat(actual, structurallyEqualTo(expected));
202 }
203
204 @Test
205 void alwaysNotTrueDnfTest() {
206 var p = Variable.of("p");
207 var q = Variable.of("q");
208 var friend = new Symbol<>("friend", 2, Boolean.class, false);
209 var friendView = new KeyOnlyRelationView<>(friend);
210 var trueDnf = Dnf.builder().parameter(p).clause().build();
211
212 var actual = Dnf.builder()
213 .parameters(p, q)
214 .clause(friendView.call(p, q), not(trueDnf.call(q)))
215 .build();
216 var expected = Dnf.builder().parameters(p, q).build();
217
218 assertThat(actual, structurallyEqualTo(expected));
219 }
220}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfToDefinitionStringTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfToDefinitionStringTest.java
new file mode 100644
index 00000000..9b469bb0
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfToDefinitionStringTest.java
@@ -0,0 +1,174 @@
1package tools.refinery.store.query;
2
3import org.junit.jupiter.api.Test;
4import tools.refinery.store.query.dnf.Dnf;
5import tools.refinery.store.query.term.Variable;
6import tools.refinery.store.query.view.KeyOnlyRelationView;
7import tools.refinery.store.representation.Symbol;
8
9import static org.hamcrest.MatcherAssert.assertThat;
10import static org.hamcrest.Matchers.is;
11import static tools.refinery.store.query.literal.Literals.not;
12
13class DnfToDefinitionStringTest {
14 @Test
15 void noClausesTest() {
16 var p = Variable.of("p");
17 var dnf = Dnf.builder("Example").parameter(p).build();
18
19 assertThat(dnf.toDefinitionString(), is("""
20 pred Example(p) <->
21 <no clauses>.
22 """));
23 }
24
25 @Test
26 void noParametersTest() {
27 var dnf = Dnf.builder("Example").build();
28
29 assertThat(dnf.toDefinitionString(), is("""
30 pred Example() <->
31 <no clauses>.
32 """));
33 }
34
35 @Test
36 void emptyClauseTest() {
37 var p = Variable.of("p");
38 var dnf = Dnf.builder("Example").parameter(p).clause().build();
39
40 assertThat(dnf.toDefinitionString(), is("""
41 pred Example(p) <->
42 <empty>.
43 """));
44 }
45
46 @Test
47 void relationViewPositiveTest() {
48 var p = Variable.of("p");
49 var q = Variable.of("q");
50 var friend = new Symbol<>("friend", 2, Boolean.class, false);
51 var friendView = new KeyOnlyRelationView<>(friend);
52 var dnf = Dnf.builder("Example").parameter(p).clause(friendView.call(p, q)).build();
53
54 assertThat(dnf.toDefinitionString(), is("""
55 pred Example(p) <->
56 @RelationView("key") friend(p, q).
57 """));
58 }
59
60 @Test
61 void relationViewNegativeTest() {
62 var p = Variable.of("p");
63 var q = Variable.of("q");
64 var friend = new Symbol<>("friend", 2, Boolean.class, false);
65 var friendView = new KeyOnlyRelationView<>(friend);
66 var dnf = Dnf.builder("Example").parameter(p).clause(not(friendView.call(p, q))).build();
67
68 assertThat(dnf.toDefinitionString(), is("""
69 pred Example(p) <->
70 !(@RelationView("key") friend(p, q)).
71 """));
72 }
73
74 @Test
75 void relationViewTransitiveTest() {
76 var p = Variable.of("p");
77 var q = Variable.of("q");
78 var friend = new Symbol<>("friend", 2, Boolean.class, false);
79 var friendView = new KeyOnlyRelationView<>(friend);
80 var dnf = Dnf.builder("Example").parameter(p).clause(friendView.callTransitive(p, q)).build();
81
82 assertThat(dnf.toDefinitionString(), is("""
83 pred Example(p) <->
84 @RelationView("key") friend+(p, q).
85 """));
86 }
87
88 @Test
89 void multipleParametersTest() {
90 var p = Variable.of("p");
91 var q = Variable.of("q");
92 var friend = new Symbol<>("friend", 2, Boolean.class, false);
93 var friendView = new KeyOnlyRelationView<>(friend);
94 var dnf = Dnf.builder("Example").parameters(p, q).clause(friendView.call(p, q)).build();
95
96 assertThat(dnf.toDefinitionString(), is("""
97 pred Example(p, q) <->
98 @RelationView("key") friend(p, q).
99 """));
100 }
101
102 @Test
103 void multipleLiteralsTest() {
104 var p = Variable.of("p");
105 var q = Variable.of("q");
106 var person = new Symbol<>("person", 1, Boolean.class, false);
107 var personView = new KeyOnlyRelationView<>(person);
108 var friend = new Symbol<>("friend", 2, Boolean.class, false);
109 var friendView = new KeyOnlyRelationView<>(friend);
110 var dnf = Dnf.builder("Example")
111 .parameter(p)
112 .clause(
113 personView.call(p),
114 personView.call(q),
115 friendView.call(p, q)
116 )
117 .build();
118
119 assertThat(dnf.toDefinitionString(), is("""
120 pred Example(p) <->
121 @RelationView("key") person(p),
122 @RelationView("key") person(q),
123 @RelationView("key") friend(p, q).
124 """));
125 }
126
127 @Test
128 void multipleClausesTest() {
129 var p = Variable.of("p");
130 var q = Variable.of("q");
131 var friend = new Symbol<>("friend", 2, Boolean.class, false);
132 var friendView = new KeyOnlyRelationView<>(friend);
133 var dnf = Dnf.builder("Example")
134 .parameter(p)
135 .clause(friendView.call(p, q))
136 .clause(friendView.call(q, p))
137 .build();
138
139 assertThat(dnf.toDefinitionString(), is("""
140 pred Example(p) <->
141 @RelationView("key") friend(p, q)
142 ;
143 @RelationView("key") friend(q, p).
144 """));
145 }
146
147 @Test
148 void dnfTest() {
149 var p = Variable.of("p");
150 var q = Variable.of("q");
151 var r = Variable.of("r");
152 var s = Variable.of("s");
153 var person = new Symbol<>("person", 1, Boolean.class, false);
154 var personView = new KeyOnlyRelationView<>(person);
155 var friend = new Symbol<>("friend", 2, Boolean.class, false);
156 var friendView = new KeyOnlyRelationView<>(friend);
157 var called = Dnf.builder("Called").parameters(r, s).clause(friendView.call(r, s)).build();
158 var dnf = Dnf.builder("Example")
159 .parameter(p)
160 .clause(
161 personView.call(p),
162 personView.call(q),
163 not(called.call(p, q))
164 )
165 .build();
166
167 assertThat(dnf.toDefinitionString(), is("""
168 pred Example(p) <->
169 @RelationView("key") person(p),
170 @RelationView("key") person(q),
171 !(@Dnf Called(p, q)).
172 """));
173 }
174}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java
new file mode 100644
index 00000000..a61e2b65
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java
@@ -0,0 +1,77 @@
1package tools.refinery.store.query.tests;
2
3import org.junit.jupiter.api.Test;
4import tools.refinery.store.query.dnf.Dnf;
5import tools.refinery.store.query.term.Variable;
6import tools.refinery.store.query.view.KeyOnlyRelationView;
7import tools.refinery.store.representation.Symbol;
8
9import static org.hamcrest.CoreMatchers.containsString;
10import static org.hamcrest.MatcherAssert.assertThat;
11import static org.junit.jupiter.api.Assertions.assertThrows;
12import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
13
14class StructurallyEqualToTest {
15 @Test
16 void flatEqualsTest() {
17 var p = Variable.of("p");
18 var q = Variable.of("q");
19 var person = new Symbol<>("Person", 1, Boolean.class, false);
20 var personView = new KeyOnlyRelationView<>(person);
21
22 var expected = Dnf.builder("Expected").parameters(q).clause(personView.call(q)).build();
23 var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build();
24
25 assertThat(actual, structurallyEqualTo(expected));
26 }
27
28 @Test
29 void flatNotEqualsTest() {
30 var p = Variable.of("p");
31 var q = Variable.of("q");
32 var person = new Symbol<>("Person", 1, Boolean.class, false);
33 var personView = new KeyOnlyRelationView<>(person);
34
35 var expected = Dnf.builder("Expected").parameters(q).clause(personView.call(q)).build();
36 var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(q)).build();
37
38 var assertion = structurallyEqualTo(expected);
39 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
40 }
41
42 @Test
43 void deepEqualsTest() {
44 var p = Variable.of("p");
45 var q = Variable.of("q");
46 var person = new Symbol<>("Person", 1, Boolean.class, false);
47 var personView = new KeyOnlyRelationView<>(person);
48
49 var expected = Dnf.builder("Expected").parameters(q).clause(
50 Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q)
51 ).build();
52 var actual = Dnf.builder("Actual").parameters(q).clause(
53 Dnf.builder("Actual2").parameters(p).clause(personView.call(p)).build().call(q)
54 ).build();
55
56 assertThat(actual, structurallyEqualTo(expected));
57 }
58
59 @Test
60 void deepNotEqualsTest() {
61 var p = Variable.of("p");
62 var q = Variable.of("q");
63 var person = new Symbol<>("Person", 1, Boolean.class, false);
64 var personView = new KeyOnlyRelationView<>(person);
65
66 var expected = Dnf.builder("Expected").parameters(q).clause(
67 Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q)
68 ).build();
69 var actual = Dnf.builder("Actual").parameters(q).clause(
70 Dnf.builder("Actual2").parameters(p).clause(personView.call(q)).build().call(q)
71 ).build();
72
73 var assertion = structurallyEqualTo(expected);
74 var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
75 assertThat(error.getMessage(), containsString(" called from Expected/1 "));
76 }
77}
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java
new file mode 100644
index 00000000..685957c9
--- /dev/null
+++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java
@@ -0,0 +1,43 @@
1package tools.refinery.store.query.tests;
2
3import org.hamcrest.Description;
4import tools.refinery.store.query.equality.DeepDnfEqualityChecker;
5
6class MismatchDescribingDnfEqualityChecker extends DeepDnfEqualityChecker {
7 private final Description description;
8 private boolean described;
9
10 MismatchDescribingDnfEqualityChecker(Description description) {
11 this.description = description;
12 }
13
14 public boolean isDescribed() {
15 return described;
16 }
17
18 @Override
19 protected boolean doCheckEqual(Pair pair) {
20 boolean result = super.doCheckEqual(pair);
21 if (!result && !described) {
22 describeMismatch(pair);
23 // Only describe the first found (innermost) mismatch.
24 described = true;
25 }
26 return result;
27 }
28
29 private void describeMismatch(Pair pair) {
30 var inProgress = getInProgress();
31 int size = inProgress.size();
32 if (size <= 1) {
33 description.appendText("was ").appendText(pair.left().toDefinitionString());
34 return;
35 }
36 var last = inProgress.get(size - 1);
37 description.appendText("expected ").appendText(last.right().toDefinitionString());
38 for (int i = size - 2; i >= 0; i--) {
39 description.appendText(" called from ").appendText(inProgress.get(i).left().toString());
40 }
41 description.appendText(" was not structurally equal to ").appendText(last.right().toDefinitionString());
42 }
43}
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java
new file mode 100644
index 00000000..bf1c1b74
--- /dev/null
+++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java
@@ -0,0 +1,14 @@
1package tools.refinery.store.query.tests;
2
3import org.hamcrest.Matcher;
4import tools.refinery.store.query.dnf.Dnf;
5
6public final class QueryMatchers {
7 private QueryMatchers() {
8 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
9 }
10
11 public static Matcher<Dnf> structurallyEqualTo(Dnf expected) {
12 return new StructurallyEqualTo(expected);
13 }
14}
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java
new file mode 100644
index 00000000..a9a78f88
--- /dev/null
+++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java
@@ -0,0 +1,36 @@
1package tools.refinery.store.query.tests;
2
3import org.hamcrest.Description;
4import org.hamcrest.TypeSafeMatcher;
5import tools.refinery.store.query.dnf.Dnf;
6import tools.refinery.store.query.equality.DeepDnfEqualityChecker;
7
8public class StructurallyEqualTo extends TypeSafeMatcher<Dnf> {
9 private final Dnf expected;
10
11 public StructurallyEqualTo(Dnf expected) {
12 this.expected = expected;
13 }
14
15 @Override
16 protected boolean matchesSafely(Dnf item) {
17 var checker = new DeepDnfEqualityChecker();
18 return checker.dnfEqual(expected, item);
19 }
20
21 @Override
22 protected void describeMismatchSafely(Dnf item, Description mismatchDescription) {
23 var describingChecker = new MismatchDescribingDnfEqualityChecker(mismatchDescription);
24 if (describingChecker.dnfEqual(expected, item)) {
25 throw new IllegalStateException("Mismatched Dnf was matching on repeated comparison");
26 }
27 if (!describingChecker.isDescribed()) {
28 super.describeMismatchSafely(item, mismatchDescription);
29 }
30 }
31
32 @Override
33 public void describeTo(Description description) {
34 description.appendText("structurally equal to ").appendText(expected.toDefinitionString());
35 }
36}