diff options
Diffstat (limited to 'subprojects')
16 files changed, 779 insertions, 29 deletions
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 index e841da9e..c0995e53 100644 --- 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 | |||
@@ -32,7 +32,9 @@ public interface Constraint { | |||
32 | return equals(other); | 32 | return equals(other); |
33 | } | 33 | } |
34 | 34 | ||
35 | String toReferenceString(); | 35 | default String toReferenceString() { |
36 | return name(); | ||
37 | } | ||
36 | 38 | ||
37 | default CallLiteral call(CallPolarity polarity, List<Variable> arguments) { | 39 | default CallLiteral call(CallPolarity polarity, List<Variable> arguments) { |
38 | return new CallLiteral(polarity, this, arguments); | 40 | return new CallLiteral(polarity, this, arguments); |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java index dd45ecd4..b5e7092b 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java | |||
@@ -106,7 +106,7 @@ class ClausePostProcessor { | |||
106 | private Set<NodeVariable> getEquivalentVariables(NodeVariable variable) { | 106 | private Set<NodeVariable> getEquivalentVariables(NodeVariable variable) { |
107 | var representative = getRepresentative(variable); | 107 | var representative = getRepresentative(variable); |
108 | if (!representative.equals(variable)) { | 108 | if (!representative.equals(variable)) { |
109 | throw new IllegalStateException("NodeVariable %s already has a representative %s" | 109 | throw new AssertionError("NodeVariable %s already has a representative %s" |
110 | .formatted(variable, representative)); | 110 | .formatted(variable, representative)); |
111 | } | 111 | } |
112 | return equivalencePartition.computeIfAbsent(variable, key -> { | 112 | return equivalencePartition.computeIfAbsent(variable, key -> { |
@@ -249,7 +249,7 @@ class ClausePostProcessor { | |||
249 | 249 | ||
250 | private void bindVariable(Variable input) { | 250 | private void bindVariable(Variable input) { |
251 | if (!remainingInputs.remove(input)) { | 251 | if (!remainingInputs.remove(input)) { |
252 | throw new IllegalStateException("Already processed input %s of literal %s".formatted(input, literal)); | 252 | throw new AssertionError("Already processed input %s of literal %s".formatted(input, literal)); |
253 | } | 253 | } |
254 | if (allInputsBound()) { | 254 | if (allInputsBound()) { |
255 | addToAllInputsBoundQueue(); | 255 | addToAllInputsBoundQueue(); |
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 index dcf7611d..8e38ca6b 100644 --- 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 | |||
@@ -223,7 +223,8 @@ public final class DnfBuilder { | |||
223 | } else if (result instanceof ClausePostProcessor.ConstantResult constantResult) { | 223 | } else if (result instanceof ClausePostProcessor.ConstantResult constantResult) { |
224 | switch (constantResult) { | 224 | switch (constantResult) { |
225 | case ALWAYS_TRUE -> { | 225 | case ALWAYS_TRUE -> { |
226 | return List.of(new DnfClause(Set.of(), List.of())); | 226 | var inputVariables = getInputVariables(); |
227 | return List.of(new DnfClause(inputVariables, List.of())); | ||
227 | } | 228 | } |
228 | case ALWAYS_FALSE -> { | 229 | case ALWAYS_FALSE -> { |
229 | // Skip this clause because it can never match. | 230 | // Skip this clause because it can never match. |
@@ -248,4 +249,14 @@ public final class DnfBuilder { | |||
248 | } | 249 | } |
249 | return Collections.unmodifiableMap(mutableParameterInfoMap); | 250 | return Collections.unmodifiableMap(mutableParameterInfoMap); |
250 | } | 251 | } |
252 | |||
253 | private Set<Variable> getInputVariables() { | ||
254 | var inputParameters = new LinkedHashSet<Variable>(); | ||
255 | for (var parameter : parameters) { | ||
256 | if (parameter.getDirection() == ParameterDirection.IN) { | ||
257 | inputParameters.add(parameter.getVariable()); | ||
258 | } | ||
259 | } | ||
260 | return Collections.unmodifiableSet(inputParameters); | ||
261 | } | ||
251 | } | 262 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/exceptions/IncompatibleParameterDirectionException.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/exceptions/IncompatibleParameterDirectionException.java deleted file mode 100644 index 52da20ae..00000000 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/exceptions/IncompatibleParameterDirectionException.java +++ /dev/null | |||
@@ -1,16 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.exceptions; | ||
7 | |||
8 | public class IncompatibleParameterDirectionException extends RuntimeException { | ||
9 | public IncompatibleParameterDirectionException(String message) { | ||
10 | super(message); | ||
11 | } | ||
12 | |||
13 | public IncompatibleParameterDirectionException(String message, Throwable cause) { | ||
14 | super(message, cause); | ||
15 | } | ||
16 | } | ||
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 index ed7d3401..8ef8e8b4 100644 --- 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 | |||
@@ -19,6 +19,8 @@ public abstract class AbstractCallLiteral implements Literal { | |||
19 | private final Set<Variable> inArguments; | 19 | private final Set<Variable> inArguments; |
20 | private final Set<Variable> outArguments; | 20 | private final Set<Variable> outArguments; |
21 | 21 | ||
22 | // Use exhaustive switch over enums. | ||
23 | @SuppressWarnings("squid:S1301") | ||
22 | protected AbstractCallLiteral(Constraint target, List<Variable> arguments) { | 24 | protected AbstractCallLiteral(Constraint target, List<Variable> arguments) { |
23 | int arity = target.arity(); | 25 | int arity = target.arity(); |
24 | if (arguments.size() != arity) { | 26 | if (arguments.size() != arity) { |
@@ -59,14 +61,14 @@ public abstract class AbstractCallLiteral implements Literal { | |||
59 | 61 | ||
60 | private static void checkInOutUnifiable(Variable argument) { | 62 | private static void checkInOutUnifiable(Variable argument) { |
61 | if (!argument.isUnifiable()) { | 63 | if (!argument.isUnifiable()) { |
62 | throw new IllegalArgumentException("Arguments %s cannot appear with both %s and %s direction" | 64 | throw new IllegalArgumentException("Argument %s cannot appear with both %s and %s direction" |
63 | .formatted(argument, ParameterDirection.IN, ParameterDirection.OUT)); | 65 | .formatted(argument, ParameterDirection.IN, ParameterDirection.OUT)); |
64 | } | 66 | } |
65 | } | 67 | } |
66 | 68 | ||
67 | private static void checkDuplicateOutUnifiable(Variable argument) { | 69 | private static void checkDuplicateOutUnifiable(Variable argument) { |
68 | if (!argument.isUnifiable()) { | 70 | if (!argument.isUnifiable()) { |
69 | throw new IllegalArgumentException("Arguments %s cannot be bound multiple times".formatted(argument)); | 71 | throw new IllegalArgumentException("Argument %s cannot be bound multiple times".formatted(argument)); |
70 | } | 72 | } |
71 | } | 73 | } |
72 | 74 | ||
@@ -87,12 +89,17 @@ public abstract class AbstractCallLiteral implements Literal { | |||
87 | 89 | ||
88 | @Override | 90 | @Override |
89 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | 91 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { |
90 | return getArgumentsOfDirection(ParameterDirection.IN); | 92 | var inputVariables = new LinkedHashSet<>(getArgumentsOfDirection(ParameterDirection.OUT)); |
93 | inputVariables.retainAll(positiveVariablesInClause); | ||
94 | inputVariables.addAll(getArgumentsOfDirection(ParameterDirection.IN)); | ||
95 | return Collections.unmodifiableSet(inputVariables); | ||
91 | } | 96 | } |
92 | 97 | ||
93 | @Override | 98 | @Override |
94 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { | 99 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { |
95 | return Set.of(); | 100 | var privateVariables = new LinkedHashSet<>(getArgumentsOfDirection(ParameterDirection.OUT)); |
101 | privateVariables.removeAll(positiveVariablesInClause); | ||
102 | return Collections.unmodifiableSet(privateVariables); | ||
96 | } | 103 | } |
97 | 104 | ||
98 | @Override | 105 | @Override |
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 index b2fec430..3a5eb5c7 100644 --- 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 | |||
@@ -61,6 +61,14 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral { | |||
61 | } | 61 | } |
62 | 62 | ||
63 | @Override | 63 | @Override |
64 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
65 | if (positiveVariablesInClause.contains(inputVariable)) { | ||
66 | throw new IllegalArgumentException("Aggregation variable %s must not be bound".formatted(inputVariable)); | ||
67 | } | ||
68 | return super.getInputVariables(positiveVariablesInClause); | ||
69 | } | ||
70 | |||
71 | @Override | ||
64 | public Literal reduce() { | 72 | public Literal reduce() { |
65 | var reduction = getTarget().getReduction(); | 73 | var reduction = getTarget().getReduction(); |
66 | return switch (reduction) { | 74 | return switch (reduction) { |
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 index 27d8ad60..29772aee 100644 --- 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 | |||
@@ -49,6 +49,22 @@ public final class CallLiteral extends AbstractCallLiteral implements CanNegate< | |||
49 | } | 49 | } |
50 | 50 | ||
51 | @Override | 51 | @Override |
52 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
53 | if (polarity.isPositive()) { | ||
54 | return getArgumentsOfDirection(ParameterDirection.IN); | ||
55 | } | ||
56 | return super.getInputVariables(positiveVariablesInClause); | ||
57 | } | ||
58 | |||
59 | @Override | ||
60 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
61 | if (polarity.isPositive()) { | ||
62 | return Set.of(); | ||
63 | } | ||
64 | return super.getPrivateVariables(positiveVariablesInClause); | ||
65 | } | ||
66 | |||
67 | @Override | ||
52 | public Literal reduce() { | 68 | public Literal reduce() { |
53 | var reduction = getTarget().getReduction(); | 69 | var reduction = getTarget().getReduction(); |
54 | var negatedReduction = polarity.isPositive() ? reduction : reduction.negate(); | 70 | var negatedReduction = polarity.isPositive() ? reduction : reduction.negate(); |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java index 0fe297ab..e5a0cdf1 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java | |||
@@ -9,7 +9,7 @@ import java.util.Objects; | |||
9 | import java.util.Optional; | 9 | import java.util.Optional; |
10 | 10 | ||
11 | public class Parameter { | 11 | public class Parameter { |
12 | public static final Parameter NODE_IN_OUT = new Parameter(null, ParameterDirection.OUT); | 12 | public static final Parameter NODE_OUT = new Parameter(null, ParameterDirection.OUT); |
13 | 13 | ||
14 | private final Class<?> dataType; | 14 | private final Class<?> dataType; |
15 | private final ParameterDirection direction; | 15 | private final ParameterDirection direction; |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java index c6f3dd43..c1f9d688 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java | |||
@@ -105,7 +105,7 @@ public abstract class AbstractFunctionView<T> extends SymbolView<T> { | |||
105 | 105 | ||
106 | private static List<Parameter> createParameters(int symbolArity, Parameter outParameter) { | 106 | private static List<Parameter> createParameters(int symbolArity, Parameter outParameter) { |
107 | var parameters = new Parameter[symbolArity + 1]; | 107 | var parameters = new Parameter[symbolArity + 1]; |
108 | Arrays.fill(parameters, Parameter.NODE_IN_OUT); | 108 | Arrays.fill(parameters, Parameter.NODE_OUT); |
109 | parameters[symbolArity] = outParameter; | 109 | parameters[symbolArity] = outParameter; |
110 | return List.of(parameters); | 110 | return List.of(parameters); |
111 | } | 111 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/NodeFunctionView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/NodeFunctionView.java index e9785c67..fcf11506 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/NodeFunctionView.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/NodeFunctionView.java | |||
@@ -11,7 +11,7 @@ import tools.refinery.store.tuple.Tuple1; | |||
11 | 11 | ||
12 | public final class NodeFunctionView extends AbstractFunctionView<Tuple1> { | 12 | public final class NodeFunctionView extends AbstractFunctionView<Tuple1> { |
13 | public NodeFunctionView(Symbol<Tuple1> symbol, String name) { | 13 | public NodeFunctionView(Symbol<Tuple1> symbol, String name) { |
14 | super(symbol, name, Parameter.NODE_IN_OUT); | 14 | super(symbol, name, Parameter.NODE_OUT); |
15 | } | 15 | } |
16 | 16 | ||
17 | public NodeFunctionView(Symbol<Tuple1> symbol) { | 17 | public NodeFunctionView(Symbol<Tuple1> symbol) { |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java index 7e5b7788..6bc5a708 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java | |||
@@ -76,7 +76,7 @@ public abstract class TuplePreservingView<T> extends SymbolView<T> { | |||
76 | 76 | ||
77 | private static List<Parameter> createParameters(int arity) { | 77 | private static List<Parameter> createParameters(int arity) { |
78 | var parameters = new Parameter[arity]; | 78 | var parameters = new Parameter[arity]; |
79 | Arrays.fill(parameters, Parameter.NODE_IN_OUT); | 79 | Arrays.fill(parameters, Parameter.NODE_OUT); |
80 | return List.of(parameters); | 80 | return List.of(parameters); |
81 | } | 81 | } |
82 | } | 82 | } |
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java new file mode 100644 index 00000000..6d53f184 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java | |||
@@ -0,0 +1,112 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | import tools.refinery.store.query.term.ParameterDirection; | ||
11 | import tools.refinery.store.query.term.Variable; | ||
12 | import tools.refinery.store.query.view.AnySymbolView; | ||
13 | import tools.refinery.store.query.view.KeyOnlyView; | ||
14 | import tools.refinery.store.representation.Symbol; | ||
15 | |||
16 | import java.util.List; | ||
17 | |||
18 | import static org.hamcrest.MatcherAssert.assertThat; | ||
19 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
20 | import static tools.refinery.store.query.literal.Literals.not; | ||
21 | import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo; | ||
22 | |||
23 | class TopologicalSortTest { | ||
24 | private static final Symbol<Boolean> friend = new Symbol<>("friend", 2, Boolean.class, false); | ||
25 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
26 | private static final Dnf example = Dnf.of("example", builder -> { | ||
27 | var a = builder.parameter("a", ParameterDirection.IN); | ||
28 | var b = builder.parameter("b", ParameterDirection.IN); | ||
29 | var c = builder.parameter("c", ParameterDirection.OUT); | ||
30 | var d = builder.parameter("d", ParameterDirection.OUT); | ||
31 | builder.clause( | ||
32 | friendView.call(a, b), | ||
33 | friendView.call(b, c), | ||
34 | friendView.call(c, d) | ||
35 | ); | ||
36 | }); | ||
37 | private static final NodeVariable p = Variable.of("p"); | ||
38 | private static final NodeVariable q = Variable.of("q"); | ||
39 | private static final NodeVariable r = Variable.of("r"); | ||
40 | private static final NodeVariable s = Variable.of("s"); | ||
41 | private static final NodeVariable t = Variable.of("t"); | ||
42 | |||
43 | @Test | ||
44 | void topologicalSortTest() { | ||
45 | var actual = Dnf.builder("Actual") | ||
46 | .parameter(p, ParameterDirection.IN) | ||
47 | .parameter(q, ParameterDirection.OUT) | ||
48 | .clause( | ||
49 | not(friendView.call(p, q)), | ||
50 | example.call(p, q, r, s), | ||
51 | example.call(r, t, q, s), | ||
52 | friendView.call(r, t) | ||
53 | ) | ||
54 | .build(); | ||
55 | |||
56 | assertThat(actual, structurallyEqualTo( | ||
57 | List.of( | ||
58 | new SymbolicParameter(p, ParameterDirection.IN), | ||
59 | new SymbolicParameter(q, ParameterDirection.OUT) | ||
60 | ), | ||
61 | List.of( | ||
62 | List.of( | ||
63 | friendView.call(r, t), | ||
64 | example.call(r, t, q, s), | ||
65 | not(friendView.call(p, q)), | ||
66 | example.call(p, q, r, s) | ||
67 | ) | ||
68 | ) | ||
69 | )); | ||
70 | } | ||
71 | |||
72 | @Test | ||
73 | void missingInputTest() { | ||
74 | var builder = Dnf.builder("Actual") | ||
75 | .parameter(p, ParameterDirection.OUT) | ||
76 | .parameter(q, ParameterDirection.OUT) | ||
77 | .clause( | ||
78 | not(friendView.call(p, q)), | ||
79 | example.call(p, q, r, s), | ||
80 | example.call(r, t, q, s), | ||
81 | friendView.call(r, t) | ||
82 | ); | ||
83 | assertThrows(IllegalArgumentException.class, builder::build); | ||
84 | } | ||
85 | |||
86 | @Test | ||
87 | void missingVariableTest() { | ||
88 | var builder = Dnf.builder("Actual") | ||
89 | .parameter(p, ParameterDirection.IN) | ||
90 | .parameter(q, ParameterDirection.OUT) | ||
91 | .clause( | ||
92 | not(friendView.call(p, q)), | ||
93 | example.call(p, q, r, s), | ||
94 | example.call(r, t, q, s) | ||
95 | ); | ||
96 | assertThrows(IllegalArgumentException.class, builder::build); | ||
97 | } | ||
98 | |||
99 | @Test | ||
100 | void circularDependencyTest() { | ||
101 | var builder = Dnf.builder("Actual") | ||
102 | .parameter(p, ParameterDirection.IN) | ||
103 | .parameter(q, ParameterDirection.OUT) | ||
104 | .clause( | ||
105 | not(friendView.call(p, q)), | ||
106 | example.call(p, q, r, s), | ||
107 | example.call(r, t, q, s), | ||
108 | example.call(p, q, r, t) | ||
109 | ); | ||
110 | assertThrows(IllegalArgumentException.class, builder::build); | ||
111 | } | ||
112 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java new file mode 100644 index 00000000..0a44664e --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java | |||
@@ -0,0 +1,428 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import org.junit.jupiter.params.ParameterizedTest; | ||
9 | import org.junit.jupiter.params.provider.Arguments; | ||
10 | import org.junit.jupiter.params.provider.MethodSource; | ||
11 | import tools.refinery.store.query.literal.BooleanLiteral; | ||
12 | import tools.refinery.store.query.literal.Literal; | ||
13 | import tools.refinery.store.query.term.DataVariable; | ||
14 | import tools.refinery.store.query.term.NodeVariable; | ||
15 | import tools.refinery.store.query.term.ParameterDirection; | ||
16 | import tools.refinery.store.query.term.Variable; | ||
17 | import tools.refinery.store.query.view.AnySymbolView; | ||
18 | import tools.refinery.store.query.view.FunctionView; | ||
19 | import tools.refinery.store.query.view.KeyOnlyView; | ||
20 | import tools.refinery.store.representation.Symbol; | ||
21 | |||
22 | import java.util.ArrayList; | ||
23 | import java.util.List; | ||
24 | import java.util.stream.Stream; | ||
25 | |||
26 | import static org.hamcrest.MatcherAssert.assertThat; | ||
27 | import static org.hamcrest.Matchers.hasItem; | ||
28 | import static org.hamcrest.Matchers.not; | ||
29 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; | ||
30 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
31 | import static tools.refinery.store.query.literal.Literals.assume; | ||
32 | import static tools.refinery.store.query.literal.Literals.not; | ||
33 | import static tools.refinery.store.query.term.int_.IntTerms.*; | ||
34 | |||
35 | class VariableDirectionTest { | ||
36 | private static final Symbol<Boolean> person = new Symbol<>("Person", 1, Boolean.class, false); | ||
37 | private static final Symbol<Boolean> friend = new Symbol<>("friend", 2, Boolean.class, false); | ||
38 | private static final Symbol<Integer> age = new Symbol<>("age", 1, Integer.class, null); | ||
39 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
40 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
41 | private static final AnySymbolView ageView = new FunctionView<>(age); | ||
42 | private static final NodeVariable p = Variable.of("p"); | ||
43 | private static final NodeVariable q = Variable.of("q"); | ||
44 | private static final DataVariable<Integer> x = Variable.of("x", Integer.class); | ||
45 | private static final DataVariable<Integer> y = Variable.of("y", Integer.class); | ||
46 | private static final DataVariable<Integer> z = Variable.of("z", Integer.class); | ||
47 | |||
48 | @ParameterizedTest | ||
49 | @MethodSource("clausesWithVariableInput") | ||
50 | void unboundOutVariableTest(List<? extends Literal> clause) { | ||
51 | var builder = Dnf.builder().parameter(p, ParameterDirection.OUT).clause(clause); | ||
52 | assertThrows(IllegalArgumentException.class, builder::build); | ||
53 | } | ||
54 | |||
55 | @ParameterizedTest | ||
56 | @MethodSource("clausesWithVariableInput") | ||
57 | void unboundInVariableTest(List<? extends Literal> clause) { | ||
58 | var builder = Dnf.builder().parameter(p, ParameterDirection.IN).clause(clause); | ||
59 | var dnf = assertDoesNotThrow(builder::build); | ||
60 | var clauses = dnf.getClauses(); | ||
61 | if (clauses.size() > 0) { | ||
62 | assertThat(clauses.get(0).positiveVariables(), hasItem(p)); | ||
63 | } | ||
64 | } | ||
65 | |||
66 | @ParameterizedTest | ||
67 | @MethodSource("clausesWithVariableInput") | ||
68 | void boundPrivateVariableTest(List<? extends Literal> clause) { | ||
69 | var clauseWithBinding = new ArrayList<Literal>(clause); | ||
70 | clauseWithBinding.add(personView.call(p)); | ||
71 | var builder = Dnf.builder().clause(clauseWithBinding); | ||
72 | var dnf = assertDoesNotThrow(builder::build); | ||
73 | var clauses = dnf.getClauses(); | ||
74 | if (clauses.size() > 0) { | ||
75 | assertThat(clauses.get(0).positiveVariables(), hasItem(p)); | ||
76 | } | ||
77 | } | ||
78 | |||
79 | static Stream<Arguments> clausesWithVariableInput() { | ||
80 | return Stream.concat( | ||
81 | clausesNotBindingVariable(), | ||
82 | literalToClauseArgumentStream(literalsWithRequiredVariableInput()) | ||
83 | ); | ||
84 | } | ||
85 | |||
86 | @ParameterizedTest | ||
87 | @MethodSource("clausesNotBindingVariable") | ||
88 | void unboundPrivateVariableTest(List<? extends Literal> clause) { | ||
89 | var builder = Dnf.builder().clause(clause); | ||
90 | var dnf = assertDoesNotThrow(builder::build); | ||
91 | var clauses = dnf.getClauses(); | ||
92 | if (clauses.size() > 0) { | ||
93 | assertThat(clauses.get(0).positiveVariables(), not(hasItem(p))); | ||
94 | } | ||
95 | } | ||
96 | |||
97 | @ParameterizedTest | ||
98 | @MethodSource("clausesNotBindingVariable") | ||
99 | void unboundByEquivalencePrivateVariableTest(List<? extends Literal> clause) { | ||
100 | var r = Variable.of("r"); | ||
101 | var clauseWithEquivalence = new ArrayList<Literal>(clause); | ||
102 | clauseWithEquivalence.add(r.isEquivalent(p)); | ||
103 | var builder = Dnf.builder().clause(clauseWithEquivalence); | ||
104 | assertThrows(IllegalArgumentException.class, builder::build); | ||
105 | } | ||
106 | |||
107 | static Stream<Arguments> clausesNotBindingVariable() { | ||
108 | return Stream.concat( | ||
109 | Stream.of( | ||
110 | Arguments.of(List.of()), | ||
111 | Arguments.of(List.of(BooleanLiteral.TRUE)), | ||
112 | Arguments.of(List.of(BooleanLiteral.FALSE)) | ||
113 | ), | ||
114 | literalToClauseArgumentStream(literalsWithPrivateVariable()) | ||
115 | ); | ||
116 | } | ||
117 | |||
118 | @ParameterizedTest | ||
119 | @MethodSource("literalsWithPrivateVariable") | ||
120 | void unboundTwicePrivateVariableTest(Literal literal) { | ||
121 | var builder = Dnf.builder().clause(not(personView.call(p)), literal); | ||
122 | assertThrows(IllegalArgumentException.class, builder::build); | ||
123 | } | ||
124 | |||
125 | @ParameterizedTest | ||
126 | @MethodSource("literalsWithPrivateVariable") | ||
127 | void unboundTwiceByEquivalencePrivateVariableTest(Literal literal) { | ||
128 | var r = Variable.of("r"); | ||
129 | var builder = Dnf.builder().clause(not(personView.call(r)), r.isEquivalent(p), literal); | ||
130 | assertThrows(IllegalArgumentException.class, builder::build); | ||
131 | } | ||
132 | |||
133 | static Stream<Arguments> literalsWithPrivateVariable() { | ||
134 | var dnfWithOutput = Dnf.builder("WithOutput") | ||
135 | .parameter(p, ParameterDirection.OUT) | ||
136 | .parameter(q, ParameterDirection.OUT) | ||
137 | .clause(friendView.call(p, q)) | ||
138 | .build(); | ||
139 | var dnfWithOutputToAggregate = Dnf.builder("WithOutputToAggregate") | ||
140 | .parameter(p, ParameterDirection.OUT) | ||
141 | .parameter(q, ParameterDirection.OUT) | ||
142 | .parameter(x, ParameterDirection.OUT) | ||
143 | .clause( | ||
144 | friendView.call(p, q), | ||
145 | ageView.call(q, x) | ||
146 | ) | ||
147 | .build(); | ||
148 | |||
149 | return Stream.of( | ||
150 | Arguments.of(not(friendView.call(p, q))), | ||
151 | Arguments.of(y.assign(friendView.count(p, q))), | ||
152 | Arguments.of(y.assign(ageView.aggregate(z, INT_SUM, p, z))), | ||
153 | Arguments.of(not(dnfWithOutput.call(p, q))), | ||
154 | Arguments.of(y.assign(dnfWithOutput.count(p, q))), | ||
155 | Arguments.of(y.assign(dnfWithOutputToAggregate.aggregate(z, INT_SUM, p, q, z))) | ||
156 | ); | ||
157 | } | ||
158 | |||
159 | @ParameterizedTest | ||
160 | @MethodSource("literalsWithRequiredVariableInput") | ||
161 | void unboundPrivateVariableTest(Literal literal) { | ||
162 | var builder = Dnf.builder().clause(literal); | ||
163 | assertThrows(IllegalArgumentException.class, builder::build); | ||
164 | } | ||
165 | |||
166 | @ParameterizedTest | ||
167 | @MethodSource("literalsWithRequiredVariableInput") | ||
168 | void boundPrivateVariableInputTest(Literal literal) { | ||
169 | var builder = Dnf.builder().clause(personView.call(p), literal); | ||
170 | var dnf = assertDoesNotThrow(builder::build); | ||
171 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(p)); | ||
172 | } | ||
173 | |||
174 | static Stream<Arguments> literalsWithRequiredVariableInput() { | ||
175 | var dnfWithInput = Dnf.builder("WithInput") | ||
176 | .parameter(p, ParameterDirection.IN) | ||
177 | .parameter(q, ParameterDirection.OUT) | ||
178 | .clause(friendView.call(p, q)).build(); | ||
179 | var dnfWithInputToAggregate = Dnf.builder("WithInputToAggregate") | ||
180 | .parameter(p, ParameterDirection.IN) | ||
181 | .parameter(q, ParameterDirection.OUT) | ||
182 | .parameter(x, ParameterDirection.OUT) | ||
183 | .clause( | ||
184 | friendView.call(p, q), | ||
185 | ageView.call(q, x) | ||
186 | ).build(); | ||
187 | |||
188 | return Stream.of( | ||
189 | Arguments.of(dnfWithInput.call(p, q)), | ||
190 | Arguments.of(dnfWithInput.call(p, p)), | ||
191 | Arguments.of(not(dnfWithInput.call(p, q))), | ||
192 | Arguments.of(not(dnfWithInput.call(p, p))), | ||
193 | Arguments.of(y.assign(dnfWithInput.count(p, q))), | ||
194 | Arguments.of(y.assign(dnfWithInput.count(p, p))), | ||
195 | Arguments.of(y.assign(dnfWithInputToAggregate.aggregate(z, INT_SUM, p, q, z))), | ||
196 | Arguments.of(y.assign(dnfWithInputToAggregate.aggregate(z, INT_SUM, p, p, z))) | ||
197 | ); | ||
198 | } | ||
199 | |||
200 | @ParameterizedTest | ||
201 | @MethodSource("literalsWithVariableOutput") | ||
202 | void boundParameterTest(Literal literal) { | ||
203 | var builder = Dnf.builder().parameter(p, ParameterDirection.OUT).clause(literal); | ||
204 | var dnf = assertDoesNotThrow(builder::build); | ||
205 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(p)); | ||
206 | } | ||
207 | |||
208 | @ParameterizedTest | ||
209 | @MethodSource("literalsWithVariableOutput") | ||
210 | void boundTwiceParameterTest(Literal literal) { | ||
211 | var builder = Dnf.builder().parameter(p, ParameterDirection.IN).clause(literal); | ||
212 | var dnf = assertDoesNotThrow(builder::build); | ||
213 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(p)); | ||
214 | } | ||
215 | |||
216 | @ParameterizedTest | ||
217 | @MethodSource("literalsWithVariableOutput") | ||
218 | void boundPrivateVariableOutputTest(Literal literal) { | ||
219 | var dnfWithInput = Dnf.builder("WithInput") | ||
220 | .parameter(p, ParameterDirection.IN) | ||
221 | .clause(personView.call(p)) | ||
222 | .build(); | ||
223 | var builder = Dnf.builder().clause(dnfWithInput.call(p), literal); | ||
224 | var dnf = assertDoesNotThrow(builder::build); | ||
225 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(p)); | ||
226 | } | ||
227 | |||
228 | @ParameterizedTest | ||
229 | @MethodSource("literalsWithVariableOutput") | ||
230 | void boundTwicePrivateVariableOutputTest(Literal literal) { | ||
231 | var builder = Dnf.builder().clause(personView.call(p), literal); | ||
232 | var dnf = assertDoesNotThrow(builder::build); | ||
233 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(p)); | ||
234 | } | ||
235 | |||
236 | static Stream<Arguments> literalsWithVariableOutput() { | ||
237 | var dnfWithOutput = Dnf.builder("WithOutput") | ||
238 | .parameter(p, ParameterDirection.OUT) | ||
239 | .parameter(q, ParameterDirection.OUT) | ||
240 | .clause(friendView.call(p, q)) | ||
241 | .build(); | ||
242 | |||
243 | return Stream.of( | ||
244 | Arguments.of(friendView.call(p, q)), | ||
245 | Arguments.of(dnfWithOutput.call(p, q)) | ||
246 | ); | ||
247 | } | ||
248 | |||
249 | @ParameterizedTest | ||
250 | @MethodSource("clausesWithDataVariableInput") | ||
251 | void unboundOutDataVariableTest(List<? extends Literal> clause) { | ||
252 | var builder = Dnf.builder().parameter(x, ParameterDirection.OUT).clause(clause); | ||
253 | assertThrows(IllegalArgumentException.class, builder::build); | ||
254 | } | ||
255 | |||
256 | @ParameterizedTest | ||
257 | @MethodSource("clausesWithDataVariableInput") | ||
258 | void unboundInDataVariableTest(List<? extends Literal> clause) { | ||
259 | var builder = Dnf.builder().parameter(x, ParameterDirection.IN).clause(clause); | ||
260 | var dnf = assertDoesNotThrow(builder::build); | ||
261 | var clauses = dnf.getClauses(); | ||
262 | if (clauses.size() > 0) { | ||
263 | assertThat(clauses.get(0).positiveVariables(), hasItem(x)); | ||
264 | } | ||
265 | } | ||
266 | |||
267 | @ParameterizedTest | ||
268 | @MethodSource("clausesWithDataVariableInput") | ||
269 | void boundPrivateDataVariableTest(List<? extends Literal> clause) { | ||
270 | var clauseWithBinding = new ArrayList<Literal>(clause); | ||
271 | clauseWithBinding.add(x.assign(constant(27))); | ||
272 | var builder = Dnf.builder().clause(clauseWithBinding); | ||
273 | var dnf = assertDoesNotThrow(builder::build); | ||
274 | var clauses = dnf.getClauses(); | ||
275 | if (clauses.size() > 0) { | ||
276 | assertThat(clauses.get(0).positiveVariables(), hasItem(x)); | ||
277 | } | ||
278 | } | ||
279 | |||
280 | static Stream<Arguments> clausesWithDataVariableInput() { | ||
281 | return Stream.concat( | ||
282 | clausesNotBindingDataVariable(), | ||
283 | literalToClauseArgumentStream(literalsWithRequiredDataVariableInput()) | ||
284 | ); | ||
285 | } | ||
286 | |||
287 | @ParameterizedTest | ||
288 | @MethodSource("clausesNotBindingDataVariable") | ||
289 | void unboundPrivateDataVariableTest(List<? extends Literal> clause) { | ||
290 | var builder = Dnf.builder().clause(clause); | ||
291 | var dnf = assertDoesNotThrow(builder::build); | ||
292 | var clauses = dnf.getClauses(); | ||
293 | if (clauses.size() > 0) { | ||
294 | assertThat(clauses.get(0).positiveVariables(), not(hasItem(x))); | ||
295 | } | ||
296 | } | ||
297 | |||
298 | static Stream<Arguments> clausesNotBindingDataVariable() { | ||
299 | return Stream.concat( | ||
300 | Stream.of( | ||
301 | Arguments.of(List.of()), | ||
302 | Arguments.of(List.of(BooleanLiteral.TRUE)), | ||
303 | Arguments.of(List.of(BooleanLiteral.FALSE)) | ||
304 | ), | ||
305 | literalToClauseArgumentStream(literalsWithPrivateDataVariable()) | ||
306 | ); | ||
307 | } | ||
308 | |||
309 | @ParameterizedTest | ||
310 | @MethodSource("literalsWithPrivateDataVariable") | ||
311 | void unboundTwicePrivateDataVariableTest(Literal literal) { | ||
312 | var builder = Dnf.builder().clause(not(ageView.call(p, x)), literal); | ||
313 | assertThrows(IllegalArgumentException.class, builder::build); | ||
314 | } | ||
315 | |||
316 | static Stream<Arguments> literalsWithPrivateDataVariable() { | ||
317 | var dnfWithOutput = Dnf.builder("WithDataOutput") | ||
318 | .parameter(y, ParameterDirection.OUT) | ||
319 | .parameter(q, ParameterDirection.OUT) | ||
320 | .clause(ageView.call(q, y)) | ||
321 | .build(); | ||
322 | |||
323 | return Stream.of( | ||
324 | Arguments.of(not(ageView.call(q, x))), | ||
325 | Arguments.of(y.assign(ageView.count(q, x))), | ||
326 | Arguments.of(not(dnfWithOutput.call(x, q))) | ||
327 | ); | ||
328 | } | ||
329 | |||
330 | @ParameterizedTest | ||
331 | @MethodSource("literalsWithRequiredDataVariableInput") | ||
332 | void unboundPrivateDataVariableTest(Literal literal) { | ||
333 | var builder = Dnf.builder().clause(literal); | ||
334 | assertThrows(IllegalArgumentException.class, builder::build); | ||
335 | } | ||
336 | |||
337 | static Stream<Arguments> literalsWithRequiredDataVariableInput() { | ||
338 | var dnfWithInput = Dnf.builder("WithDataInput") | ||
339 | .parameter(y, ParameterDirection.IN) | ||
340 | .parameter(q, ParameterDirection.OUT) | ||
341 | .clause(ageView.call(q, x)) | ||
342 | .build(); | ||
343 | // We are passing {@code y} to the parameter named {@code right} of {@code greaterEq}. | ||
344 | @SuppressWarnings("SuspiciousNameCombination") | ||
345 | var dnfWithInputToAggregate = Dnf.builder("WithDataInputToAggregate") | ||
346 | .parameter(y, ParameterDirection.IN) | ||
347 | .parameter(q, ParameterDirection.OUT) | ||
348 | .parameter(x, ParameterDirection.OUT) | ||
349 | .clause( | ||
350 | friendView.call(p, q), | ||
351 | ageView.call(q, x), | ||
352 | assume(greaterEq(x, y)) | ||
353 | ) | ||
354 | .build(); | ||
355 | |||
356 | return Stream.of( | ||
357 | Arguments.of(dnfWithInput.call(x, q)), | ||
358 | Arguments.of(not(dnfWithInput.call(x, q))), | ||
359 | Arguments.of(y.assign(dnfWithInput.count(x, q))), | ||
360 | Arguments.of(y.assign(dnfWithInputToAggregate.aggregate(z, INT_SUM, x, q, z))) | ||
361 | ); | ||
362 | } | ||
363 | |||
364 | @ParameterizedTest | ||
365 | @MethodSource("literalsWithDataVariableOutput") | ||
366 | void boundDataParameterTest(Literal literal) { | ||
367 | var builder = Dnf.builder().parameter(x, ParameterDirection.OUT).clause(literal); | ||
368 | var dnf = assertDoesNotThrow(builder::build); | ||
369 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(x)); | ||
370 | } | ||
371 | |||
372 | @ParameterizedTest | ||
373 | @MethodSource("literalsWithDataVariableOutput") | ||
374 | void boundTwiceDataParameterTest(Literal literal) { | ||
375 | var builder = Dnf.builder().parameter(x, ParameterDirection.IN).clause(literal); | ||
376 | assertThrows(IllegalArgumentException.class, builder::build); | ||
377 | } | ||
378 | |||
379 | @ParameterizedTest | ||
380 | @MethodSource("literalsWithDataVariableOutput") | ||
381 | void boundPrivateDataVariableOutputTest(Literal literal) { | ||
382 | var dnfWithInput = Dnf.builder("WithInput") | ||
383 | .parameter(x, ParameterDirection.IN) | ||
384 | .clause(assume(greaterEq(x, constant(24)))) | ||
385 | .build(); | ||
386 | var builder = Dnf.builder().clause(dnfWithInput.call(x), literal); | ||
387 | var dnf = assertDoesNotThrow(builder::build); | ||
388 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(x)); | ||
389 | } | ||
390 | |||
391 | @ParameterizedTest | ||
392 | @MethodSource("literalsWithDataVariableOutput") | ||
393 | void boundTwicePrivateDataVariableOutputTest(Literal literal) { | ||
394 | var builder = Dnf.builder().clause(x.assign(constant(27)), literal); | ||
395 | assertThrows(IllegalArgumentException.class, builder::build); | ||
396 | } | ||
397 | |||
398 | static Stream<Arguments> literalsWithDataVariableOutput() { | ||
399 | var dnfWithOutput = Dnf.builder("WithOutput") | ||
400 | .parameter(q, ParameterDirection.OUT) | ||
401 | .clause(personView.call(q)) | ||
402 | .build(); | ||
403 | var dnfWithDataOutput = Dnf.builder("WithDataOutput") | ||
404 | .parameter(y, ParameterDirection.OUT) | ||
405 | .parameter(q, ParameterDirection.OUT) | ||
406 | .clause(ageView.call(q, y)) | ||
407 | .build(); | ||
408 | var dnfWithOutputToAggregate = Dnf.builder("WithDataOutputToAggregate") | ||
409 | .parameter(q, ParameterDirection.OUT) | ||
410 | .parameter(y, ParameterDirection.OUT) | ||
411 | .clause(ageView.call(q, y)) | ||
412 | .build(); | ||
413 | |||
414 | return Stream.of( | ||
415 | Arguments.of(x.assign(constant(24))), | ||
416 | Arguments.of(ageView.call(q, x)), | ||
417 | Arguments.of(x.assign(personView.count(q))), | ||
418 | Arguments.of(x.assign(ageView.aggregate(z, INT_SUM, q, z))), | ||
419 | Arguments.of(dnfWithDataOutput.call(x, q)), | ||
420 | Arguments.of(x.assign(dnfWithOutput.count(q))), | ||
421 | Arguments.of(x.assign(dnfWithOutputToAggregate.aggregate(z, INT_SUM, q, z))) | ||
422 | ); | ||
423 | } | ||
424 | |||
425 | private static Stream<Arguments> literalToClauseArgumentStream(Stream<Arguments> literalArgumentsStream) { | ||
426 | return literalArgumentsStream.map(arguments -> Arguments.of(List.of(arguments.get()[0]))); | ||
427 | } | ||
428 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java new file mode 100644 index 00000000..5293b273 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java | |||
@@ -0,0 +1,88 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.Constraint; | ||
10 | import tools.refinery.store.query.dnf.Dnf; | ||
11 | import tools.refinery.store.query.term.*; | ||
12 | |||
13 | import java.util.List; | ||
14 | import java.util.Set; | ||
15 | |||
16 | import static org.hamcrest.MatcherAssert.assertThat; | ||
17 | import static org.hamcrest.Matchers.containsInAnyOrder; | ||
18 | import static org.hamcrest.Matchers.empty; | ||
19 | import static org.junit.jupiter.api.Assertions.assertAll; | ||
20 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
21 | import static tools.refinery.store.query.literal.Literals.not; | ||
22 | import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM; | ||
23 | import static tools.refinery.store.query.term.int_.IntTerms.constant; | ||
24 | |||
25 | class AggregationLiteralTest { | ||
26 | private static final NodeVariable p = Variable.of("p"); | ||
27 | private static final DataVariable<Integer> x = Variable.of("x", Integer.class); | ||
28 | private static final DataVariable<Integer> y = Variable.of("y", Integer.class); | ||
29 | private static final DataVariable<Integer> z = Variable.of("z", Integer.class); | ||
30 | private static final Constraint fakeConstraint = new Constraint() { | ||
31 | @Override | ||
32 | public String name() { | ||
33 | return getClass().getName(); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public List<Parameter> getParameters() { | ||
38 | return List.of( | ||
39 | new Parameter(null, ParameterDirection.OUT), | ||
40 | new Parameter(Integer.class, ParameterDirection.OUT) | ||
41 | ); | ||
42 | } | ||
43 | }; | ||
44 | |||
45 | @Test | ||
46 | void parameterDirectionTest() { | ||
47 | var literal = x.assign(fakeConstraint.aggregate(y, INT_SUM, p, y)); | ||
48 | assertAll( | ||
49 | () -> assertThat(literal.getOutputVariables(), containsInAnyOrder(x)), | ||
50 | () -> assertThat(literal.getInputVariables(Set.of()), empty()), | ||
51 | () -> assertThat(literal.getInputVariables(Set.of(p)), containsInAnyOrder(p)), | ||
52 | () -> assertThat(literal.getPrivateVariables(Set.of()), containsInAnyOrder(p, y)), | ||
53 | () -> assertThat(literal.getPrivateVariables(Set.of(p)), containsInAnyOrder(y)) | ||
54 | ); | ||
55 | } | ||
56 | |||
57 | @Test | ||
58 | void missingAggregationVariableTest() { | ||
59 | var aggregation = fakeConstraint.aggregate(y, INT_SUM, p, z); | ||
60 | assertThrows(IllegalArgumentException.class, () -> x.assign(aggregation)); | ||
61 | } | ||
62 | |||
63 | @Test | ||
64 | void circularAggregationVariableTest() { | ||
65 | var aggregation = fakeConstraint.aggregate(x, INT_SUM, p, x); | ||
66 | assertThrows(IllegalArgumentException.class, () -> x.assign(aggregation)); | ||
67 | } | ||
68 | |||
69 | @Test | ||
70 | void unboundTwiceVariableTest() { | ||
71 | var builder = Dnf.builder() | ||
72 | .clause( | ||
73 | not(fakeConstraint.call(p, y)), | ||
74 | x.assign(fakeConstraint.aggregate(y, INT_SUM, p, y)) | ||
75 | ); | ||
76 | assertThrows(IllegalArgumentException.class, builder::build); | ||
77 | } | ||
78 | |||
79 | @Test | ||
80 | void unboundBoundVariableTest() { | ||
81 | var builder = Dnf.builder() | ||
82 | .clause( | ||
83 | y.assign(constant(27)), | ||
84 | x.assign(fakeConstraint.aggregate(y, INT_SUM, p, y)) | ||
85 | ); | ||
86 | assertThrows(IllegalArgumentException.class, builder::build); | ||
87 | } | ||
88 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/CallLiteralTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/CallLiteralTest.java new file mode 100644 index 00000000..a01c6586 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/CallLiteralTest.java | |||
@@ -0,0 +1,94 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.Constraint; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | import tools.refinery.store.query.term.Parameter; | ||
12 | import tools.refinery.store.query.term.ParameterDirection; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | |||
15 | import java.util.List; | ||
16 | import java.util.Set; | ||
17 | |||
18 | import static org.hamcrest.MatcherAssert.assertThat; | ||
19 | import static org.hamcrest.Matchers.containsInAnyOrder; | ||
20 | import static org.hamcrest.Matchers.empty; | ||
21 | import static org.junit.jupiter.api.Assertions.assertAll; | ||
22 | import static tools.refinery.store.query.literal.Literals.not; | ||
23 | |||
24 | class CallLiteralTest { | ||
25 | private static final NodeVariable p = Variable.of("p"); | ||
26 | private static final NodeVariable q = Variable.of("q"); | ||
27 | private static final NodeVariable r = Variable.of("r"); | ||
28 | private static final NodeVariable s = Variable.of("s"); | ||
29 | |||
30 | private static final Constraint fakeConstraint = new Constraint() { | ||
31 | @Override | ||
32 | public String name() { | ||
33 | return getClass().getName(); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public List<Parameter> getParameters() { | ||
38 | return List.of( | ||
39 | new Parameter(null, ParameterDirection.IN), | ||
40 | new Parameter(null, ParameterDirection.IN), | ||
41 | new Parameter(null, ParameterDirection.OUT), | ||
42 | new Parameter(null, ParameterDirection.OUT) | ||
43 | ); | ||
44 | } | ||
45 | }; | ||
46 | |||
47 | @Test | ||
48 | void notRepeatedPositiveDirectionTest() { | ||
49 | var literal = fakeConstraint.call(p, q, r, s); | ||
50 | assertAll( | ||
51 | () -> assertThat(literal.getOutputVariables(), containsInAnyOrder(r, s)), | ||
52 | () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p, q)), | ||
53 | () -> assertThat(literal.getInputVariables(Set.of(p, q, r)), containsInAnyOrder(p, q)), | ||
54 | () -> assertThat(literal.getPrivateVariables(Set.of()), empty()), | ||
55 | () -> assertThat(literal.getPrivateVariables(Set.of(p, q, r)), empty()) | ||
56 | ); | ||
57 | } | ||
58 | |||
59 | @Test | ||
60 | void notRepeatedNegativeDirectionTest() { | ||
61 | var literal = not(fakeConstraint.call(p, q, r, s)); | ||
62 | assertAll( | ||
63 | () -> assertThat(literal.getOutputVariables(), empty()), | ||
64 | () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p, q)), | ||
65 | () -> assertThat(literal.getInputVariables(Set.of(p, q, r)), containsInAnyOrder(p, q, r)), | ||
66 | () -> assertThat(literal.getPrivateVariables(Set.of()), containsInAnyOrder(r, s)), | ||
67 | () -> assertThat(literal.getPrivateVariables(Set.of(p, q, r)), containsInAnyOrder(s)) | ||
68 | ); | ||
69 | } | ||
70 | |||
71 | @Test | ||
72 | void repeatedPositiveDirectionTest() { | ||
73 | var literal = fakeConstraint.call(p, p, q, q); | ||
74 | assertAll( | ||
75 | () -> assertThat(literal.getOutputVariables(), containsInAnyOrder(q)), | ||
76 | () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p)), | ||
77 | () -> assertThat(literal.getInputVariables(Set.of(p, q)), containsInAnyOrder(p)), | ||
78 | () -> assertThat(literal.getPrivateVariables(Set.of()), empty()), | ||
79 | () -> assertThat(literal.getPrivateVariables(Set.of(p, q)), empty()) | ||
80 | ); | ||
81 | } | ||
82 | |||
83 | @Test | ||
84 | void repeatedNegativeDirectionTest() { | ||
85 | var literal = not(fakeConstraint.call(p, p, q, q)); | ||
86 | assertAll( | ||
87 | () -> assertThat(literal.getOutputVariables(), empty()), | ||
88 | () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p)), | ||
89 | () -> assertThat(literal.getInputVariables(Set.of(p, q)), containsInAnyOrder(p, q)), | ||
90 | () -> assertThat(literal.getPrivateVariables(Set.of()), containsInAnyOrder(q)), | ||
91 | () -> assertThat(literal.getPrivateVariables(Set.of(p, q)), empty()) | ||
92 | ); | ||
93 | } | ||
94 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java index 1f74ce38..6b2f050b 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java | |||
@@ -33,7 +33,7 @@ public record PartialRelation(String name, int arity) implements PartialSymbol<T | |||
33 | @Override | 33 | @Override |
34 | public List<Parameter> getParameters() { | 34 | public List<Parameter> getParameters() { |
35 | var parameters = new Parameter[arity]; | 35 | var parameters = new Parameter[arity]; |
36 | Arrays.fill(parameters, Parameter.NODE_IN_OUT); | 36 | Arrays.fill(parameters, Parameter.NODE_OUT); |
37 | return List.of(parameters); | 37 | return List.of(parameters); |
38 | } | 38 | } |
39 | 39 | ||