aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store-query
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/store-query')
-rw-r--r--subprojects/store-query/build.gradle.kts15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java72
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java26
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AbstractQueryBuilder.java175
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java324
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java213
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java262
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java28
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java24
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java20
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java99
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java179
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java27
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java66
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java52
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback0.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data0.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data1.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data0.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data1.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data2.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data0.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data1.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data2.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data3.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data0.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data1.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data2.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data3.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data4.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback0.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback1.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback2.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback3.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback4.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback0.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback1.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback2.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback3.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback4.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java78
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java54
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java143
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java138
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java79
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java83
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java63
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java130
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java37
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java10
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java65
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java101
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java81
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Reduction.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java63
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java49
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java80
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSetListener.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java18
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java20
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java23
-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/SubstitutionBuilder.java79
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java41
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java18
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java49
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java113
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java71
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java82
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java108
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java60
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java60
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java28
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java25
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java26
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java79
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java84
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolAndTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolBinaryTerm.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolOrTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java35
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolXorTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/ComparisonTerm.java19
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/EqTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterEqTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessEqTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/NotEqTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntAddTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntBinaryTerm.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntDivTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMaxTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinusTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMulTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPlusTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPowTerm.java43
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSubTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java40
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java94
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntUnaryTerm.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealAddTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealBinaryTerm.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealDivTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMaxTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinusTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMulTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPlusTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPowTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSubTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java90
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java94
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealUnaryTerm.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityAddTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityBinaryTerm.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMaxTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMinTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMulTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java86
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTerms.java73
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java754
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/MapBasedValuation.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java37
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/ValuationBuilder.java40
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java110
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java73
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenView.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java36
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyView.java41
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayView.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustView.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/NodeFunctionView.java20
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/SymbolView.java85
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java82
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/ViewImplication.java23
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java210
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java325
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java157
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java112
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java428
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java88
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/literal/CallLiteralTest.java94
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java97
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/term/bool/BoolTermsEvaluateTest.java75
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/term/int_/IntTermsEvaluateTest.java259
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/term/real/RealTermEvaluateTest.java238
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java56
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorTest.java80
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java104
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToRawTest.java159
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java127
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java634
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java68
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java46
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java41
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualToRaw.java51
177 files changed, 11143 insertions, 0 deletions
diff --git a/subprojects/store-query/build.gradle.kts b/subprojects/store-query/build.gradle.kts
new file mode 100644
index 00000000..4d8e2605
--- /dev/null
+++ b/subprojects/store-query/build.gradle.kts
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7plugins {
8 id("tools.refinery.gradle.java-library")
9 id("tools.refinery.gradle.java-test-fixtures")
10}
11
12dependencies {
13 api(project(":refinery-store"))
14 testFixturesApi(libs.hamcrest)
15}
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..916fb35c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java
@@ -0,0 +1,72 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.literal.*;
10import tools.refinery.store.query.term.*;
11
12import java.util.List;
13
14public interface Constraint {
15 String name();
16
17 List<Parameter> getParameters();
18
19 default int arity() {
20 return getParameters().size();
21 }
22
23 default boolean invalidIndex(int i) {
24 return i < 0 || i >= arity();
25 }
26
27 default Reduction getReduction() {
28 return Reduction.NOT_REDUCIBLE;
29 }
30
31 default boolean equals(LiteralEqualityHelper helper, Constraint other) {
32 return equals(other);
33 }
34
35 default String toReferenceString() {
36 return name();
37 }
38
39 default CallLiteral call(CallPolarity polarity, List<Variable> arguments) {
40 return new CallLiteral(polarity, this, arguments);
41 }
42
43 default CallLiteral call(CallPolarity polarity, Variable... arguments) {
44 return call(polarity, List.of(arguments));
45 }
46
47 default CallLiteral call(Variable... arguments) {
48 return call(CallPolarity.POSITIVE, arguments);
49 }
50
51 default CallLiteral callTransitive(NodeVariable left, NodeVariable right) {
52 return call(CallPolarity.TRANSITIVE, List.of(left, right));
53 }
54
55 default AssignedValue<Integer> count(List<Variable> arguments) {
56 return targetVariable -> new CountLiteral(targetVariable, this, arguments);
57 }
58
59 default AssignedValue<Integer> count(Variable... arguments) {
60 return count(List.of(arguments));
61 }
62
63 default <R, T> AssignedValue<R> aggregateBy(DataVariable<T> inputVariable, Aggregator<R, T> aggregator,
64 List<Variable> arguments) {
65 return targetVariable -> new AggregationLiteral<>(targetVariable, aggregator, inputVariable, this, arguments);
66 }
67
68 default <R, T> AssignedValue<R> aggregateBy(DataVariable<T> inputVariable, Aggregator<R, T> aggregator,
69 Variable... arguments) {
70 return aggregateBy(inputVariable, aggregator, List.of(arguments));
71 }
72}
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..1fa96a07
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java
@@ -0,0 +1,26 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query;
7
8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.query.dnf.AnyQuery;
10import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.resultset.AnyResultSet;
12import tools.refinery.store.query.resultset.ResultSet;
13
14public interface ModelQueryAdapter extends ModelAdapter {
15 ModelQueryStoreAdapter getStoreAdapter();
16
17 default AnyResultSet getResultSet(AnyQuery query) {
18 return getResultSet((Query<?>) query);
19 }
20
21 <T> ResultSet<T> getResultSet(Query<T> query);
22
23 boolean hasPendingChanges();
24
25 void flushChanges();
26}
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..c62a95b5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query;
7
8import tools.refinery.store.adapter.ModelAdapterBuilder;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.dnf.AnyQuery;
11
12import java.util.Collection;
13import java.util.List;
14
15@SuppressWarnings("UnusedReturnValue")
16public interface ModelQueryBuilder extends ModelAdapterBuilder {
17 default ModelQueryBuilder queries(AnyQuery... queries) {
18 return queries(List.of(queries));
19 }
20
21 default ModelQueryBuilder queries(Collection<? extends AnyQuery> queries) {
22 queries.forEach(this::query);
23 return this;
24 }
25
26 ModelQueryBuilder query(AnyQuery query);
27
28 @Override
29 ModelQueryStoreAdapter build(ModelStore store);
30}
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..f0a950a6
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query;
7
8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.query.dnf.AnyQuery;
11import tools.refinery.store.query.view.AnySymbolView;
12
13import java.util.Collection;
14
15public interface ModelQueryStoreAdapter extends ModelStoreAdapter {
16 Collection<AnySymbolView> getSymbolViews();
17
18 Collection<AnyQuery> getQueries();
19
20 @Override
21 ModelQueryAdapter createModelAdapter(Model model);
22}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AbstractQueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AbstractQueryBuilder.java
new file mode 100644
index 00000000..2a3e3ce0
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AbstractQueryBuilder.java
@@ -0,0 +1,175 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.dnf.callback.*;
9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.Collection;
15import java.util.List;
16import java.util.Set;
17
18public abstract class AbstractQueryBuilder<T extends AbstractQueryBuilder<T>> {
19 protected final DnfBuilder dnfBuilder;
20
21 protected AbstractQueryBuilder(DnfBuilder dnfBuilder) {
22 this.dnfBuilder = dnfBuilder;
23 }
24
25 protected abstract T self();
26
27 public NodeVariable parameter() {
28 return dnfBuilder.parameter();
29 }
30
31 public NodeVariable parameter(String name) {
32 return dnfBuilder.parameter(name);
33 }
34
35 public NodeVariable parameter(ParameterDirection direction) {
36 return dnfBuilder.parameter(direction);
37 }
38
39 public NodeVariable parameter(String name, ParameterDirection direction) {
40 return dnfBuilder.parameter(name, direction);
41 }
42
43 public T parameter(NodeVariable variable) {
44 dnfBuilder.parameter(variable);
45 return self();
46 }
47
48 public T parameter(NodeVariable variable, ParameterDirection direction) {
49 dnfBuilder.parameter(variable, direction);
50 return self();
51 }
52
53 public T parameters(NodeVariable... variables) {
54 dnfBuilder.parameters(variables);
55 return self();
56 }
57
58 public T parameters(List<NodeVariable> variables) {
59 dnfBuilder.parameters(variables);
60 return self();
61 }
62
63 public T parameters(List<NodeVariable> variables, ParameterDirection direction) {
64 dnfBuilder.parameters(variables, direction);
65 return self();
66 }
67
68 public T symbolicParameters(List<SymbolicParameter> parameters) {
69 dnfBuilder.symbolicParameters(parameters);
70 return self();
71 }
72
73 public T functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) {
74 dnfBuilder.functionalDependencies(functionalDependencies);
75 return self();
76 }
77
78 public T functionalDependency(FunctionalDependency<Variable> functionalDependency) {
79 dnfBuilder.functionalDependency(functionalDependency);
80 return self();
81 }
82
83 public T functionalDependency(Set<? extends Variable> forEach, Set<? extends Variable> unique) {
84 dnfBuilder.functionalDependency(forEach, unique);
85 return self();
86 }
87
88 public T clause(ClauseCallback0 callback) {
89 dnfBuilder.clause(callback);
90 return self();
91 }
92
93 public T clause(ClauseCallback1Data0 callback) {
94 dnfBuilder.clause(callback);
95 return self();
96 }
97
98 public <U1> T clause(Class<U1> type1, ClauseCallback1Data1<U1> callback) {
99 dnfBuilder.clause(type1, callback);
100 return self();
101 }
102
103 public T clause(ClauseCallback2Data0 callback) {
104 dnfBuilder.clause(callback);
105 return self();
106 }
107
108 public <U1> T clause(Class<U1> type1, ClauseCallback2Data1<U1> callback) {
109 dnfBuilder.clause(type1, callback);
110 return self();
111 }
112
113 public <U1, U2> T clause(Class<U1> type1, Class<U2> type2, ClauseCallback2Data2<U1, U2> callback) {
114 dnfBuilder.clause(type1, type2, callback);
115 return self();
116 }
117
118 public T clause(ClauseCallback3Data0 callback) {
119 dnfBuilder.clause(callback);
120 return self();
121 }
122
123 public <U1> T clause(Class<U1> type1, ClauseCallback3Data1<U1> callback) {
124 dnfBuilder.clause(type1, callback);
125 return self();
126 }
127
128 public <U1, U2> T clause(Class<U1> type1, Class<U2> type2, ClauseCallback3Data2<U1, U2> callback) {
129 dnfBuilder.clause(type1, type2, callback);
130 return self();
131 }
132
133 public <U1, U2, U3> T clause(Class<U1> type1, Class<U2> type2, Class<U3> type3,
134 ClauseCallback3Data3<U1, U2, U3> callback) {
135 dnfBuilder.clause(type1, type2, type3, callback);
136 return self();
137 }
138
139 public T clause(ClauseCallback4Data0 callback) {
140 dnfBuilder.clause(callback);
141 return self();
142 }
143
144 public <U1> T clause(Class<U1> type1, ClauseCallback4Data1<U1> callback) {
145 dnfBuilder.clause(type1, callback);
146 return self();
147 }
148
149 public <U1, U2> T clause(Class<U1> type1, Class<U2> type2, ClauseCallback4Data2<U1, U2> callback) {
150 dnfBuilder.clause(type1, type2, callback);
151 return self();
152 }
153
154 public <U1, U2, U3> T clause(Class<U1> type1, Class<U2> type2, Class<U3> type3,
155 ClauseCallback4Data3<U1, U2, U3> callback) {
156 dnfBuilder.clause(type1, type2, type3, callback);
157 return self();
158 }
159
160 public <U1, U2, U3, U4> T clause(Class<U1> type1, Class<U2> type2, Class<U3> type3, Class<U4> type4,
161 ClauseCallback4Data4<U1, U2, U3, U4> callback) {
162 dnfBuilder.clause(type1, type2, type3, type4, callback);
163 return self();
164 }
165
166 public T clause(Literal... literals) {
167 dnfBuilder.clause(literals);
168 return self();
169 }
170
171 public T clause(Collection<? extends Literal> literals) {
172 dnfBuilder.clause(literals);
173 return self();
174 }
175}
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..5e28af68
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8public sealed interface AnyQuery permits Query {
9 String name();
10
11 int arity();
12
13 Class<?> valueType();
14
15 Dnf getDnf();
16}
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
new file mode 100644
index 00000000..b5e7092b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java
@@ -0,0 +1,324 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import org.jetbrains.annotations.NotNull;
9import tools.refinery.store.query.literal.BooleanLiteral;
10import tools.refinery.store.query.literal.EquivalenceLiteral;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.substitution.MapBasedSubstitution;
13import tools.refinery.store.query.substitution.StatelessSubstitution;
14import tools.refinery.store.query.substitution.Substitution;
15import tools.refinery.store.query.term.NodeVariable;
16import tools.refinery.store.query.term.ParameterDirection;
17import tools.refinery.store.query.term.Variable;
18
19import java.util.*;
20import java.util.function.Function;
21
22class ClausePostProcessor {
23 private final Map<Variable, ParameterInfo> parameters;
24 private final List<Literal> literals;
25 private final Map<NodeVariable, NodeVariable> representatives = new LinkedHashMap<>();
26 private final Map<NodeVariable, Set<NodeVariable>> equivalencePartition = new HashMap<>();
27 private List<Literal> substitutedLiterals;
28 private final Set<Variable> existentiallyQuantifiedVariables = new LinkedHashSet<>();
29 private Set<Variable> positiveVariables;
30 private Map<Variable, Set<SortableLiteral>> variableToLiteralInputMap;
31 private PriorityQueue<SortableLiteral> literalsWithAllInputsBound;
32 private LinkedHashSet<Literal> topologicallySortedLiterals;
33
34 public ClausePostProcessor(Map<Variable, ParameterInfo> parameters, List<Literal> literals) {
35 this.parameters = parameters;
36 this.literals = literals;
37 }
38
39 public Result postProcessClause() {
40 mergeEquivalentNodeVariables();
41 substitutedLiterals = new ArrayList<>(literals.size());
42 keepParameterEquivalences();
43 substituteLiterals();
44 computeExistentiallyQuantifiedVariables();
45 computePositiveVariables();
46 validatePositiveRepresentatives();
47 validatePrivateVariables();
48 topologicallySortLiterals();
49 var filteredLiterals = new ArrayList<Literal>(topologicallySortedLiterals.size());
50 for (var literal : topologicallySortedLiterals) {
51 var reducedLiteral = literal.reduce();
52 if (BooleanLiteral.FALSE.equals(reducedLiteral)) {
53 return ConstantResult.ALWAYS_FALSE;
54 } else if (!BooleanLiteral.TRUE.equals(reducedLiteral)) {
55 filteredLiterals.add(reducedLiteral);
56 }
57 }
58 if (filteredLiterals.isEmpty()) {
59 return ConstantResult.ALWAYS_TRUE;
60 }
61 var clause = new DnfClause(Collections.unmodifiableSet(positiveVariables),
62 Collections.unmodifiableList(filteredLiterals));
63 return new ClauseResult(clause);
64 }
65
66 private void mergeEquivalentNodeVariables() {
67 for (var literal : literals) {
68 if (isPositiveEquivalence(literal)) {
69 var equivalenceLiteral = (EquivalenceLiteral) literal;
70 mergeVariables(equivalenceLiteral.left(), equivalenceLiteral.right());
71 }
72 }
73 }
74
75 private static boolean isPositiveEquivalence(Literal literal) {
76 return literal instanceof EquivalenceLiteral equivalenceLiteral && equivalenceLiteral.positive();
77 }
78
79 private void mergeVariables(NodeVariable left, NodeVariable right) {
80 var leftRepresentative = getRepresentative(left);
81 var rightRepresentative = getRepresentative(right);
82 var leftInfo = parameters.get(leftRepresentative);
83 var rightInfo = parameters.get(rightRepresentative);
84 if (leftInfo != null && (rightInfo == null || leftInfo.index() <= rightInfo.index())) {
85 // Prefer the variable occurring earlier in the parameter list as a representative.
86 doMergeVariables(leftRepresentative, rightRepresentative);
87 } else {
88 doMergeVariables(rightRepresentative, leftRepresentative);
89 }
90 }
91
92 private void doMergeVariables(NodeVariable parentRepresentative, NodeVariable newChildRepresentative) {
93 var parentSet = getEquivalentVariables(parentRepresentative);
94 var childSet = getEquivalentVariables(newChildRepresentative);
95 parentSet.addAll(childSet);
96 equivalencePartition.remove(newChildRepresentative);
97 for (var childEquivalentNodeVariable : childSet) {
98 representatives.put(childEquivalentNodeVariable, parentRepresentative);
99 }
100 }
101
102 private NodeVariable getRepresentative(NodeVariable variable) {
103 return representatives.computeIfAbsent(variable, Function.identity());
104 }
105
106 private Set<NodeVariable> getEquivalentVariables(NodeVariable variable) {
107 var representative = getRepresentative(variable);
108 if (!representative.equals(variable)) {
109 throw new AssertionError("NodeVariable %s already has a representative %s"
110 .formatted(variable, representative));
111 }
112 return equivalencePartition.computeIfAbsent(variable, key -> {
113 var set = new HashSet<NodeVariable>(1);
114 set.add(key);
115 return set;
116 });
117 }
118
119 private void keepParameterEquivalences() {
120 for (var pair : representatives.entrySet()) {
121 var left = pair.getKey();
122 var right = pair.getValue();
123 if (!left.equals(right) && parameters.containsKey(left) && parameters.containsKey(right)) {
124 substitutedLiterals.add(left.isEquivalent(right));
125 }
126 }
127 }
128
129 private void substituteLiterals() {
130 Substitution substitution;
131 if (representatives.isEmpty()) {
132 substitution = null;
133 } else {
134 substitution = new MapBasedSubstitution(Collections.unmodifiableMap(representatives),
135 StatelessSubstitution.IDENTITY);
136 }
137 for (var literal : literals) {
138 if (isPositiveEquivalence(literal)) {
139 // We already retained all equivalences that cannot be replaced with substitutions in
140 // {@link#keepParameterEquivalences()}.
141 continue;
142 }
143 var substitutedLiteral = substitution == null ? literal : literal.substitute(substitution);
144 substitutedLiterals.add(substitutedLiteral);
145 }
146 }
147
148 private void computeExistentiallyQuantifiedVariables() {
149 for (var literal : substitutedLiterals) {
150 for (var variable : literal.getOutputVariables()) {
151 boolean added = existentiallyQuantifiedVariables.add(variable);
152 if (!variable.isUnifiable()) {
153 var parameterInfo = parameters.get(variable);
154 if (parameterInfo != null && parameterInfo.direction() == ParameterDirection.IN) {
155 throw new IllegalArgumentException("Trying to bind %s parameter %s"
156 .formatted(ParameterDirection.IN, variable));
157 }
158 if (!added) {
159 throw new IllegalArgumentException("Variable %s has multiple assigned values"
160 .formatted(variable));
161 }
162 }
163 }
164 }
165 }
166
167 private void computePositiveVariables() {
168 positiveVariables = new LinkedHashSet<>();
169 for (var pair : parameters.entrySet()) {
170 var variable = pair.getKey();
171 if (pair.getValue().direction() == ParameterDirection.IN) {
172 // Inputs count as positive, because they are already bound when we evaluate literals.
173 positiveVariables.add(variable);
174 } else if (!existentiallyQuantifiedVariables.contains(variable)) {
175 throw new IllegalArgumentException("Unbound %s parameter %s"
176 .formatted(ParameterDirection.OUT, variable));
177 }
178 }
179 positiveVariables.addAll(existentiallyQuantifiedVariables);
180 }
181
182 private void validatePositiveRepresentatives() {
183 for (var pair : equivalencePartition.entrySet()) {
184 var representative = pair.getKey();
185 if (!positiveVariables.contains(representative)) {
186 var variableSet = pair.getValue();
187 throw new IllegalArgumentException("Variables %s were merged by equivalence but are not bound"
188 .formatted(variableSet));
189 }
190 }
191 }
192
193 private void validatePrivateVariables() {
194 var negativeVariablesMap = new HashMap<Variable, Literal>();
195 for (var literal : substitutedLiterals) {
196 for (var variable : literal.getPrivateVariables(positiveVariables)) {
197 var oldLiteral = negativeVariablesMap.put(variable, literal);
198 if (oldLiteral != null) {
199 throw new IllegalArgumentException("Unbound variable %s appears in multiple literals %s and %s"
200 .formatted(variable, oldLiteral, literal));
201 }
202 }
203 }
204 }
205
206 private void topologicallySortLiterals() {
207 topologicallySortedLiterals = new LinkedHashSet<>(substitutedLiterals.size());
208 variableToLiteralInputMap = new HashMap<>();
209 literalsWithAllInputsBound = new PriorityQueue<>();
210 int size = substitutedLiterals.size();
211 for (int i = 0; i < size; i++) {
212 var literal = substitutedLiterals.get(i);
213 var sortableLiteral = new SortableLiteral(i, literal);
214 sortableLiteral.enqueue();
215 }
216 while (!literalsWithAllInputsBound.isEmpty()) {
217 var variable = literalsWithAllInputsBound.remove();
218 variable.addToSortedLiterals();
219 }
220 if (!variableToLiteralInputMap.isEmpty()) {
221 throw new IllegalArgumentException("Unbound input variables %s"
222 .formatted(variableToLiteralInputMap.keySet()));
223 }
224 }
225
226 private class SortableLiteral implements Comparable<SortableLiteral> {
227 private final int index;
228 private final Literal literal;
229 private final Set<Variable> remainingInputs;
230
231 private SortableLiteral(int index, Literal literal) {
232 this.index = index;
233 this.literal = literal;
234 remainingInputs = new HashSet<>(literal.getInputVariables(positiveVariables));
235 for (var pair : parameters.entrySet()) {
236 if (pair.getValue().direction() == ParameterDirection.IN) {
237 remainingInputs.remove(pair.getKey());
238 }
239 }
240 }
241
242 public void enqueue() {
243 if (allInputsBound()) {
244 addToAllInputsBoundQueue();
245 } else {
246 addToVariableToLiteralInputMap();
247 }
248 }
249
250 private void bindVariable(Variable input) {
251 if (!remainingInputs.remove(input)) {
252 throw new AssertionError("Already processed input %s of literal %s".formatted(input, literal));
253 }
254 if (allInputsBound()) {
255 addToAllInputsBoundQueue();
256 }
257 }
258
259 private boolean allInputsBound() {
260 return remainingInputs.isEmpty();
261 }
262
263 private void addToVariableToLiteralInputMap() {
264 for (var inputVariable : remainingInputs) {
265 var literalSetForInput = variableToLiteralInputMap.computeIfAbsent(
266 inputVariable, key -> new HashSet<>());
267 literalSetForInput.add(this);
268 }
269 }
270
271 private void addToAllInputsBoundQueue() {
272 literalsWithAllInputsBound.add(this);
273 }
274
275 public void addToSortedLiterals() {
276 if (!allInputsBound()) {
277 throw new AssertionError("Inputs %s of %s are not yet bound".formatted(remainingInputs, literal));
278 }
279 // Add literal if we haven't yet added a duplicate of this literal.
280 topologicallySortedLiterals.add(literal);
281 for (var variable : literal.getOutputVariables()) {
282 var literalSetForInput = variableToLiteralInputMap.remove(variable);
283 if (literalSetForInput == null) {
284 continue;
285 }
286 for (var targetSortableLiteral : literalSetForInput) {
287 targetSortableLiteral.bindVariable(variable);
288 }
289 }
290 }
291
292 @Override
293 public int compareTo(@NotNull ClausePostProcessor.SortableLiteral other) {
294 return Integer.compare(index, other.index);
295 }
296
297 @Override
298 public boolean equals(Object o) {
299 if (this == o) return true;
300 if (o == null || getClass() != o.getClass()) return false;
301 SortableLiteral that = (SortableLiteral) o;
302 return index == that.index && Objects.equals(literal, that.literal);
303 }
304
305 @Override
306 public int hashCode() {
307 return Objects.hash(index, literal);
308 }
309 }
310
311 public sealed interface Result permits ClauseResult, ConstantResult {
312 }
313
314 public record ClauseResult(DnfClause clause) implements Result {
315 }
316
317 public enum ConstantResult implements Result {
318 ALWAYS_TRUE,
319 ALWAYS_FALSE
320 }
321
322 public record ParameterInfo(ParameterDirection direction, int index) {
323 }
324}
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..50b245f7
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java
@@ -0,0 +1,213 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.literal.Reduction;
10import tools.refinery.store.query.equality.DnfEqualityChecker;
11import tools.refinery.store.query.equality.LiteralEqualityHelper;
12import tools.refinery.store.query.term.Parameter;
13import tools.refinery.store.query.term.Variable;
14
15import java.util.Collection;
16import java.util.Collections;
17import java.util.List;
18import java.util.Set;
19import java.util.function.Consumer;
20import java.util.stream.Collectors;
21
22public final class Dnf implements Constraint {
23 private static final String INDENTATION = " ";
24
25 private final String name;
26 private final String uniqueName;
27 private final List<SymbolicParameter> symbolicParameters;
28 private final List<FunctionalDependency<Variable>> functionalDependencies;
29 private final List<DnfClause> clauses;
30
31 Dnf(String name, List<SymbolicParameter> symbolicParameters,
32 List<FunctionalDependency<Variable>> functionalDependencies, List<DnfClause> clauses) {
33 validateFunctionalDependencies(symbolicParameters, functionalDependencies);
34 this.name = name;
35 this.uniqueName = DnfUtils.generateUniqueName(name);
36 this.symbolicParameters = symbolicParameters;
37 this.functionalDependencies = functionalDependencies;
38 this.clauses = clauses;
39 }
40
41 private static void validateFunctionalDependencies(
42 Collection<SymbolicParameter> symbolicParameters,
43 Collection<FunctionalDependency<Variable>> functionalDependencies) {
44 var parameterSet = symbolicParameters.stream().map(SymbolicParameter::getVariable).collect(Collectors.toSet());
45 for (var functionalDependency : functionalDependencies) {
46 validateParameters(symbolicParameters, parameterSet, functionalDependency.forEach(), functionalDependency);
47 validateParameters(symbolicParameters, parameterSet, functionalDependency.unique(), functionalDependency);
48 }
49 }
50
51 private static void validateParameters(Collection<SymbolicParameter> symbolicParameters,
52 Set<Variable> parameterSet, Collection<Variable> toValidate,
53 FunctionalDependency<Variable> functionalDependency) {
54 for (var variable : toValidate) {
55 if (!parameterSet.contains(variable)) {
56 throw new IllegalArgumentException(
57 "Variable %s of functional dependency %s does not appear in the parameter list %s"
58 .formatted(variable, functionalDependency, symbolicParameters));
59 }
60 }
61 }
62
63 @Override
64 public String name() {
65 return name == null ? uniqueName : name;
66 }
67
68 public boolean isExplicitlyNamed() {
69 return name == null;
70 }
71
72 public String getUniqueName() {
73 return uniqueName;
74 }
75
76 public List<SymbolicParameter> getSymbolicParameters() {
77 return symbolicParameters;
78 }
79
80 public List<Parameter> getParameters() {
81 return Collections.unmodifiableList(symbolicParameters);
82 }
83
84 public List<FunctionalDependency<Variable>> getFunctionalDependencies() {
85 return functionalDependencies;
86 }
87
88 @Override
89 public int arity() {
90 return symbolicParameters.size();
91 }
92
93 public List<DnfClause> getClauses() {
94 return clauses;
95 }
96
97 public RelationalQuery asRelation() {
98 return new RelationalQuery(this);
99 }
100
101 public <T> FunctionalQuery<T> asFunction(Class<T> type) {
102 return new FunctionalQuery<>(this, type);
103 }
104
105 @Override
106 public Reduction getReduction() {
107 if (clauses.isEmpty()) {
108 return Reduction.ALWAYS_FALSE;
109 }
110 for (var clause : clauses) {
111 if (clause.literals().isEmpty()) {
112 return Reduction.ALWAYS_TRUE;
113 }
114 }
115 return Reduction.NOT_REDUCIBLE;
116 }
117
118 public boolean equalsWithSubstitution(DnfEqualityChecker callEqualityChecker, Dnf other) {
119 if (arity() != other.arity()) {
120 return false;
121 }
122 for (int i = 0; i < arity(); i++) {
123 if (!symbolicParameters.get(i).getDirection().equals(other.getSymbolicParameters().get(i).getDirection())) {
124 return false;
125 }
126 }
127 int numClauses = clauses.size();
128 if (numClauses != other.clauses.size()) {
129 return false;
130 }
131 for (int i = 0; i < numClauses; i++) {
132 var literalEqualityHelper = new LiteralEqualityHelper(callEqualityChecker, symbolicParameters,
133 other.symbolicParameters);
134 if (!clauses.get(i).equalsWithSubstitution(literalEqualityHelper, other.clauses.get(i))) {
135 return false;
136 }
137 }
138 return true;
139 }
140
141 @Override
142 public boolean equals(LiteralEqualityHelper helper, Constraint other) {
143 if (other instanceof Dnf otherDnf) {
144 return helper.dnfEqual(this, otherDnf);
145 }
146 return false;
147 }
148
149 @Override
150 public String toString() {
151 return "%s/%d".formatted(name(), arity());
152 }
153
154 @Override
155 public String toReferenceString() {
156 return "@Dnf " + name();
157 }
158
159 public String toDefinitionString() {
160 var builder = new StringBuilder();
161 builder.append("pred ").append(name()).append("(");
162 var parameterIterator = symbolicParameters.iterator();
163 if (parameterIterator.hasNext()) {
164 builder.append(parameterIterator.next());
165 while (parameterIterator.hasNext()) {
166 builder.append(", ").append(parameterIterator.next());
167 }
168 }
169 builder.append(") <->");
170 var clauseIterator = clauses.iterator();
171 if (clauseIterator.hasNext()) {
172 appendClause(clauseIterator.next(), builder);
173 while (clauseIterator.hasNext()) {
174 builder.append("\n;");
175 appendClause(clauseIterator.next(), builder);
176 }
177 } else {
178 builder.append("\n").append(INDENTATION).append("<no clauses>");
179 }
180 builder.append(".\n");
181 return builder.toString();
182 }
183
184 private static void appendClause(DnfClause clause, StringBuilder builder) {
185 var iterator = clause.literals().iterator();
186 if (!iterator.hasNext()) {
187 builder.append("\n").append(INDENTATION).append("<empty>");
188 return;
189 }
190 builder.append("\n").append(INDENTATION).append(iterator.next());
191 while (iterator.hasNext()) {
192 builder.append(",\n").append(INDENTATION).append(iterator.next());
193 }
194 }
195
196 public static DnfBuilder builder() {
197 return builder(null);
198 }
199
200 public static DnfBuilder builder(String name) {
201 return new DnfBuilder(name);
202 }
203
204 public static Dnf of(Consumer<DnfBuilder> callback) {
205 return of(null, callback);
206 }
207
208 public static Dnf of(String name, Consumer<DnfBuilder> callback) {
209 var builder = builder(name);
210 callback.accept(builder);
211 return builder.build();
212 }
213}
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..8e38ca6b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java
@@ -0,0 +1,262 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.dnf.callback.*;
9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.DataVariable;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.term.Variable;
14
15import java.util.*;
16
17@SuppressWarnings("UnusedReturnValue")
18public final class DnfBuilder {
19 private final String name;
20 private final Set<Variable> parameterVariables = new LinkedHashSet<>();
21 private final List<SymbolicParameter> parameters = new ArrayList<>();
22 private final List<FunctionalDependency<Variable>> functionalDependencies = new ArrayList<>();
23 private final List<List<Literal>> clauses = new ArrayList<>();
24
25 DnfBuilder(String name) {
26 this.name = name;
27 }
28
29 public NodeVariable parameter() {
30 return parameter((String) null);
31 }
32
33 public NodeVariable parameter(String name) {
34 return parameter(name, ParameterDirection.OUT);
35 }
36
37 public NodeVariable parameter(ParameterDirection direction) {
38 return parameter((String) null, direction);
39 }
40
41 public NodeVariable parameter(String name, ParameterDirection direction) {
42 var variable = Variable.of(name);
43 parameter(variable, direction);
44 return variable;
45 }
46
47 public <T> DataVariable<T> parameter(Class<T> type) {
48 return parameter(null, type);
49 }
50
51 public <T> DataVariable<T> parameter(String name, Class<T> type) {
52 return parameter(name, type, ParameterDirection.OUT);
53 }
54
55 public <T> DataVariable<T> parameter(Class<T> type, ParameterDirection direction) {
56 return parameter(null, type, direction);
57 }
58
59 public <T> DataVariable<T> parameter(String name, Class<T> type, ParameterDirection direction) {
60 var variable = Variable.of(name, type);
61 parameter(variable, direction);
62 return variable;
63 }
64
65 public DnfBuilder parameter(Variable variable) {
66 return parameter(variable, ParameterDirection.OUT);
67 }
68
69 public DnfBuilder parameter(Variable variable, ParameterDirection direction) {
70 return symbolicParameter(new SymbolicParameter(variable, direction));
71 }
72
73 public DnfBuilder parameters(Variable... variables) {
74 return parameters(List.of(variables));
75 }
76
77 public DnfBuilder parameters(Collection<? extends Variable> variables) {
78 return parameters(variables, ParameterDirection.OUT);
79 }
80
81 public DnfBuilder parameters(Collection<? extends Variable> variables, ParameterDirection direction) {
82 for (var variable : variables) {
83 parameter(variable, direction);
84 }
85 return this;
86 }
87
88 public DnfBuilder symbolicParameter(SymbolicParameter symbolicParameter) {
89 var variable = symbolicParameter.getVariable();
90 if (!parameterVariables.add(variable)) {
91 throw new IllegalArgumentException("Variable %s is already on the parameter list %s"
92 .formatted(variable, parameters));
93 }
94 parameters.add(symbolicParameter);
95 return this;
96 }
97
98 public DnfBuilder symbolicParameters(SymbolicParameter... symbolicParameters) {
99 return symbolicParameters(List.of(symbolicParameters));
100 }
101
102 public DnfBuilder symbolicParameters(Collection<SymbolicParameter> symbolicParameters) {
103 for (var symbolicParameter : symbolicParameters) {
104 symbolicParameter(symbolicParameter);
105 }
106 return this;
107 }
108
109 public DnfBuilder functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) {
110 this.functionalDependencies.addAll(functionalDependencies);
111 return this;
112 }
113
114 public DnfBuilder functionalDependency(FunctionalDependency<Variable> functionalDependency) {
115 functionalDependencies.add(functionalDependency);
116 return this;
117 }
118
119 public DnfBuilder functionalDependency(Set<? extends Variable> forEach, Set<? extends Variable> unique) {
120 return functionalDependency(new FunctionalDependency<>(Set.copyOf(forEach), Set.copyOf(unique)));
121 }
122
123 public DnfBuilder clause(ClauseCallback0 callback) {
124 return clause(callback.toLiterals());
125 }
126
127 public DnfBuilder clause(ClauseCallback1Data0 callback) {
128 return clause(callback.toLiterals(Variable.of("v1")));
129 }
130
131 public <T> DnfBuilder clause(Class<T> type1, ClauseCallback1Data1<T> callback) {
132 return clause(callback.toLiterals(Variable.of("v1", type1)));
133 }
134
135 public DnfBuilder clause(ClauseCallback2Data0 callback) {
136 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2")));
137 }
138
139 public <T> DnfBuilder clause(Class<T> type1, ClauseCallback2Data1<T> callback) {
140 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("d1", type1)));
141 }
142
143 public <T1, T2> DnfBuilder clause(Class<T1> type1, Class<T2> type2, ClauseCallback2Data2<T1, T2> callback) {
144 return clause(callback.toLiterals(Variable.of("d1", type1), Variable.of("d2", type2)));
145 }
146
147 public DnfBuilder clause(ClauseCallback3Data0 callback) {
148 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3")));
149 }
150
151 public <T> DnfBuilder clause(Class<T> type1, ClauseCallback3Data1<T> callback) {
152 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("d1", type1)));
153 }
154
155 public <T1, T2> DnfBuilder clause(Class<T1> type1, Class<T2> type2, ClauseCallback3Data2<T1, T2> callback) {
156 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("d1", type1), Variable.of("d2", type2)));
157 }
158
159 public <T1, T2, T3> DnfBuilder clause(Class<T1> type1, Class<T2> type2, Class<T3> type3,
160 ClauseCallback3Data3<T1, T2, T3> callback) {
161 return clause(callback.toLiterals(Variable.of("d1", type1), Variable.of("d2", type2),
162 Variable.of("d3", type3)));
163 }
164
165 public DnfBuilder clause(ClauseCallback4Data0 callback) {
166 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3"), Variable.of("v4")));
167 }
168
169 public <T> DnfBuilder clause(Class<T> type1, ClauseCallback4Data1<T> callback) {
170 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3"), Variable.of("d1",
171 type1)));
172 }
173
174 public <T1, T2> DnfBuilder clause(Class<T1> type1, Class<T2> type2, ClauseCallback4Data2<T1, T2> callback) {
175 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("d1", type1),
176 Variable.of("d2", type2)));
177 }
178
179 public <T1, T2, T3> DnfBuilder clause(Class<T1> type1, Class<T2> type2, Class<T3> type3,
180 ClauseCallback4Data3<T1, T2, T3> callback) {
181 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("d1", type1), Variable.of("d2", type2),
182 Variable.of("d3", type3)));
183 }
184
185 public <T1, T2, T3, T4> DnfBuilder clause(Class<T1> type1, Class<T2> type2, Class<T3> type3, Class<T4> type4,
186 ClauseCallback4Data4<T1, T2, T3, T4> callback) {
187 return clause(callback.toLiterals(Variable.of("d1", type1), Variable.of("d2", type2),
188 Variable.of("d3", type3), Variable.of("d4", type4)));
189 }
190
191 public DnfBuilder clause(Literal... literals) {
192 clause(List.of(literals));
193 return this;
194 }
195
196 public DnfBuilder clause(Collection<? extends Literal> literals) {
197 clauses.add(List.copyOf(literals));
198 return this;
199 }
200
201 <T> void output(DataVariable<T> outputVariable) {
202 // Copy parameter variables to exclude the newly added {@code outputVariable}.
203 var fromParameters = Set.copyOf(parameterVariables);
204 parameter(outputVariable, ParameterDirection.OUT);
205 functionalDependency(fromParameters, Set.of(outputVariable));
206 }
207
208 public Dnf build() {
209 var postProcessedClauses = postProcessClauses();
210 return new Dnf(name, Collections.unmodifiableList(parameters),
211 Collections.unmodifiableList(functionalDependencies),
212 Collections.unmodifiableList(postProcessedClauses));
213 }
214
215 private List<DnfClause> postProcessClauses() {
216 var parameterInfoMap = getParameterInfoMap();
217 var postProcessedClauses = new ArrayList<DnfClause>(clauses.size());
218 for (var literals : clauses) {
219 var postProcessor = new ClausePostProcessor(parameterInfoMap, literals);
220 var result = postProcessor.postProcessClause();
221 if (result instanceof ClausePostProcessor.ClauseResult clauseResult) {
222 postProcessedClauses.add(clauseResult.clause());
223 } else if (result instanceof ClausePostProcessor.ConstantResult constantResult) {
224 switch (constantResult) {
225 case ALWAYS_TRUE -> {
226 var inputVariables = getInputVariables();
227 return List.of(new DnfClause(inputVariables, List.of()));
228 }
229 case ALWAYS_FALSE -> {
230 // Skip this clause because it can never match.
231 }
232 default -> throw new IllegalStateException("Unexpected ClausePostProcessor.ConstantResult: " +
233 constantResult);
234 }
235 } else {
236 throw new IllegalStateException("Unexpected ClausePostProcessor.Result: " + result);
237 }
238 }
239 return postProcessedClauses;
240 }
241
242 private Map<Variable, ClausePostProcessor.ParameterInfo> getParameterInfoMap() {
243 var mutableParameterInfoMap = new LinkedHashMap<Variable, ClausePostProcessor.ParameterInfo>();
244 int arity = parameters.size();
245 for (int i = 0; i < arity; i++) {
246 var parameter = parameters.get(i);
247 mutableParameterInfoMap.put(parameter.getVariable(),
248 new ClausePostProcessor.ParameterInfo(parameter.getDirection(), i));
249 }
250 return Collections.unmodifiableMap(mutableParameterInfoMap);
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 }
262}
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..fdd0d47c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java
@@ -0,0 +1,28 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.List;
13import java.util.Set;
14
15public record DnfClause(Set<Variable> positiveVariables, List<Literal> literals) {
16 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, DnfClause other) {
17 int size = literals.size();
18 if (size != other.literals.size()) {
19 return false;
20 }
21 for (int i = 0; i < size; i++) {
22 if (!literals.get(i).equalsWithSubstitution(helper, other.literals.get(i))) {
23 return false;
24 }
25 }
26 return true;
27 }
28}
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..65ab3634
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java
@@ -0,0 +1,24 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import java.util.UUID;
9
10public final class DnfUtils {
11 private DnfUtils() {
12 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
13 }
14
15 public static String generateUniqueName(String originalName) {
16 UUID uuid = UUID.randomUUID();
17 String uniqueString = "_" + uuid.toString().replace('-', '_');
18 if (originalName == null) {
19 return uniqueString;
20 } else {
21 return originalName + uniqueString;
22 }
23 }
24}
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..b00b2cb7
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java
@@ -0,0 +1,20 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import java.util.HashSet;
9import java.util.Set;
10
11public record FunctionalDependency<T>(Set<T> forEach, Set<T> unique) {
12 public FunctionalDependency {
13 var uniqueForEach = new HashSet<>(unique);
14 uniqueForEach.retainAll(forEach);
15 if (!uniqueForEach.isEmpty()) {
16 throw new IllegalArgumentException("Variables %s appear on both sides of the functional dependency"
17 .formatted(uniqueForEach));
18 }
19 }
20}
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..5a32b1ba
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java
@@ -0,0 +1,99 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.literal.CallPolarity;
9import tools.refinery.store.query.term.Aggregator;
10import tools.refinery.store.query.term.AssignedValue;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.ArrayList;
15import java.util.List;
16import java.util.Objects;
17
18public final class FunctionalQuery<T> extends Query<T> {
19 private final Class<T> type;
20
21 FunctionalQuery(Dnf dnf, Class<T> type) {
22 super(dnf);
23 var parameters = dnf.getSymbolicParameters();
24 int outputIndex = dnf.arity() - 1;
25 for (int i = 0; i < outputIndex; i++) {
26 var parameter = parameters.get(i);
27 var parameterType = parameter.tryGetType();
28 if (parameterType.isPresent()) {
29 throw new IllegalArgumentException("Expected parameter %s of %s to be a node variable, got %s instead"
30 .formatted(parameter, dnf, parameterType.get().getName()));
31 }
32 }
33 var outputParameter = parameters.get(outputIndex);
34 var outputParameterType = outputParameter.tryGetType();
35 if (outputParameterType.isEmpty() || !outputParameterType.get().equals(type)) {
36 throw new IllegalArgumentException("Expected parameter %s of %s to be %s, but got %s instead".formatted(
37 outputParameter, dnf, type, outputParameterType.map(Class::getName).orElse("node")));
38 }
39 this.type = type;
40 }
41
42 @Override
43 public int arity() {
44 return getDnf().arity() - 1;
45 }
46
47 @Override
48 public Class<T> valueType() {
49 return type;
50 }
51
52 @Override
53 public T defaultValue() {
54 return null;
55 }
56
57 public AssignedValue<T> call(List<NodeVariable> arguments) {
58 return targetVariable -> {
59 var argumentsWithTarget = new ArrayList<Variable>(arguments.size() + 1);
60 argumentsWithTarget.addAll(arguments);
61 argumentsWithTarget.add(targetVariable);
62 return getDnf().call(CallPolarity.POSITIVE, argumentsWithTarget);
63 };
64 }
65
66 public AssignedValue<T> call(NodeVariable... arguments) {
67 return call(List.of(arguments));
68 }
69
70 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, List<NodeVariable> arguments) {
71 return targetVariable -> {
72 var placeholderVariable = Variable.of(type);
73 var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1);
74 argumentsWithPlaceholder.addAll(arguments);
75 argumentsWithPlaceholder.add(placeholderVariable);
76 return getDnf()
77 .aggregateBy(placeholderVariable, aggregator, argumentsWithPlaceholder)
78 .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 if (!super.equals(o)) return false;
91 FunctionalQuery<?> that = (FunctionalQuery<?>) o;
92 return Objects.equals(type, that.type);
93 }
94
95 @Override
96 public int hashCode() {
97 return Objects.hash(super.hashCode(), type);
98 }
99}
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..d1cd7ba8
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.term.DataVariable;
9
10public final class FunctionalQueryBuilder<T> extends AbstractQueryBuilder<FunctionalQueryBuilder<T>> {
11 private final DataVariable<T> outputVariable;
12 private final Class<T> type;
13
14 FunctionalQueryBuilder(DataVariable<T> outputVariable, DnfBuilder dnfBuilder, Class<T> type) {
15 super(dnfBuilder);
16 this.outputVariable = outputVariable;
17 this.type = type;
18 }
19
20 @Override
21 protected FunctionalQueryBuilder<T> self() {
22 return this;
23 }
24
25 public FunctionalQuery<T> build() {
26 dnfBuilder.output(outputVariable);
27 return dnfBuilder.build().asFunction(type);
28 }
29}
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..aaa52ce6
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java
@@ -0,0 +1,179 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.dnf.callback.*;
9import tools.refinery.store.query.term.ParameterDirection;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Objects;
13
14public abstract sealed class Query<T> implements AnyQuery permits FunctionalQuery, RelationalQuery {
15 private static final String OUTPUT_VARIABLE_NAME = "output";
16
17 private final Dnf dnf;
18
19 protected Query(Dnf dnf) {
20 for (var parameter : dnf.getSymbolicParameters()) {
21 if (parameter.getDirection() != ParameterDirection.OUT) {
22 throw new IllegalArgumentException("Query parameter %s with direction %s is not allowed"
23 .formatted(parameter.getVariable(), parameter.getDirection()));
24 }
25 }
26 this.dnf = dnf;
27 }
28
29 @Override
30 public String name() {
31 return dnf.name();
32 }
33
34 @Override
35 public Dnf getDnf() {
36 return dnf;
37 }
38
39 // Allow redeclaration of the method with refined return type.
40 @SuppressWarnings("squid:S3038")
41 @Override
42 public abstract Class<T> valueType();
43
44 public abstract T defaultValue();
45
46 @Override
47 public boolean equals(Object o) {
48 if (this == o) return true;
49 if (o == null || getClass() != o.getClass()) return false;
50 Query<?> that = (Query<?>) o;
51 return Objects.equals(dnf, that.dnf);
52 }
53
54 @Override
55 public int hashCode() {
56 return Objects.hash(dnf);
57 }
58
59 @Override
60 public String toString() {
61 return dnf.toString();
62 }
63
64 public static QueryBuilder builder() {
65 return builder(null);
66 }
67
68 public static QueryBuilder builder(String name) {
69 return new QueryBuilder(name);
70 }
71
72 public static RelationalQuery of(QueryCallback0 callback) {
73 return of(null, callback);
74 }
75
76 public static RelationalQuery of(String name, QueryCallback0 callback) {
77 var builder = builder(name);
78 callback.accept(builder);
79 return builder.build();
80 }
81
82 public static RelationalQuery of(QueryCallback1 callback) {
83 return of(null, callback);
84 }
85
86 public static RelationalQuery of(String name, QueryCallback1 callback) {
87 var builder = builder(name);
88 callback.accept(builder, builder.parameter("p1"));
89 return builder.build();
90 }
91
92 public static RelationalQuery of(QueryCallback2 callback) {
93 return of(null, callback);
94 }
95
96 public static RelationalQuery of(String name, QueryCallback2 callback) {
97 var builder = builder(name);
98 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"));
99 return builder.build();
100 }
101
102 public static RelationalQuery of(QueryCallback3 callback) {
103 return of(null, callback);
104 }
105
106 public static RelationalQuery of(String name, QueryCallback3 callback) {
107 var builder = builder(name);
108 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"));
109 return builder.build();
110 }
111
112 public static RelationalQuery of(QueryCallback4 callback) {
113 return of(null, callback);
114 }
115
116 public static RelationalQuery of(String name, QueryCallback4 callback) {
117 var builder = builder(name);
118 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"),
119 builder.parameter("p4"));
120 return builder.build();
121 }
122
123 public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback0<T> callback) {
124 return of(null, type, callback);
125 }
126
127 public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback0<T> callback) {
128 var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type);
129 var builder = builder(name).output(outputVariable);
130 callback.accept(builder, outputVariable);
131 return builder.build();
132 }
133
134 public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback1<T> callback) {
135 return of(null, type, callback);
136 }
137
138 public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback1<T> callback) {
139 var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type);
140 var builder = builder(name).output(outputVariable);
141 callback.accept(builder, builder.parameter("p1"), outputVariable);
142 return builder.build();
143 }
144
145 public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback2<T> callback) {
146 return of(null, type, callback);
147 }
148
149 public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback2<T> callback) {
150 var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type);
151 var builder = builder(name).output(outputVariable);
152 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), outputVariable);
153 return builder.build();
154 }
155
156 public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback3<T> callback) {
157 return of(null, type, callback);
158 }
159
160 public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback3<T> callback) {
161 var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type);
162 var builder = builder(name).output(outputVariable);
163 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"),
164 outputVariable);
165 return builder.build();
166 }
167
168 public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback4<T> callback) {
169 return of(null, type, callback);
170 }
171
172 public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback4<T> callback) {
173 var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type);
174 var builder = builder(name).output(outputVariable);
175 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"),
176 builder.parameter("p4"), outputVariable);
177 return builder.build();
178 }
179}
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..138911bc
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java
@@ -0,0 +1,27 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.term.DataVariable;
9
10public final class QueryBuilder extends AbstractQueryBuilder<QueryBuilder> {
11 QueryBuilder(String name) {
12 super(Dnf.builder(name));
13 }
14
15 @Override
16 protected QueryBuilder self() {
17 return this;
18 }
19
20 public <T> FunctionalQueryBuilder<T> output(DataVariable<T> outputVariable) {
21 return new FunctionalQueryBuilder<>(outputVariable, dnfBuilder, outputVariable.getType());
22 }
23
24 public RelationalQuery build() {
25 return dnfBuilder.build().asRelation();
26 }
27}
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..d34a7ace
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java
@@ -0,0 +1,66 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.literal.CallLiteral;
9import tools.refinery.store.query.literal.CallPolarity;
10import tools.refinery.store.query.term.AssignedValue;
11import tools.refinery.store.query.term.NodeVariable;
12
13import java.util.Collections;
14import java.util.List;
15
16public final class RelationalQuery extends Query<Boolean> {
17 RelationalQuery(Dnf dnf) {
18 super(dnf);
19 for (var parameter : dnf.getSymbolicParameters()) {
20 var parameterType = parameter.tryGetType();
21 if (parameterType.isPresent()) {
22 throw new IllegalArgumentException("Expected parameter %s of %s to be a node variable, got %s instead"
23 .formatted(parameter, dnf, parameterType.get().getName()));
24 }
25 }
26 }
27
28 @Override
29 public int arity() {
30 return getDnf().arity();
31 }
32
33 @Override
34 public Class<Boolean> valueType() {
35 return Boolean.class;
36 }
37
38 @Override
39 public Boolean defaultValue() {
40 return false;
41 }
42
43 public CallLiteral call(CallPolarity polarity, List<NodeVariable> arguments) {
44 return getDnf().call(polarity, Collections.unmodifiableList(arguments));
45 }
46
47 public CallLiteral call(CallPolarity polarity, NodeVariable... arguments) {
48 return getDnf().call(polarity, arguments);
49 }
50
51 public CallLiteral call(NodeVariable... arguments) {
52 return getDnf().call(arguments);
53 }
54
55 public CallLiteral callTransitive(NodeVariable left, NodeVariable right) {
56 return getDnf().callTransitive(left, right);
57 }
58
59 public AssignedValue<Integer> count(List<NodeVariable> arguments) {
60 return getDnf().count(Collections.unmodifiableList(arguments));
61 }
62
63 public AssignedValue<Integer> count(NodeVariable... arguments) {
64 return getDnf().count(arguments);
65 }
66}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java
new file mode 100644
index 00000000..e0d3ba1f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java
@@ -0,0 +1,52 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.term.Parameter;
9import tools.refinery.store.query.term.ParameterDirection;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Objects;
13
14public final class SymbolicParameter extends Parameter {
15 private final Variable variable;
16
17 public SymbolicParameter(Variable variable, ParameterDirection direction) {
18 super(variable.tryGetType().orElse(null), direction);
19 this.variable = variable;
20 }
21
22 public Variable getVariable() {
23 return variable;
24 }
25
26 public boolean isUnifiable() {
27 return variable.isUnifiable();
28 }
29
30 @Override
31 public String toString() {
32 var direction = getDirection();
33 if (direction == ParameterDirection.OUT) {
34 return variable.toString();
35 }
36 return "%s %s".formatted(getDirection(), variable);
37 }
38
39 @Override
40 public boolean equals(Object o) {
41 if (this == o) return true;
42 if (o == null || getClass() != o.getClass()) return false;
43 if (!super.equals(o)) return false;
44 SymbolicParameter that = (SymbolicParameter) o;
45 return Objects.equals(variable, that.variable);
46 }
47
48 @Override
49 public int hashCode() {
50 return Objects.hash(super.hashCode(), variable);
51 }
52}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback0.java
new file mode 100644
index 00000000..d98dda2e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback0.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9
10import java.util.Collection;
11
12@FunctionalInterface
13public interface ClauseCallback0 {
14 Collection<Literal> toLiterals();
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data0.java
new file mode 100644
index 00000000..4c01a527
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data0.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback1Data0 {
15 Collection<Literal> toLiterals(NodeVariable v1);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data1.java
new file mode 100644
index 00000000..2c0cb6eb
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data1.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback1Data1<T> {
15 Collection<Literal> toLiterals(DataVariable<T> d1);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data0.java
new file mode 100644
index 00000000..d764bdba
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data0.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback2Data0 {
15 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data1.java
new file mode 100644
index 00000000..140af03a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data1.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback2Data1<T> {
16 Collection<Literal> toLiterals(NodeVariable v1, DataVariable<T> x1);
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data2.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data2.java
new file mode 100644
index 00000000..bfc8637c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data2.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback2Data2<T1, T2> {
15 Collection<Literal> toLiterals(DataVariable<T1> x1, DataVariable<T2> x2);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data0.java
new file mode 100644
index 00000000..074df65b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data0.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback3Data0 {
15 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data1.java
new file mode 100644
index 00000000..24ba5187
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data1.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback3Data1<T> {
16 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, DataVariable<T> d1);
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data2.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data2.java
new file mode 100644
index 00000000..2a2e837a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data2.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback3Data2<T1, T2> {
16 Collection<Literal> toLiterals(NodeVariable v1, DataVariable<T1> d1, DataVariable<T2> d2);
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data3.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data3.java
new file mode 100644
index 00000000..8f4bdd01
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data3.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback3Data3<T1, T2, T3> {
15 Collection<Literal> toLiterals(DataVariable<T1> d1, DataVariable<T2> d2, DataVariable<T3> d3);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data0.java
new file mode 100644
index 00000000..ed0f87b2
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data0.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback4Data0 {
15 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3, NodeVariable v4);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data1.java
new file mode 100644
index 00000000..9b27e2e1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data1.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback4Data1<T> {
16 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3, DataVariable<T> d1);
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data2.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data2.java
new file mode 100644
index 00000000..cbc4808e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data2.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback4Data2<T1, T2> {
16 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, DataVariable<T1> d1, DataVariable<T2> d2);
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data3.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data3.java
new file mode 100644
index 00000000..a6258f36
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data3.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback4Data3<T1, T2, T3> {
16 Collection<Literal> toLiterals(NodeVariable v1, DataVariable<T1> d1, DataVariable<T2> d2, DataVariable<T3> d3);
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data4.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data4.java
new file mode 100644
index 00000000..b52a911a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data4.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback4Data4<T1, T2, T3, T4> {
15 Collection<Literal> toLiterals(DataVariable<T1> d1, DataVariable<T2> d2, DataVariable<T3> d3, DataVariable<T4> d4);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback0.java
new file mode 100644
index 00000000..63b3eee6
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback0.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.FunctionalQueryBuilder;
9import tools.refinery.store.query.term.DataVariable;
10
11@FunctionalInterface
12public interface FunctionalQueryCallback0<T> {
13 void accept(FunctionalQueryBuilder<T> builder, DataVariable<T> output);
14}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback1.java
new file mode 100644
index 00000000..1295a118
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback1.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.FunctionalQueryBuilder;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12@FunctionalInterface
13public interface FunctionalQueryCallback1<T> {
14 void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, DataVariable<T> output);
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback2.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback2.java
new file mode 100644
index 00000000..d5b7f9ff
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback2.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.FunctionalQueryBuilder;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12@FunctionalInterface
13public interface FunctionalQueryCallback2<T> {
14 void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, NodeVariable p2, DataVariable<T> output);
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback3.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback3.java
new file mode 100644
index 00000000..dc8404a0
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback3.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.FunctionalQueryBuilder;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12@FunctionalInterface
13public interface FunctionalQueryCallback3<T> {
14 void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, NodeVariable p2, NodeVariable p3,
15 DataVariable<T> output);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback4.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback4.java
new file mode 100644
index 00000000..b6d3ddb0
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback4.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.FunctionalQueryBuilder;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12@FunctionalInterface
13public interface FunctionalQueryCallback4<T> {
14 void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, NodeVariable p2, NodeVariable p3, NodeVariable p4,
15 DataVariable<T> output);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback0.java
new file mode 100644
index 00000000..3cf1de48
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback0.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.QueryBuilder;
9
10@FunctionalInterface
11public interface QueryCallback0 {
12 void accept(QueryBuilder builder);
13}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback1.java
new file mode 100644
index 00000000..0a150955
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback1.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.QueryBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public interface QueryCallback1 {
13 void accept(QueryBuilder builder, NodeVariable p1);
14}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback2.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback2.java
new file mode 100644
index 00000000..9493a7b4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback2.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.QueryBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public interface QueryCallback2 {
13 void accept(QueryBuilder builder, NodeVariable p1, NodeVariable p2);
14}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback3.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback3.java
new file mode 100644
index 00000000..358c7da7
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback3.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.QueryBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public interface QueryCallback3 {
13 void accept(QueryBuilder builder, NodeVariable p1, NodeVariable p2, NodeVariable p3);
14}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback4.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback4.java
new file mode 100644
index 00000000..890dda16
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback4.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.QueryBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public interface QueryCallback4 {
13 void accept(QueryBuilder builder, NodeVariable p1, NodeVariable p2, NodeVariable p3, NodeVariable p4);
14}
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..1eeb5723
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java
@@ -0,0 +1,78 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.equality;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.DnfClause;
10import tools.refinery.store.query.dnf.SymbolicParameter;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.util.CycleDetectingMapper;
13
14import java.util.List;
15
16public class DeepDnfEqualityChecker implements DnfEqualityChecker {
17 private final CycleDetectingMapper<Pair, Boolean> mapper = new CycleDetectingMapper<>(Pair::toString,
18 this::doCheckEqual);
19
20 @Override
21 public boolean dnfEqual(Dnf left, Dnf right) {
22 return mapper.map(new Pair(left, right));
23 }
24
25 public boolean dnfEqualRaw(List<SymbolicParameter> symbolicParameters,
26 List<? extends List<? extends Literal>> clauses, Dnf other) {
27 int arity = symbolicParameters.size();
28 if (arity != other.arity()) {
29 return false;
30 }
31 for (int i = 0; i < arity; i++) {
32 if (!symbolicParameters.get(i).getDirection().equals(other.getSymbolicParameters().get(i).getDirection())) {
33 return false;
34 }
35 }
36 int numClauses = clauses.size();
37 if (numClauses != other.getClauses().size()) {
38 return false;
39 }
40 for (int i = 0; i < numClauses; i++) {
41 var literalEqualityHelper = new LiteralEqualityHelper(this, symbolicParameters,
42 other.getSymbolicParameters());
43 if (!equalsWithSubstitutionRaw(literalEqualityHelper, clauses.get(i), other.getClauses().get(i))) {
44 return false;
45 }
46 }
47 return true;
48 }
49
50 private boolean equalsWithSubstitutionRaw(LiteralEqualityHelper helper, List<? extends Literal> literals,
51 DnfClause other) {
52 int size = literals.size();
53 if (size != other.literals().size()) {
54 return false;
55 }
56 for (int i = 0; i < size; i++) {
57 if (!literals.get(i).equalsWithSubstitution(helper, other.literals().get(i))) {
58 return false;
59 }
60 }
61 return true;
62 }
63
64 protected boolean doCheckEqual(Pair pair) {
65 return pair.left.equalsWithSubstitution(this, pair.right);
66 }
67
68 protected List<Pair> getInProgress() {
69 return mapper.getInProgress();
70 }
71
72 protected record Pair(Dnf left, Dnf right) {
73 @Override
74 public String toString() {
75 return "(%s, %s)".formatted(left.name(), right.name());
76 }
77 }
78}
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..4a8bee3b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.equality;
7
8import tools.refinery.store.query.dnf.Dnf;
9
10@FunctionalInterface
11public interface DnfEqualityChecker {
12 boolean dnfEqual(Dnf left, Dnf right);
13}
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..9315fb30
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java
@@ -0,0 +1,54 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.equality;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.SymbolicParameter;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.HashMap;
13import java.util.List;
14import java.util.Map;
15
16public class LiteralEqualityHelper {
17 private final DnfEqualityChecker dnfEqualityChecker;
18 private final Map<Variable, Variable> leftToRight;
19 private final Map<Variable, Variable> rightToLeft;
20
21 public LiteralEqualityHelper(DnfEqualityChecker dnfEqualityChecker, List<SymbolicParameter> leftParameters,
22 List<SymbolicParameter> rightParameters) {
23 this.dnfEqualityChecker = dnfEqualityChecker;
24 var arity = leftParameters.size();
25 if (arity != rightParameters.size()) {
26 throw new IllegalArgumentException("Parameter lists have unequal length");
27 }
28 leftToRight = new HashMap<>(arity);
29 rightToLeft = new HashMap<>(arity);
30 for (int i = 0; i < arity; i++) {
31 if (!variableEqual(leftParameters.get(i).getVariable(), rightParameters.get(i).getVariable())) {
32 throw new IllegalArgumentException("Parameter lists cannot be unified: duplicate parameter " + i);
33 }
34 }
35 }
36
37 public boolean dnfEqual(Dnf left, Dnf right) {
38 return dnfEqualityChecker.dnfEqual(left, right);
39 }
40
41 public boolean variableEqual(Variable left, Variable right) {
42 if (checkMapping(leftToRight, left, right) && checkMapping(rightToLeft, right, left)) {
43 leftToRight.put(left, right);
44 rightToLeft.put(right, left);
45 return true;
46 }
47 return false;
48 }
49
50 private static boolean checkMapping(Map<Variable, Variable> map, Variable key, Variable expectedValue) {
51 var currentValue = map.get(key);
52 return currentValue == null || currentValue.equals(expectedValue);
53 }
54}
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..8ef8e8b4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java
@@ -0,0 +1,143 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.*;
15
16public abstract class AbstractCallLiteral implements Literal {
17 private final Constraint target;
18 private final List<Variable> arguments;
19 private final Set<Variable> inArguments;
20 private final Set<Variable> outArguments;
21
22 // Use exhaustive switch over enums.
23 @SuppressWarnings("squid:S1301")
24 protected AbstractCallLiteral(Constraint target, List<Variable> arguments) {
25 int arity = target.arity();
26 if (arguments.size() != arity) {
27 throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(),
28 target.arity(), arguments.size()));
29 }
30 this.target = target;
31 this.arguments = arguments;
32 var mutableInArguments = new LinkedHashSet<Variable>();
33 var mutableOutArguments = new LinkedHashSet<Variable>();
34 var parameters = target.getParameters();
35 for (int i = 0; i < arity; i++) {
36 var argument = arguments.get(i);
37 var parameter = parameters.get(i);
38 if (!parameter.isAssignable(argument)) {
39 throw new IllegalArgumentException("Argument %d of %s is not assignable to parameter %s"
40 .formatted(i, target, parameter));
41 }
42 switch (parameter.getDirection()) {
43 case IN -> {
44 if (mutableOutArguments.remove(argument)) {
45 checkInOutUnifiable(argument);
46 }
47 mutableInArguments.add(argument);
48 }
49 case OUT -> {
50 if (mutableInArguments.contains(argument)) {
51 checkInOutUnifiable(argument);
52 } else if (!mutableOutArguments.add(argument)) {
53 checkDuplicateOutUnifiable(argument);
54 }
55 }
56 }
57 }
58 inArguments = Collections.unmodifiableSet(mutableInArguments);
59 outArguments = Collections.unmodifiableSet(mutableOutArguments);
60 }
61
62 private static void checkInOutUnifiable(Variable argument) {
63 if (!argument.isUnifiable()) {
64 throw new IllegalArgumentException("Argument %s cannot appear with both %s and %s direction"
65 .formatted(argument, ParameterDirection.IN, ParameterDirection.OUT));
66 }
67 }
68
69 private static void checkDuplicateOutUnifiable(Variable argument) {
70 if (!argument.isUnifiable()) {
71 throw new IllegalArgumentException("Argument %s cannot be bound multiple times".formatted(argument));
72 }
73 }
74
75 public Constraint getTarget() {
76 return target;
77 }
78
79 public List<Variable> getArguments() {
80 return arguments;
81 }
82
83 protected Set<Variable> getArgumentsOfDirection(ParameterDirection direction) {
84 return switch (direction) {
85 case IN -> inArguments;
86 case OUT -> outArguments;
87 };
88 }
89
90 @Override
91 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
92 var inputVariables = new LinkedHashSet<>(getArgumentsOfDirection(ParameterDirection.OUT));
93 inputVariables.retainAll(positiveVariablesInClause);
94 inputVariables.addAll(getArgumentsOfDirection(ParameterDirection.IN));
95 return Collections.unmodifiableSet(inputVariables);
96 }
97
98 @Override
99 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
100 var privateVariables = new LinkedHashSet<>(getArgumentsOfDirection(ParameterDirection.OUT));
101 privateVariables.removeAll(positiveVariablesInClause);
102 return Collections.unmodifiableSet(privateVariables);
103 }
104
105 @Override
106 public Literal substitute(Substitution substitution) {
107 var substitutedArguments = arguments.stream().map(substitution::getSubstitute).toList();
108 return doSubstitute(substitution, substitutedArguments);
109 }
110
111 protected abstract Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments);
112
113 @Override
114 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
115 if (other == null || getClass() != other.getClass()) {
116 return false;
117 }
118 var otherCallLiteral = (AbstractCallLiteral) other;
119 var arity = arguments.size();
120 if (arity != otherCallLiteral.arguments.size()) {
121 return false;
122 }
123 for (int i = 0; i < arity; i++) {
124 if (!helper.variableEqual(arguments.get(i), otherCallLiteral.arguments.get(i))) {
125 return false;
126 }
127 }
128 return target.equals(helper, otherCallLiteral.target);
129 }
130
131 @Override
132 public boolean equals(Object o) {
133 if (this == o) return true;
134 if (o == null || getClass() != o.getClass()) return false;
135 AbstractCallLiteral that = (AbstractCallLiteral) o;
136 return target.equals(that.target) && arguments.equals(that.arguments);
137 }
138
139 @Override
140 public int hashCode() {
141 return Objects.hash(getClass(), target, arguments);
142 }
143}
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..3a5eb5c7
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java
@@ -0,0 +1,138 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.*;
12
13import java.util.List;
14import java.util.Objects;
15import java.util.Set;
16
17public class AggregationLiteral<R, T> extends AbstractCallLiteral {
18 private final DataVariable<R> resultVariable;
19 private final DataVariable<T> inputVariable;
20 private final Aggregator<R, T> aggregator;
21
22 public AggregationLiteral(DataVariable<R> resultVariable, Aggregator<R, T> aggregator,
23 DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) {
24 super(target, arguments);
25 if (!inputVariable.getType().equals(aggregator.getInputType())) {
26 throw new IllegalArgumentException("Input variable %s must of type %s, got %s instead".formatted(
27 inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName()));
28 }
29 if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(inputVariable)) {
30 throw new IllegalArgumentException("Input variable %s must be bound with direction %s in the argument list"
31 .formatted(inputVariable, ParameterDirection.OUT));
32 }
33 if (!resultVariable.getType().equals(aggregator.getResultType())) {
34 throw new IllegalArgumentException("Result variable %s must of type %s, got %s instead".formatted(
35 resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName()));
36 }
37 if (arguments.contains(resultVariable)) {
38 throw new IllegalArgumentException("Result variable %s must not appear in the argument list".formatted(
39 resultVariable));
40 }
41 this.resultVariable = resultVariable;
42 this.inputVariable = inputVariable;
43 this.aggregator = aggregator;
44 }
45
46 public DataVariable<R> getResultVariable() {
47 return resultVariable;
48 }
49
50 public DataVariable<T> getInputVariable() {
51 return inputVariable;
52 }
53
54 public Aggregator<R, T> getAggregator() {
55 return aggregator;
56 }
57
58 @Override
59 public Set<Variable> getOutputVariables() {
60 return Set.of(resultVariable);
61 }
62
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
72 public Literal reduce() {
73 var reduction = getTarget().getReduction();
74 return switch (reduction) {
75 case ALWAYS_FALSE -> {
76 var emptyValue = aggregator.getEmptyResult();
77 yield emptyValue == null ? BooleanLiteral.FALSE :
78 resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), emptyValue));
79 }
80 case ALWAYS_TRUE -> throw new IllegalArgumentException("Trying to aggregate over an infinite set");
81 case NOT_REDUCIBLE -> this;
82 };
83 }
84
85 @Override
86 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
87 return new AggregationLiteral<>(substitution.getTypeSafeSubstitute(resultVariable), aggregator,
88 substitution.getTypeSafeSubstitute(inputVariable), getTarget(), substitutedArguments);
89 }
90
91 @Override
92 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
93 if (!super.equalsWithSubstitution(helper, other)) {
94 return false;
95 }
96 var otherAggregationLiteral = (AggregationLiteral<?, ?>) other;
97 return helper.variableEqual(resultVariable, otherAggregationLiteral.resultVariable) &&
98 aggregator.equals(otherAggregationLiteral.aggregator) &&
99 helper.variableEqual(inputVariable, otherAggregationLiteral.inputVariable);
100 }
101
102 @Override
103 public boolean equals(Object o) {
104 if (this == o) return true;
105 if (o == null || getClass() != o.getClass()) return false;
106 if (!super.equals(o)) return false;
107 AggregationLiteral<?, ?> that = (AggregationLiteral<?, ?>) o;
108 return resultVariable.equals(that.resultVariable) && inputVariable.equals(that.inputVariable) &&
109 aggregator.equals(that.aggregator);
110 }
111
112 @Override
113 public int hashCode() {
114 return Objects.hash(super.hashCode(), resultVariable, inputVariable, aggregator);
115 }
116
117 @Override
118 public String toString() {
119 var builder = new StringBuilder();
120 builder.append(resultVariable);
121 builder.append(" is ");
122 builder.append(getTarget().toReferenceString());
123 builder.append("(");
124 var argumentIterator = getArguments().iterator();
125 if (argumentIterator.hasNext()) {
126 var argument = argumentIterator.next();
127 if (inputVariable.equals(argument)) {
128 builder.append("@Aggregate(\"").append(aggregator).append("\") ");
129 }
130 builder.append(argument);
131 while (argumentIterator.hasNext()) {
132 builder.append(", ").append(argumentIterator.next());
133 }
134 }
135 builder.append(")");
136 return builder.toString();
137 }
138}
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..dbf999a2
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java
@@ -0,0 +1,79 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.DataVariable;
11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.Collections;
15import java.util.Objects;
16import java.util.Set;
17
18public record AssignLiteral<T>(DataVariable<T> variable, Term<T> term) implements Literal {
19 public AssignLiteral {
20 if (!term.getType().equals(variable.getType())) {
21 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted(
22 term, variable.getType().getName(), term.getType().getName()));
23 }
24 var inputVariables = term.getInputVariables();
25 if (inputVariables.contains(variable)) {
26 throw new IllegalArgumentException("Result variable %s must not appear in the term %s".formatted(
27 variable, term));
28 }
29 }
30
31 @Override
32 public Set<Variable> getOutputVariables() {
33 return Set.of(variable);
34 }
35
36 @Override
37 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
38 return Collections.unmodifiableSet(term.getInputVariables());
39 }
40
41 @Override
42 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
43 return Set.of();
44 }
45
46 @Override
47 public Literal substitute(Substitution substitution) {
48 return new AssignLiteral<>(substitution.getTypeSafeSubstitute(variable), term.substitute(substitution));
49 }
50
51 @Override
52 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
53 if (other == null || getClass() != other.getClass()) {
54 return false;
55 }
56 var otherLetLiteral = (AssignLiteral<?>) other;
57 return helper.variableEqual(variable, otherLetLiteral.variable) && term.equalsWithSubstitution(helper,
58 otherLetLiteral.term);
59 }
60
61 @Override
62 public String toString() {
63 return "%s is (%s)".formatted(variable, term);
64 }
65
66 @Override
67 public boolean equals(Object obj) {
68 if (obj == this) return true;
69 if (obj == null || obj.getClass() != this.getClass()) return false;
70 var that = (AssignLiteral<?>) obj;
71 return Objects.equals(this.variable, that.variable) &&
72 Objects.equals(this.term, that.term);
73 }
74
75 @Override
76 public int hashCode() {
77 return Objects.hash(getClass(), variable, term);
78 }
79}
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..1ca04c77
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java
@@ -0,0 +1,83 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.ConstantTerm;
11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.Collections;
15import java.util.Objects;
16import java.util.Set;
17
18public record AssumeLiteral(Term<Boolean> term) implements Literal {
19 public AssumeLiteral {
20 if (!term.getType().equals(Boolean.class)) {
21 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted(
22 term, Boolean.class.getName(), term.getType().getName()));
23 }
24 }
25
26 @Override
27 public Set<Variable> getOutputVariables() {
28 return Set.of();
29 }
30
31 @Override
32 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
33 return Collections.unmodifiableSet(term.getInputVariables());
34 }
35
36 @Override
37 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
38 return Set.of();
39 }
40
41
42 @Override
43 public Literal substitute(Substitution substitution) {
44 return new AssumeLiteral(term.substitute(substitution));
45 }
46
47 @Override
48 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
49 if (other == null || getClass() != other.getClass()) {
50 return false;
51 }
52 var otherAssumeLiteral = (AssumeLiteral) other;
53 return term.equalsWithSubstitution(helper, otherAssumeLiteral.term);
54 }
55
56 @Override
57 public Literal reduce() {
58 if (term instanceof ConstantTerm<Boolean> constantTerm) {
59 // Return {@link BooleanLiteral#FALSE} for {@code false} or {@code null} literals.
60 return Boolean.TRUE.equals(constantTerm.getValue()) ? BooleanLiteral.TRUE :
61 BooleanLiteral.FALSE;
62 }
63 return this;
64 }
65
66 @Override
67 public String toString() {
68 return "(%s)".formatted(term);
69 }
70
71 @Override
72 public boolean equals(Object obj) {
73 if (obj == this) return true;
74 if (obj == null || obj.getClass() != this.getClass()) return false;
75 var that = (AssumeLiteral) obj;
76 return Objects.equals(this.term, that.term);
77 }
78
79 @Override
80 public int hashCode() {
81 return Objects.hash(getClass(), term);
82 }
83}
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..f312d202
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java
@@ -0,0 +1,63 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Set;
13
14public enum BooleanLiteral implements CanNegate<BooleanLiteral> {
15 TRUE(true),
16 FALSE(false);
17
18 private final boolean value;
19
20 BooleanLiteral(boolean value) {
21 this.value = value;
22 }
23
24 @Override
25 public Set<Variable> getOutputVariables() {
26 return Set.of();
27 }
28
29 @Override
30 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
31 return Set.of();
32 }
33
34 @Override
35 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
36 return Set.of();
37 }
38
39 @Override
40 public Literal substitute(Substitution substitution) {
41 // No variables to substitute.
42 return this;
43 }
44
45 @Override
46 public BooleanLiteral negate() {
47 return fromBoolean(!value);
48 }
49
50 @Override
51 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
52 return equals(other);
53 }
54
55 @Override
56 public String toString() {
57 return Boolean.toString(value);
58 }
59
60 public static BooleanLiteral fromBoolean(boolean value) {
61 return value ? TRUE : FALSE;
62 }
63}
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..29772aee
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java
@@ -0,0 +1,130 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.*;
15
16public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> {
17 private final CallPolarity polarity;
18
19 public CallLiteral(CallPolarity polarity, Constraint target, List<Variable> arguments) {
20 super(target, arguments);
21 var parameters = target.getParameters();
22 int arity = target.arity();
23 if (polarity.isTransitive()) {
24 if (arity != 2) {
25 throw new IllegalArgumentException("Transitive closures can only take binary relations");
26 }
27 if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) {
28 throw new IllegalArgumentException("Transitive closures can only be computed over nodes");
29 }
30 }
31 this.polarity = polarity;
32 }
33
34 public CallPolarity getPolarity() {
35 return polarity;
36 }
37
38 @Override
39 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
40 return new CallLiteral(polarity, getTarget(), substitutedArguments);
41 }
42
43 @Override
44 public Set<Variable> getOutputVariables() {
45 if (polarity.isPositive()) {
46 return getArgumentsOfDirection(ParameterDirection.OUT);
47 }
48 return Set.of();
49 }
50
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
68 public Literal reduce() {
69 var reduction = getTarget().getReduction();
70 var negatedReduction = polarity.isPositive() ? reduction : reduction.negate();
71 return switch (negatedReduction) {
72 case ALWAYS_TRUE -> BooleanLiteral.TRUE;
73 case ALWAYS_FALSE -> BooleanLiteral.FALSE;
74 default -> this;
75 };
76 }
77
78 @Override
79 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
80 if (!super.equalsWithSubstitution(helper, other)) {
81 return false;
82 }
83 var otherCallLiteral = (CallLiteral) other;
84 return polarity.equals(otherCallLiteral.polarity);
85 }
86
87 @Override
88 public CallLiteral negate() {
89 return new CallLiteral(polarity.negate(), getTarget(), getArguments());
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 CallLiteral that = (CallLiteral) o;
98 return polarity == that.polarity;
99 }
100
101 @Override
102 public int hashCode() {
103 return Objects.hash(super.hashCode(), polarity);
104 }
105
106 @Override
107 public String toString() {
108 var builder = new StringBuilder();
109 if (!polarity.isPositive()) {
110 builder.append("!(");
111 }
112 builder.append(getTarget().toReferenceString());
113 if (polarity.isTransitive()) {
114 builder.append("+");
115 }
116 builder.append("(");
117 var argumentIterator = getArguments().iterator();
118 if (argumentIterator.hasNext()) {
119 builder.append(argumentIterator.next());
120 while (argumentIterator.hasNext()) {
121 builder.append(", ").append(argumentIterator.next());
122 }
123 }
124 builder.append(")");
125 if (!polarity.isPositive()) {
126 builder.append(")");
127 }
128 return builder.toString();
129 }
130}
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..ca70b0fd
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java
@@ -0,0 +1,37 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8public enum CallPolarity {
9 POSITIVE(true, false),
10 NEGATIVE(false, false),
11 TRANSITIVE(true, true);
12
13 private final boolean positive;
14
15 private final boolean transitive;
16
17 CallPolarity(boolean positive, boolean transitive) {
18 this.positive = positive;
19 this.transitive = transitive;
20 }
21
22 public boolean isPositive() {
23 return positive;
24 }
25
26 public boolean isTransitive() {
27 return transitive;
28 }
29
30 public CallPolarity negate() {
31 return switch (this) {
32 case POSITIVE -> NEGATIVE;
33 case NEGATIVE -> POSITIVE;
34 case TRANSITIVE -> throw new IllegalArgumentException("Transitive polarity cannot be negated");
35 };
36 }
37}
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..35dcb3fb
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java
@@ -0,0 +1,10 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8public interface CanNegate<T extends CanNegate<T>> extends Literal {
9 T negate();
10}
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..73545620
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java
@@ -0,0 +1,65 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.Variable;
12
13import java.util.Objects;
14import java.util.Set;
15
16public record ConstantLiteral(NodeVariable variable, int nodeId) implements Literal {
17 @Override
18 public Set<Variable> getOutputVariables() {
19 return Set.of(variable);
20 }
21
22 @Override
23 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
24 return Set.of();
25 }
26
27 @Override
28 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
29 return Set.of();
30 }
31
32 @Override
33 public ConstantLiteral substitute(Substitution substitution) {
34 return new ConstantLiteral(substitution.getTypeSafeSubstitute(variable), nodeId);
35 }
36
37 @Override
38 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
39 if (other.getClass() != getClass()) {
40 return false;
41 }
42 var otherConstantLiteral = (ConstantLiteral) other;
43 return helper.variableEqual(variable, otherConstantLiteral.variable) && nodeId == otherConstantLiteral.nodeId;
44 }
45
46
47 @Override
48 public String toString() {
49 return "%s === @Constant %d".formatted(variable, nodeId);
50 }
51
52 @Override
53 public boolean equals(Object obj) {
54 if (obj == this) return true;
55 if (obj == null || obj.getClass() != this.getClass()) return false;
56 var that = (ConstantLiteral) obj;
57 return Objects.equals(this.variable, that.variable) &&
58 this.nodeId == that.nodeId;
59 }
60
61 @Override
62 public int hashCode() {
63 return Objects.hash(getClass(), variable, nodeId);
64 }
65}
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..4d4749c8
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java
@@ -0,0 +1,101 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.DataVariable;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.query.term.int_.IntTerms;
14
15import java.util.List;
16import java.util.Objects;
17import java.util.Set;
18
19public class CountLiteral extends AbstractCallLiteral {
20 private final DataVariable<Integer> resultVariable;
21
22 public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) {
23 super(target, arguments);
24 if (!resultVariable.getType().equals(Integer.class)) {
25 throw new IllegalArgumentException("Count result variable %s must be of type %s, got %s instead".formatted(
26 resultVariable, Integer.class.getName(), resultVariable.getType().getName()));
27 }
28 if (arguments.contains(resultVariable)) {
29 throw new IllegalArgumentException("Count result variable %s must not appear in the argument list"
30 .formatted(resultVariable));
31 }
32 this.resultVariable = resultVariable;
33 }
34
35 public DataVariable<Integer> getResultVariable() {
36 return resultVariable;
37 }
38
39 @Override
40 public Set<Variable> getOutputVariables() {
41 return Set.of(resultVariable);
42 }
43
44 @Override
45 public Literal reduce() {
46 var reduction = getTarget().getReduction();
47 return switch (reduction) {
48 case ALWAYS_FALSE -> getResultVariable().assign(IntTerms.constant(0));
49 // The only way a constant {@code true} predicate can be called in a negative position is to have all of
50 // its arguments bound as input variables. Thus, there will only be a single match.
51 case ALWAYS_TRUE -> getResultVariable().assign(IntTerms.constant(1));
52 case NOT_REDUCIBLE -> this;
53 };
54 }
55
56 @Override
57 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
58 return new CountLiteral(substitution.getTypeSafeSubstitute(resultVariable), getTarget(), substitutedArguments);
59 }
60
61 @Override
62 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
63 if (!super.equalsWithSubstitution(helper, other)) {
64 return false;
65 }
66 var otherCountLiteral = (CountLiteral) other;
67 return helper.variableEqual(resultVariable, otherCountLiteral.resultVariable);
68 }
69
70 @Override
71 public boolean equals(Object o) {
72 if (this == o) return true;
73 if (o == null || getClass() != o.getClass()) return false;
74 if (!super.equals(o)) return false;
75 CountLiteral that = (CountLiteral) o;
76 return resultVariable.equals(that.resultVariable);
77 }
78
79 @Override
80 public int hashCode() {
81 return Objects.hash(super.hashCode(), resultVariable);
82 }
83
84 @Override
85 public String toString() {
86 var builder = new StringBuilder();
87 builder.append(resultVariable);
88 builder.append(" is count ");
89 builder.append(getTarget().toReferenceString());
90 builder.append("(");
91 var argumentIterator = getArguments().iterator();
92 if (argumentIterator.hasNext()) {
93 builder.append(argumentIterator.next());
94 while (argumentIterator.hasNext()) {
95 builder.append(", ").append(argumentIterator.next());
96 }
97 }
98 builder.append(")");
99 return builder.toString();
100 }
101}
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..28ba7625
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java
@@ -0,0 +1,81 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.Variable;
12
13import java.util.Objects;
14import java.util.Set;
15
16public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariable right)
17 implements CanNegate<EquivalenceLiteral> {
18 @Override
19 public Set<Variable> getOutputVariables() {
20 return Set.of(left);
21 }
22
23 @Override
24 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
25 return Set.of(right);
26 }
27
28 @Override
29 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
30 return Set.of();
31 }
32
33 @Override
34 public EquivalenceLiteral negate() {
35 return new EquivalenceLiteral(!positive, left, right);
36 }
37
38 @Override
39 public EquivalenceLiteral substitute(Substitution substitution) {
40 return new EquivalenceLiteral(positive, substitution.getTypeSafeSubstitute(left),
41 substitution.getTypeSafeSubstitute(right));
42 }
43
44 @Override
45 public Literal reduce() {
46 if (left.equals(right)) {
47 return positive ? BooleanLiteral.TRUE : BooleanLiteral.FALSE;
48 }
49 return this;
50 }
51
52 @Override
53 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
54 if (other.getClass() != getClass()) {
55 return false;
56 }
57 var otherEquivalenceLiteral = (EquivalenceLiteral) other;
58 return helper.variableEqual(left, otherEquivalenceLiteral.left) && helper.variableEqual(right,
59 otherEquivalenceLiteral.right);
60 }
61
62 @Override
63 public String toString() {
64 return "%s %s %s".formatted(left, positive ? "===" : "!==", right);
65 }
66
67 @Override
68 public boolean equals(Object obj) {
69 if (obj == this) return true;
70 if (obj == null || obj.getClass() != this.getClass()) return false;
71 var that = (EquivalenceLiteral) obj;
72 return this.positive == that.positive &&
73 Objects.equals(this.left, that.left) &&
74 Objects.equals(this.right, that.right);
75 }
76
77 @Override
78 public int hashCode() {
79 return Objects.hash(getClass(), positive, left, right);
80 }
81}
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..ce6c11fe
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Set;
13
14public interface Literal {
15 Set<Variable> getOutputVariables();
16
17 Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause);
18
19 Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause);
20
21 Literal substitute(Substitution substitution);
22
23 default Literal reduce() {
24 return this;
25 }
26
27 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
28 boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other);
29}
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..b3a87811
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.term.Term;
9
10public final class Literals {
11 private Literals() {
12 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
13 }
14
15 public static <T extends CanNegate<T>> T not(CanNegate<T> literal) {
16 return literal.negate();
17 }
18
19 public static AssumeLiteral assume(Term<Boolean> term) {
20 return new AssumeLiteral(term);
21 }
22}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Reduction.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Reduction.java
new file mode 100644
index 00000000..ee155a9a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Reduction.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8public enum Reduction {
9 /**
10 * Signifies that a literal should be preserved in the clause.
11 */
12 NOT_REDUCIBLE,
13
14 /**
15 * Signifies that the literal may be omitted from the cause (if the model being queried is nonempty).
16 */
17 ALWAYS_TRUE,
18
19 /**
20 * Signifies that the clause with the literal may be omitted entirely.
21 */
22 ALWAYS_FALSE;
23
24 public Reduction negate() {
25 return switch (this) {
26 case NOT_REDUCIBLE -> NOT_REDUCIBLE;
27 case ALWAYS_TRUE -> ALWAYS_FALSE;
28 case ALWAYS_FALSE -> ALWAYS_TRUE;
29 };
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java
new file mode 100644
index 00000000..a710c64d
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java
@@ -0,0 +1,63 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.query.ModelQueryAdapter;
9import tools.refinery.store.query.dnf.Query;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.ArrayList;
13import java.util.List;
14
15public abstract class AbstractResultSet<T> implements ResultSet<T> {
16 private final ModelQueryAdapter adapter;
17 private final Query<T> query;
18 private final List<ResultSetListener<T>> listeners = new ArrayList<>();
19
20 protected AbstractResultSet(ModelQueryAdapter adapter, Query<T> query) {
21 this.adapter = adapter;
22 this.query = query;
23 }
24
25 @Override
26 public ModelQueryAdapter getAdapter() {
27 return adapter;
28 }
29
30 @Override
31 public Query<T> getQuery() {
32 return query;
33 }
34
35 @Override
36 public void addListener(ResultSetListener<T> listener) {
37 if (listeners.isEmpty()) {
38 startListeningForChanges();
39 }
40 listeners.add(listener);
41 }
42
43 @Override
44 public void removeListener(ResultSetListener<T> listener) {
45 listeners.remove(listener);
46 if (listeners.isEmpty()) {
47 stopListeningForChanges();
48 }
49 }
50
51 protected abstract void startListeningForChanges();
52
53 protected abstract void stopListeningForChanges();
54
55 protected void notifyChange(Tuple key, T oldValue, T newValue) {
56 int listenerCount = listeners.size();
57 // Use a for loop instead of a for-each loop to avoid {@code Iterator} allocation overhead.
58 //noinspection ForLoopReplaceableByForEach
59 for (int i = 0; i < listenerCount; i++) {
60 listeners.get(i).put(key, oldValue, newValue);
61 }
62 }
63}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java
new file mode 100644
index 00000000..02809477
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.query.ModelQueryAdapter;
9import tools.refinery.store.query.dnf.AnyQuery;
10
11public sealed interface AnyResultSet permits ResultSet {
12 ModelQueryAdapter getAdapter();
13
14 AnyQuery getQuery();
15
16 int size();
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java
new file mode 100644
index 00000000..2795a44b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java
@@ -0,0 +1,49 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.Cursors;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.tuple.Tuple;
13
14public record EmptyResultSet<T>(ModelQueryAdapter adapter, Query<T> query) implements ResultSet<T> {
15 @Override
16 public ModelQueryAdapter getAdapter() {
17 return adapter;
18 }
19
20 @Override
21 public Query<T> getQuery() {
22 return query;
23 }
24
25 @Override
26 public T get(Tuple parameters) {
27 return query.defaultValue();
28 }
29
30 @Override
31 public Cursor<Tuple, T> getAll() {
32 return Cursors.empty();
33 }
34
35 @Override
36 public int size() {
37 return 0;
38 }
39
40 @Override
41 public void addListener(ResultSetListener<T> listener) {
42 // No need to store the listener, because the empty result set will never change.
43 }
44
45 @Override
46 public void removeListener(ResultSetListener<T> listener) {
47 // No need to remove the listener, because we never stored it.
48 }
49}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java
new file mode 100644
index 00000000..39006d65
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java
@@ -0,0 +1,80 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.query.ModelQueryAdapter;
10import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.utils.OrderStatisticTree;
12import tools.refinery.store.tuple.Tuple;
13
14import java.util.Objects;
15
16public class OrderedResultSet<T> implements AutoCloseable, ResultSet<T> {
17 private final ResultSet<T> resultSet;
18 private final OrderStatisticTree<Tuple> tree = new OrderStatisticTree<>();
19 private final ResultSetListener<T> listener = (key, fromValue, toValue) -> {
20 var defaultValue = getQuery().defaultValue();
21 if (Objects.equals(defaultValue, toValue)) {
22 tree.remove(key);
23 } else {
24 tree.add(key);
25 }
26 };
27
28 public OrderedResultSet(ResultSet<T> resultSet) {
29 this.resultSet = resultSet;
30 resultSet.addListener(listener);
31 var cursor = resultSet.getAll();
32 while (cursor.move()) {
33 tree.add(cursor.getKey());
34 }
35 }
36
37 @Override
38 public ModelQueryAdapter getAdapter() {
39 return resultSet.getAdapter();
40 }
41
42 @Override
43 public int size() {
44 return resultSet.size();
45 }
46
47 @Override
48 public Query<T> getQuery() {
49 return resultSet.getQuery();
50 }
51
52 @Override
53 public T get(Tuple parameters) {
54 return resultSet.get(parameters);
55 }
56
57 public Tuple getKey(int index) {
58 return tree.get(index);
59 }
60
61 @Override
62 public Cursor<Tuple, T> getAll() {
63 return resultSet.getAll();
64 }
65
66 @Override
67 public void addListener(ResultSetListener<T> listener) {
68 resultSet.addListener(listener);
69 }
70
71 @Override
72 public void removeListener(ResultSetListener<T> listener) {
73 resultSet.removeListener(listener);
74 }
75
76 @Override
77 public void close() {
78 resultSet.removeListener(listener);
79 }
80}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java
new file mode 100644
index 00000000..33d1ea95
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.query.dnf.Query;
10import tools.refinery.store.tuple.Tuple;
11
12public non-sealed interface ResultSet<T> extends AnyResultSet {
13 Query<T> getQuery();
14
15 T get(Tuple parameters);
16
17 Cursor<Tuple, T> getAll();
18
19 void addListener(ResultSetListener<T> listener);
20
21 void removeListener(ResultSetListener<T> listener);
22}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSetListener.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSetListener.java
new file mode 100644
index 00000000..fd8a503e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSetListener.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.tuple.Tuple;
9
10@FunctionalInterface
11public interface ResultSetListener<T> {
12 void put(Tuple key, T fromValue, T toValue);
13}
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..a8201eef
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java
@@ -0,0 +1,18 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.substitution;
7
8import tools.refinery.store.query.term.Variable;
9
10import java.util.Map;
11
12public record MapBasedSubstitution(Map<Variable, Variable> map, Substitution fallback) implements Substitution {
13 @Override
14 public Variable getSubstitute(Variable variable) {
15 var value = map.get(variable);
16 return value == null ? fallback.getSubstitute(variable) : value;
17 }
18}
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..9b737ceb
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java
@@ -0,0 +1,20 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.substitution;
7
8import tools.refinery.store.query.term.Variable;
9
10import java.util.HashMap;
11import java.util.Map;
12
13public class RenewingSubstitution implements Substitution {
14 private final Map<Variable, Variable> alreadyRenewed = new HashMap<>();
15
16 @Override
17 public Variable getSubstitute(Variable variable) {
18 return alreadyRenewed.computeIfAbsent(variable, Variable::renew);
19 }
20}
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..bb3803d3
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java
@@ -0,0 +1,23 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.substitution;
7
8import tools.refinery.store.query.term.Variable;
9
10public enum StatelessSubstitution implements Substitution {
11 FAILING {
12 @Override
13 public Variable getSubstitute(Variable variable) {
14 throw new IllegalArgumentException("No substitute for " + variable);
15 }
16 },
17 IDENTITY {
18 @Override
19 public Variable getSubstitute(Variable variable) {
20 return variable;
21 }
22 }
23}
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..834fce12
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.substitution;
7
8import tools.refinery.store.query.term.DataVariable;
9import tools.refinery.store.query.term.NodeVariable;
10import tools.refinery.store.query.term.Variable;
11
12@FunctionalInterface
13public interface Substitution {
14 Variable getSubstitute(Variable variable);
15
16 default NodeVariable getTypeSafeSubstitute(NodeVariable variable) {
17 var substitute = getSubstitute(variable);
18 return substitute.asNodeVariable();
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 static SubstitutionBuilder builder() {
27 return new SubstitutionBuilder();
28 }
29}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/SubstitutionBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/SubstitutionBuilder.java
new file mode 100644
index 00000000..37fb6908
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/SubstitutionBuilder.java
@@ -0,0 +1,79 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.substitution;
7
8import tools.refinery.store.query.term.DataVariable;
9import tools.refinery.store.query.term.NodeVariable;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.List;
15import java.util.Map;
16
17@SuppressWarnings("UnusedReturnValue")
18public class SubstitutionBuilder {
19 private final Map<Variable, Variable> map = new HashMap<>();
20 private Substitution fallback;
21
22 SubstitutionBuilder() {
23 total();
24 }
25
26 public SubstitutionBuilder put(NodeVariable original, NodeVariable substitute) {
27 return putChecked(original, substitute);
28 }
29
30 public <T> SubstitutionBuilder put(DataVariable<T> original, DataVariable<T> substitute) {
31 return putChecked(original, substitute);
32 }
33
34 public SubstitutionBuilder putChecked(Variable original, Variable substitute) {
35 if (!original.tryGetType().equals(substitute.tryGetType())) {
36 throw new IllegalArgumentException("Cannot substitute variable %s of sort %s with variable %s of sort %s"
37 .formatted(original, original.tryGetType().map(Class::getName).orElse("node"), substitute,
38 substitute.tryGetType().map(Class::getName).orElse("node")));
39 }
40 if (map.containsKey(original)) {
41 throw new IllegalArgumentException("Already has substitution for variable %s".formatted(original));
42 }
43 map.put(original, substitute);
44 return this;
45 }
46
47 public SubstitutionBuilder putManyChecked(List<Variable> originals, List<Variable> substitutes) {
48 int size = originals.size();
49 if (size != substitutes.size()) {
50 throw new IllegalArgumentException("Cannot substitute %d variables %s with %d variables %s"
51 .formatted(size, originals, substitutes.size(), substitutes));
52 }
53 for (int i = 0; i < size; i++) {
54 putChecked(originals.get(i), substitutes.get(i));
55 }
56 return this;
57 }
58
59 public SubstitutionBuilder fallback(Substitution newFallback) {
60 fallback = newFallback;
61 return this;
62 }
63
64 public SubstitutionBuilder total() {
65 return fallback(StatelessSubstitution.FAILING);
66 }
67
68 public SubstitutionBuilder partial() {
69 return fallback(StatelessSubstitution.IDENTITY);
70 }
71
72 public SubstitutionBuilder renewing() {
73 return fallback(new RenewingSubstitution());
74 }
75
76 public Substitution build() {
77 return map.isEmpty() ? fallback : new MapBasedSubstitution(Collections.unmodifiableMap(map), fallback);
78 }
79}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java
new file mode 100644
index 00000000..d0ae3c12
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java
@@ -0,0 +1,41 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9
10import java.util.Objects;
11
12public abstract class AbstractTerm<T> implements Term<T> {
13 private final Class<T> type;
14
15 protected AbstractTerm(Class<T> type) {
16 this.type = type;
17 }
18
19 @Override
20 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
21 return getClass().equals(other.getClass()) && type.equals(other.getType());
22 }
23
24 @Override
25 public Class<T> getType() {
26 return type;
27 }
28
29 @Override
30 public boolean equals(Object o) {
31 if (this == o) return true;
32 if (o == null || getClass() != o.getClass()) return false;
33 AbstractTerm<?> that = (AbstractTerm<?>) o;
34 return type.equals(that.type);
35 }
36
37 @Override
38 public int hashCode() {
39 return Objects.hash(getClass(), type);
40 }
41}
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..0684a9d9
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java
@@ -0,0 +1,18 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import java.util.stream.Stream;
9
10public interface Aggregator<R, T> {
11 Class<R> getResultType();
12
13 Class<T> getInputType();
14
15 R aggregateStream(Stream<T> stream);
16
17 R getEmptyResult();
18}
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..192c39c5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java
@@ -0,0 +1,49 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10
11import java.util.Optional;
12import java.util.Set;
13
14public abstract sealed class AnyDataVariable extends Variable implements AnyTerm permits DataVariable {
15 protected AnyDataVariable(String name) {
16 super(name);
17 }
18
19 @Override
20 public Optional<Class<?>> tryGetType() {
21 return Optional.of(getType());
22 }
23
24 @Override
25 public NodeVariable asNodeVariable() {
26 throw new IllegalStateException("%s is a data variable".formatted(this));
27 }
28
29 @Override
30 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
31 return other instanceof AnyDataVariable dataVariable && helper.variableEqual(this, dataVariable);
32 }
33
34 @Override
35 public Set<AnyDataVariable> getInputVariables() {
36 return Set.of(this);
37 }
38
39 @Override
40 public boolean isUnifiable() {
41 return false;
42 }
43
44 @Override
45 public abstract AnyDataVariable renew(@Nullable String name);
46
47 @Override
48 public abstract AnyDataVariable renew();
49}
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..c12c0166
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10
11import java.util.Set;
12
13public sealed interface AnyTerm permits AnyDataVariable, Term {
14 Class<?> getType();
15
16 AnyTerm substitute(Substitution substitution);
17
18 boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other);
19
20 Set<AnyDataVariable> getInputVariables();
21}
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..0cf30aa6
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.literal.Literal;
9
10@FunctionalInterface
11public interface AssignedValue<T> {
12 Literal toLiteral(DataVariable<T> targetVariable);
13}
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..8ad17839
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java
@@ -0,0 +1,113 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.valuation.Valuation;
11
12import java.util.Collections;
13import java.util.HashSet;
14import java.util.Objects;
15import java.util.Set;
16
17public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> {
18 private final Class<T1> leftType;
19 private final Class<T2> rightType;
20 private final Term<T1> left;
21 private final Term<T2> right;
22
23 protected BinaryTerm(Class<R> type, Class<T1> leftType, Class<T2> rightType, Term<T1> left, Term<T2> right) {
24 super(type);
25 if (!left.getType().equals(leftType)) {
26 throw new IllegalArgumentException("Expected left %s to be of type %s, got %s instead".formatted(
27 left, leftType.getName(), left.getType().getName()));
28 }
29 if (!right.getType().equals(rightType)) {
30 throw new IllegalArgumentException("Expected right %s to be of type %s, got %s instead".formatted(
31 right, rightType.getName(), right.getType().getName()));
32 }
33 this.leftType = leftType;
34 this.rightType = rightType;
35 this.left = left;
36 this.right = right;
37 }
38
39 public Class<T1> getLeftType() {
40 return leftType;
41 }
42
43 public Class<T2> getRightType() {
44 return rightType;
45 }
46
47 public Term<T1> getLeft() {
48 return left;
49 }
50
51 public Term<T2> getRight() {
52 return right;
53 }
54
55 @Override
56 public R evaluate(Valuation valuation) {
57 var leftValue = left.evaluate(valuation);
58 if (leftValue == null) {
59 return null;
60 }
61 var rightValue = right.evaluate(valuation);
62 if (rightValue == null) {
63 return null;
64 }
65 return doEvaluate(leftValue, rightValue);
66 }
67
68 protected abstract R doEvaluate(T1 leftValue, T2 rightValue);
69
70 @Override
71 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
72 if (!super.equalsWithSubstitution(helper, other)) {
73 return false;
74 }
75 var otherBinaryTerm = (BinaryTerm<?, ?, ?>) other;
76 return leftType.equals(otherBinaryTerm.leftType) &&
77 rightType.equals(otherBinaryTerm.rightType) &&
78 left.equalsWithSubstitution(helper, otherBinaryTerm.left) &&
79 right.equalsWithSubstitution(helper, otherBinaryTerm.right);
80 }
81
82 @Override
83 public Term<R> substitute(Substitution substitution) {
84 return doSubstitute(substitution, left.substitute(substitution), right.substitute(substitution));
85 }
86
87 public abstract Term<R> doSubstitute(Substitution substitution, Term<T1> substitutedLeft,
88 Term<T2> substitutedRight);
89
90 @Override
91 public Set<AnyDataVariable> getInputVariables() {
92 var inputVariables = new HashSet<>(left.getInputVariables());
93 inputVariables.addAll(right.getInputVariables());
94 return Collections.unmodifiableSet(inputVariables);
95 }
96
97 @Override
98 public boolean equals(Object o) {
99 if (this == o) return true;
100 if (o == null || getClass() != o.getClass()) return false;
101 if (!super.equals(o)) return false;
102 BinaryTerm<?, ?, ?> that = (BinaryTerm<?, ?, ?>) o;
103 return Objects.equals(leftType, that.leftType) &&
104 Objects.equals(rightType, that.rightType) &&
105 Objects.equals(left, that.left) &&
106 Objects.equals(right, that.right);
107 }
108
109 @Override
110 public int hashCode() {
111 return Objects.hash(super.hashCode(), leftType, rightType, left, right);
112 }
113}
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..2f6c56d1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java
@@ -0,0 +1,71 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.valuation.Valuation;
11
12import java.util.Objects;
13import java.util.Set;
14
15public final class ConstantTerm<T> extends AbstractTerm<T> {
16 private final T value;
17
18 public ConstantTerm(Class<T> type, T value) {
19 super(type);
20 if (value != null && !type.isInstance(value)) {
21 throw new IllegalArgumentException("Value %s is not an instance of %s".formatted(value, type.getName()));
22 }
23 this.value = value;
24 }
25
26 public T getValue() {
27 return value;
28 }
29
30 @Override
31 public T evaluate(Valuation valuation) {
32 return getValue();
33 }
34
35 @Override
36 public Term<T> substitute(Substitution substitution) {
37 return this;
38 }
39
40 @Override
41 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
42 if (!super.equalsWithSubstitution(helper, other)) {
43 return false;
44 }
45 var otherConstantTerm = (ConstantTerm<?>) other;
46 return Objects.equals(value, otherConstantTerm.value);
47 }
48
49 @Override
50 public Set<AnyDataVariable> getInputVariables() {
51 return Set.of();
52 }
53
54 @Override
55 public String toString() {
56 return value.toString();
57 }
58
59 @Override
60 public boolean equals(Object o) {
61 if (this == o) return true;
62 if (o == null || getClass() != o.getClass()) return false;
63 ConstantTerm<?> that = (ConstantTerm<?>) o;
64 return Objects.equals(value, that.value);
65 }
66
67 @Override
68 public int hashCode() {
69 return Objects.hash(value);
70 }
71}
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..00950360
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java
@@ -0,0 +1,82 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.literal.Literal;
11import tools.refinery.store.query.substitution.Substitution;
12import tools.refinery.store.query.valuation.Valuation;
13
14import java.util.Objects;
15
16public final class DataVariable<T> extends AnyDataVariable implements Term<T> {
17 private final Class<T> type;
18
19 DataVariable(String name, Class<T> type) {
20 super(name);
21 this.type = type;
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 <U> DataVariable<U> asDataVariable(Class<U> newType) {
41 if (!getType().equals(newType)) {
42 throw new IllegalStateException("%s is not of type %s but of type %s".formatted(this, newType.getName(),
43 getType().getName()));
44 }
45 @SuppressWarnings("unchecked")
46 var result = (DataVariable<U>) this;
47 return result;
48 }
49
50 @Override
51 public T evaluate(Valuation valuation) {
52 return valuation.getValue(this);
53 }
54
55 @Override
56 public Term<T> substitute(Substitution substitution) {
57 return substitution.getTypeSafeSubstitute(this);
58 }
59
60 @Override
61 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
62 return other instanceof DataVariable<?> dataVariable && helper.variableEqual(this, dataVariable);
63 }
64
65 public Literal assign(AssignedValue<T> value) {
66 return value.toLiteral(this);
67 }
68
69 @Override
70 public boolean equals(Object o) {
71 if (this == o) return true;
72 if (o == null || getClass() != o.getClass()) return false;
73 if (!super.equals(o)) return false;
74 DataVariable<?> that = (DataVariable<?>) o;
75 return type.equals(that.type);
76 }
77
78 @Override
79 public int hashCode() {
80 return Objects.hash(super.hashCode(), type);
81 }
82}
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..657cb631
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java
@@ -0,0 +1,108 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import java.util.Comparator;
9import java.util.Objects;
10import java.util.SortedMap;
11import java.util.TreeMap;
12
13public class ExtremeValueAggregator<T> implements StatefulAggregator<T, T> {
14 private final Class<T> type;
15 private final T emptyResult;
16 private final Comparator<T> comparator;
17
18 public ExtremeValueAggregator(Class<T> type, T emptyResult) {
19 this(type, emptyResult, null);
20 }
21
22 public ExtremeValueAggregator(Class<T> type, T emptyResult, Comparator<T> comparator) {
23 this.type = type;
24 this.emptyResult = emptyResult;
25 this.comparator = comparator;
26 }
27
28 @Override
29 public Class<T> getResultType() {
30 return getInputType();
31 }
32
33 @Override
34 public Class<T> getInputType() {
35 return type;
36 }
37
38 @Override
39 public StatefulAggregate<T, T> createEmptyAggregate() {
40 return new Aggregate();
41 }
42
43 @Override
44 public T getEmptyResult() {
45 return emptyResult;
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 ExtremeValueAggregator<?> that = (ExtremeValueAggregator<?>) o;
53 return type.equals(that.type) && Objects.equals(emptyResult, that.emptyResult) && Objects.equals(comparator,
54 that.comparator);
55 }
56
57 @Override
58 public int hashCode() {
59 return Objects.hash(type, emptyResult, comparator);
60 }
61
62 private class Aggregate implements StatefulAggregate<T, T> {
63 private final SortedMap<T, Integer> values;
64
65 private Aggregate() {
66 values = new TreeMap<>(comparator);
67 }
68
69 private Aggregate(Aggregate other) {
70 values = new TreeMap<>(other.values);
71 }
72
73 @Override
74 public void add(T value) {
75 values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1);
76 }
77
78 @Override
79 public void remove(T value) {
80 values.compute(value, (theValue, currentCount) -> {
81 if (currentCount == null || currentCount <= 0) {
82 throw new IllegalStateException("Invalid count %d for value %s".formatted(currentCount, theValue));
83 }
84 return currentCount.equals(1) ? null : currentCount - 1;
85 });
86 }
87
88 @Override
89 public T getResult() {
90 return isEmpty() ? emptyResult : values.firstKey();
91 }
92
93 @Override
94 public boolean isEmpty() {
95 return values.isEmpty();
96 }
97
98 @Override
99 public StatefulAggregate<T, T> deepCopy() {
100 return new Aggregate(this);
101 }
102
103 @Override
104 public boolean contains(T value) {
105 return StatefulAggregate.super.contains(value);
106 }
107 }
108}
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..a2f3261f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java
@@ -0,0 +1,60 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.literal.ConstantLiteral;
10import tools.refinery.store.query.literal.EquivalenceLiteral;
11
12import java.util.Optional;
13
14public final class NodeVariable extends Variable {
15 NodeVariable(@Nullable String name) {
16 super(name);
17 }
18
19 @Override
20 public Optional<Class<?>> tryGetType() {
21 return Optional.empty();
22 }
23
24 @Override
25 public boolean isUnifiable() {
26 return true;
27 }
28
29 @Override
30 public NodeVariable renew(@Nullable String name) {
31 return Variable.of(name);
32 }
33
34 @Override
35 public NodeVariable renew() {
36 return renew(getExplicitName());
37 }
38
39 @Override
40 public NodeVariable asNodeVariable() {
41 return this;
42 }
43
44 @Override
45 public <T> DataVariable<T> asDataVariable(Class<T> type) {
46 throw new IllegalStateException("%s is a node variable".formatted(this));
47 }
48
49 public ConstantLiteral isConstant(int value) {
50 return new ConstantLiteral(this, value);
51 }
52
53 public EquivalenceLiteral isEquivalent(NodeVariable other) {
54 return new EquivalenceLiteral(true, this, other);
55 }
56
57 public EquivalenceLiteral notEquivalent(NodeVariable other) {
58 return new EquivalenceLiteral(false, this, other);
59 }
60}
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
new file mode 100644
index 00000000..e5a0cdf1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java
@@ -0,0 +1,60 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import java.util.Objects;
9import java.util.Optional;
10
11public class Parameter {
12 public static final Parameter NODE_OUT = new Parameter(null, ParameterDirection.OUT);
13
14 private final Class<?> dataType;
15 private final ParameterDirection direction;
16
17 public Parameter(Class<?> dataType, ParameterDirection direction) {
18 this.dataType = dataType;
19 this.direction = direction;
20 }
21
22 public boolean isNodeVariable() {
23 return dataType == null;
24 }
25
26 public boolean isDataVariable() {
27 return !isNodeVariable();
28 }
29
30 public Optional<Class<?>> tryGetType() {
31 return Optional.ofNullable(dataType);
32 }
33
34 public ParameterDirection getDirection() {
35 return direction;
36 }
37
38 public boolean isAssignable(Variable variable) {
39 if (variable instanceof AnyDataVariable dataVariable) {
40 return dataVariable.getType().equals(dataType);
41 } else if (variable instanceof NodeVariable) {
42 return !isDataVariable();
43 } else {
44 throw new IllegalArgumentException("Unknown variable " + variable);
45 }
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 Parameter parameter = (Parameter) o;
53 return Objects.equals(dataType, parameter.dataType) && direction == parameter.direction;
54 }
55
56 @Override
57 public int hashCode() {
58 return Objects.hash(dataType, direction);
59 }
60}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java
new file mode 100644
index 00000000..cd0739be
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8public enum ParameterDirection {
9 OUT("@Out"),
10 IN("@In");
11
12 private final String name;
13
14 ParameterDirection(String name) {
15 this.name = name;
16 }
17
18 @Override
19 public String toString() {
20 return name;
21 }
22}
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..ab310556
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8public interface StatefulAggregate<R, T> {
9 void add(T value);
10
11 void remove(T value);
12
13 R getResult();
14
15 boolean isEmpty();
16
17 StatefulAggregate<R, T> deepCopy();
18
19 default boolean contains(T value) {
20 throw new UnsupportedOperationException();
21 }
22}
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..df746a90
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java
@@ -0,0 +1,28 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import java.util.stream.Stream;
9
10public interface StatefulAggregator<R, T> extends Aggregator<R, T> {
11 StatefulAggregate<R, T> createEmptyAggregate();
12
13 @Override
14 default R aggregateStream(Stream<T> stream) {
15 var accumulator = createEmptyAggregate();
16 var iterator = stream.iterator();
17 while (iterator.hasNext()) {
18 var value = iterator.next();
19 accumulator.add(value);
20 }
21 return accumulator.getResult();
22 }
23
24 @Override
25 default R getEmptyResult() {
26 return createEmptyAggregate().getResult();
27 }
28}
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..a094919e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java
@@ -0,0 +1,25 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import java.util.stream.Stream;
9
10public interface StatelessAggregator<R, T> extends Aggregator<R, T> {
11 R add(R current, T value);
12
13 R remove(R current, T value);
14
15 @Override
16 default R aggregateStream(Stream<T> stream) {
17 var accumulator = getEmptyResult();
18 var iterator = stream.iterator();
19 while (iterator.hasNext()) {
20 var value = iterator.next();
21 accumulator = add(accumulator, value);
22 }
23 return accumulator;
24 }
25}
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..e6818b88
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java
@@ -0,0 +1,26 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.literal.AssignLiteral;
9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.valuation.Valuation;
12
13public non-sealed interface Term<T> extends AnyTerm, AssignedValue<T> {
14 @Override
15 Class<T> getType();
16
17 T evaluate(Valuation valuation);
18
19 @Override
20 Term<T> substitute(Substitution substitution);
21
22 @Override
23 default Literal toLiteral(DataVariable<T> targetVariable) {
24 return new AssignLiteral<>(targetVariable, this);
25 }
26}
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..a46ebe31
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java
@@ -0,0 +1,79 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.valuation.Valuation;
11
12import java.util.Objects;
13import java.util.Set;
14
15public abstract class UnaryTerm<R, T> extends AbstractTerm<R> {
16 private final Class<T> bodyType;
17 private final Term<T> body;
18
19 protected UnaryTerm(Class<R> type, Class<T> bodyType, Term<T> body) {
20 super(type);
21 if (!body.getType().equals(bodyType)) {
22 throw new IllegalArgumentException("Expected body %s to be of type %s, got %s instead".formatted(body,
23 bodyType.getName(), body.getType().getName()));
24 }
25 this.bodyType = bodyType;
26 this.body = body;
27 }
28
29 public Class<T> getBodyType() {
30 return bodyType;
31 }
32
33 public Term<T> getBody() {
34 return body;
35 }
36
37 @Override
38 public R evaluate(Valuation valuation) {
39 var bodyValue = body.evaluate(valuation);
40 return bodyValue == null ? null : doEvaluate(bodyValue);
41 }
42
43 protected abstract R doEvaluate(T bodyValue);
44
45 @Override
46 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
47 if (!super.equalsWithSubstitution(helper, other)) {
48 return false;
49 }
50 var otherUnaryTerm = (UnaryTerm<?, ?>) other;
51 return bodyType.equals(otherUnaryTerm.bodyType) && body.equalsWithSubstitution(helper, otherUnaryTerm.body);
52 }
53
54 @Override
55 public Term<R> substitute(Substitution substitution) {
56 return doSubstitute(substitution, body.substitute(substitution));
57 }
58
59 protected abstract Term<R> doSubstitute(Substitution substitution, Term<T> substitutedBody);
60
61 @Override
62 public Set<AnyDataVariable> getInputVariables() {
63 return body.getInputVariables();
64 }
65
66 @Override
67 public boolean equals(Object o) {
68 if (this == o) return true;
69 if (o == null || getClass() != o.getClass()) return false;
70 if (!super.equals(o)) return false;
71 UnaryTerm<?, ?> unaryTerm = (UnaryTerm<?, ?>) o;
72 return Objects.equals(bodyType, unaryTerm.bodyType) && Objects.equals(body, unaryTerm.body);
73 }
74
75 @Override
76 public int hashCode() {
77 return Objects.hash(super.hashCode(), bodyType, body);
78 }
79}
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..a0268c8e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java
@@ -0,0 +1,84 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.dnf.DnfUtils;
10
11import java.util.Objects;
12import java.util.Optional;
13
14public abstract sealed class Variable permits AnyDataVariable, NodeVariable {
15 private final String explicitName;
16 private final String uniqueName;
17
18 protected Variable(String name) {
19 this.explicitName = name;
20 uniqueName = DnfUtils.generateUniqueName(name);
21 }
22
23 public abstract Optional<Class<?>> tryGetType();
24
25 public String getName() {
26 return explicitName == null ? uniqueName : explicitName;
27 }
28
29 protected String getExplicitName() {
30 return explicitName;
31 }
32
33 public boolean isExplicitlyNamed() {
34 return explicitName != null;
35 }
36
37 public String getUniqueName() {
38 return uniqueName;
39 }
40
41 public abstract boolean isUnifiable();
42
43 public abstract Variable renew(@Nullable String name);
44
45 public abstract Variable renew();
46
47 public abstract NodeVariable asNodeVariable();
48
49 public abstract <T> DataVariable<T> asDataVariable(Class<T> type);
50
51 @Override
52 public String toString() {
53 return getName();
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 Variable variable = (Variable) o;
61 return Objects.equals(uniqueName, variable.uniqueName);
62 }
63
64 @Override
65 public int hashCode() {
66 return Objects.hash(uniqueName);
67 }
68
69 public static NodeVariable of(@Nullable String name) {
70 return new NodeVariable(name);
71 }
72
73 public static NodeVariable of() {
74 return of((String) null);
75 }
76
77 public static <T> DataVariable<T> of(@Nullable String name, Class<T> type) {
78 return new DataVariable<>(name, type);
79 }
80
81 public static <T> DataVariable<T> of(Class<T> type) {
82 return of(null, type);
83 }
84}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolAndTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolAndTerm.java
new file mode 100644
index 00000000..f9e1c06f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolAndTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class BoolAndTerm extends BoolBinaryTerm {
12 public BoolAndTerm(Term<Boolean> left, Term<Boolean> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedLeft,
18 Term<Boolean> substitutedRight) {
19 return new BoolAndTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) {
24 return leftValue && rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s && %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolBinaryTerm.java
new file mode 100644
index 00000000..a85aa63a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolBinaryTerm.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.term.BinaryTerm;
9import tools.refinery.store.query.term.Term;
10
11public abstract class BoolBinaryTerm extends BinaryTerm<Boolean, Boolean, Boolean> {
12 protected BoolBinaryTerm(Term<Boolean> left, Term<Boolean> right) {
13 super(Boolean.class, Boolean.class, Boolean.class, left, right);
14 }
15}
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..8d3382b3
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.query.term.UnaryTerm;
11
12public class BoolNotTerm extends UnaryTerm<Boolean, Boolean> {
13 protected BoolNotTerm(Term<Boolean> body) {
14 super(Boolean.class, Boolean.class, body);
15 }
16
17 @Override
18 protected Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedBody) {
19 return new BoolNotTerm(substitutedBody);
20 }
21
22 @Override
23 protected Boolean doEvaluate(Boolean bodyValue) {
24 return !bodyValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(!%s)".formatted(getBody());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolOrTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolOrTerm.java
new file mode 100644
index 00000000..b5195d52
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolOrTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class BoolOrTerm extends BoolBinaryTerm {
12 public BoolOrTerm(Term<Boolean> left, Term<Boolean> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedLeft,
18 Term<Boolean> substitutedRight) {
19 return new BoolOrTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) {
24 return leftValue || rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s || %s)".formatted(getLeft(), getRight());
30 }
31}
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..fa54f686
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java
@@ -0,0 +1,35 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.term.ConstantTerm;
9import tools.refinery.store.query.term.Term;
10
11public final class BoolTerms {
12 private BoolTerms() {
13 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
14 }
15
16 public static Term<Boolean> constant(Boolean value) {
17 return new ConstantTerm<>(Boolean.class, value);
18 }
19
20 public static Term<Boolean> not(Term<Boolean> body) {
21 return new BoolNotTerm(body);
22 }
23
24 public static Term<Boolean> and(Term<Boolean> left, Term<Boolean> right) {
25 return new BoolAndTerm(left, right);
26 }
27
28 public static Term<Boolean> or(Term<Boolean> left, Term<Boolean> right) {
29 return new BoolOrTerm(left, right);
30 }
31
32 public static Term<Boolean> xor(Term<Boolean> left, Term<Boolean> right) {
33 return new BoolXorTerm(left, right);
34 }
35}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolXorTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolXorTerm.java
new file mode 100644
index 00000000..7478b8a5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolXorTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class BoolXorTerm extends BoolBinaryTerm {
12 public BoolXorTerm(Term<Boolean> left, Term<Boolean> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedLeft,
18 Term<Boolean> substitutedRight) {
19 return new BoolXorTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) {
24 return leftValue ^ rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s ^^ %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/ComparisonTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/ComparisonTerm.java
new file mode 100644
index 00000000..5ca5a0a1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/ComparisonTerm.java
@@ -0,0 +1,19 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.term.BinaryTerm;
9import tools.refinery.store.query.term.Term;
10
11public abstract class ComparisonTerm<T> extends BinaryTerm<Boolean, T, T> {
12 protected ComparisonTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(Boolean.class, argumentType, argumentType, left, right);
14 }
15
16 public Class<T> getArgumentType() {
17 return getLeftType();
18 }
19}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/EqTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/EqTerm.java
new file mode 100644
index 00000000..b8cf36f8
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/EqTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class EqTerm<T> extends ComparisonTerm<T> {
12 public EqTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return leftValue.equals(rightValue);
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new EqTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s == %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterEqTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterEqTerm.java
new file mode 100644
index 00000000..b109eb1a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterEqTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class GreaterEqTerm<T extends Comparable<T>> extends ComparisonTerm<T> {
12 public GreaterEqTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return leftValue.compareTo(rightValue) >= 0;
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new GreaterEqTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s >= %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterTerm.java
new file mode 100644
index 00000000..1b67f8b5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class GreaterTerm<T extends Comparable<T>> extends ComparisonTerm<T> {
12 public GreaterTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return leftValue.compareTo(rightValue) > 0;
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new GreaterTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s > %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessEqTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessEqTerm.java
new file mode 100644
index 00000000..1b34535f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessEqTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class LessEqTerm<T extends Comparable<T>> extends ComparisonTerm<T> {
12 public LessEqTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return leftValue.compareTo(rightValue) <= 0;
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new LessEqTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s <= %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessTerm.java
new file mode 100644
index 00000000..44e70902
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class LessTerm<T extends Comparable<T>> extends ComparisonTerm<T> {
12 public LessTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return leftValue.compareTo(rightValue) < 0;
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new LessTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s < %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/NotEqTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/NotEqTerm.java
new file mode 100644
index 00000000..1f9734c4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/NotEqTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class NotEqTerm<T> extends ComparisonTerm<T> {
12 public NotEqTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return !leftValue.equals(rightValue);
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new NotEqTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s != %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntAddTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntAddTerm.java
new file mode 100644
index 00000000..dbea3efc
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntAddTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntAddTerm extends IntBinaryTerm {
12 public IntAddTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntAddTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return leftValue + rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s + %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntBinaryTerm.java
new file mode 100644
index 00000000..27ced4e4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntBinaryTerm.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.term.BinaryTerm;
9import tools.refinery.store.query.term.Term;
10
11public abstract class IntBinaryTerm extends BinaryTerm<Integer, Integer, Integer> {
12 protected IntBinaryTerm(Term<Integer> left, Term<Integer> right) {
13 super(Integer.class, Integer.class, Integer.class, left, right);
14 }
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntDivTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntDivTerm.java
new file mode 100644
index 00000000..2a35058c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntDivTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntDivTerm extends IntBinaryTerm {
12 public IntDivTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntDivTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return rightValue == 0 ? null : leftValue / rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s / %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMaxTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMaxTerm.java
new file mode 100644
index 00000000..f81fc509
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMaxTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntMaxTerm extends IntBinaryTerm {
12 public IntMaxTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntMaxTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return Math.max(rightValue, leftValue);
25 }
26
27 @Override
28 public String toString() {
29 return "max(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinTerm.java
new file mode 100644
index 00000000..89182e26
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntMinTerm extends IntBinaryTerm {
12 public IntMinTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntMinTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return Math.min(rightValue, leftValue);
25 }
26
27 @Override
28 public String toString() {
29 return "min(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinusTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinusTerm.java
new file mode 100644
index 00000000..709aa5ba
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinusTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntMinusTerm extends IntUnaryTerm {
12 public IntMinusTerm(Term<Integer> body) {
13 super(body);
14 }
15
16 @Override
17 protected Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) {
18 return new IntMinusTerm(substitutedBody);
19 }
20
21 @Override
22 protected Integer doEvaluate(Integer bodyValue) {
23 return -bodyValue;
24 }
25
26 @Override
27 public String toString() {
28 return "(-%s)".formatted(getBody());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMulTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMulTerm.java
new file mode 100644
index 00000000..89d4c5f4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMulTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntMulTerm extends IntBinaryTerm {
12 public IntMulTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntMulTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return leftValue * rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s * %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPlusTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPlusTerm.java
new file mode 100644
index 00000000..aef83bb4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPlusTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntPlusTerm extends IntUnaryTerm {
12 public IntPlusTerm(Term<Integer> body) {
13 super(body);
14 }
15
16 @Override
17 protected Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) {
18 return new IntPlusTerm(substitutedBody);
19 }
20
21 @Override
22 protected Integer doEvaluate(Integer bodyValue) {
23 return bodyValue;
24 }
25
26 @Override
27 public String toString() {
28 return "(+%s)".formatted(getBody());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPowTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPowTerm.java
new file mode 100644
index 00000000..d5af97a1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPowTerm.java
@@ -0,0 +1,43 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntPowTerm extends IntBinaryTerm {
12 public IntPowTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntPowTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return rightValue < 0 ? null : power(leftValue, rightValue);
25 }
26
27 private static int power(int base, int exponent) {
28 int accum = 1;
29 while (exponent > 0) {
30 if (exponent % 2 == 1) {
31 accum = accum * base;
32 }
33 base = base * base;
34 exponent = exponent / 2;
35 }
36 return accum;
37 }
38
39 @Override
40 public String toString() {
41 return "(%s ** %s)".formatted(getLeft(), getRight());
42 }
43}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSubTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSubTerm.java
new file mode 100644
index 00000000..2c27afb1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSubTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntSubTerm extends IntBinaryTerm {
12 public IntSubTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntSubTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return leftValue - rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s - %s)".formatted(getLeft(), getRight());
30 }
31}
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..cd718c53
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java
@@ -0,0 +1,40 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.term.StatelessAggregator;
9
10public final class IntSumAggregator implements StatelessAggregator<Integer, Integer> {
11 public static final IntSumAggregator INSTANCE = new IntSumAggregator();
12
13 private IntSumAggregator() {
14 }
15
16 @Override
17 public Class<Integer> getResultType() {
18 return Integer.class;
19 }
20
21 @Override
22 public Class<Integer> getInputType() {
23 return Integer.class;
24 }
25
26 @Override
27 public Integer getEmptyResult() {
28 return 0;
29 }
30
31 @Override
32 public Integer add(Integer current, Integer value) {
33 return current + value;
34 }
35
36 @Override
37 public Integer remove(Integer current, Integer value) {
38 return current - value;
39 }
40}
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..acb98b94
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java
@@ -0,0 +1,94 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.term.Aggregator;
9import tools.refinery.store.query.term.ConstantTerm;
10import tools.refinery.store.query.term.ExtremeValueAggregator;
11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.comparable.*;
13
14import java.util.Comparator;
15
16public final class IntTerms {
17 public static final Aggregator<Integer, Integer> INT_SUM = IntSumAggregator.INSTANCE;
18 public static final Aggregator<Integer, Integer> INT_MIN = new ExtremeValueAggregator<>(Integer.class,
19 Integer.MAX_VALUE);
20 public static final Aggregator<Integer, Integer> INT_MAX = new ExtremeValueAggregator<>(Integer.class,
21 Integer.MIN_VALUE, Comparator.reverseOrder());
22
23 private IntTerms() {
24 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
25 }
26
27 public static Term<Integer> constant(Integer value) {
28 return new ConstantTerm<>(Integer.class, value);
29 }
30
31 public static Term<Integer> plus(Term<Integer> body) {
32 return new IntPlusTerm(body);
33 }
34
35 public static Term<Integer> minus(Term<Integer> body) {
36 return new IntMinusTerm(body);
37 }
38
39 public static Term<Integer> add(Term<Integer> left, Term<Integer> right) {
40 return new IntAddTerm(left, right);
41 }
42
43 public static Term<Integer> sub(Term<Integer> left, Term<Integer> right) {
44 return new IntSubTerm(left, right);
45 }
46
47 public static Term<Integer> mul(Term<Integer> left, Term<Integer> right) {
48 return new IntMulTerm(left, right);
49 }
50
51 public static Term<Integer> div(Term<Integer> left, Term<Integer> right) {
52 return new IntDivTerm(left, right);
53 }
54
55 public static Term<Integer> pow(Term<Integer> left, Term<Integer> right) {
56 return new IntPowTerm(left, right);
57 }
58
59 public static Term<Integer> min(Term<Integer> left, Term<Integer> right) {
60 return new IntMinTerm(left, right);
61 }
62
63 public static Term<Integer> max(Term<Integer> left, Term<Integer> right) {
64 return new IntMaxTerm(left, right);
65 }
66
67 public static Term<Boolean> eq(Term<Integer> left, Term<Integer> right) {
68 return new EqTerm<>(Integer.class, left, right);
69 }
70
71 public static Term<Boolean> notEq(Term<Integer> left, Term<Integer> right) {
72 return new NotEqTerm<>(Integer.class, left, right);
73 }
74
75 public static Term<Boolean> less(Term<Integer> left, Term<Integer> right) {
76 return new LessTerm<>(Integer.class, left, right);
77 }
78
79 public static Term<Boolean> lessEq(Term<Integer> left, Term<Integer> right) {
80 return new LessEqTerm<>(Integer.class, left, right);
81 }
82
83 public static Term<Boolean> greater(Term<Integer> left, Term<Integer> right) {
84 return new GreaterTerm<>(Integer.class, left, right);
85 }
86
87 public static Term<Boolean> greaterEq(Term<Integer> left, Term<Integer> right) {
88 return new GreaterEqTerm<>(Integer.class, left, right);
89 }
90
91 public static Term<Integer> asInt(Term<Double> body) {
92 return new RealToIntTerm(body);
93 }
94}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntUnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntUnaryTerm.java
new file mode 100644
index 00000000..49b4c647
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntUnaryTerm.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.term.Term;
9import tools.refinery.store.query.term.UnaryTerm;
10
11public abstract class IntUnaryTerm extends UnaryTerm<Integer, Integer> {
12 protected IntUnaryTerm(Term<Integer> body) {
13 super(Integer.class, Integer.class, body);
14 }
15}
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..7d383562
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.query.term.UnaryTerm;
11
12public class RealToIntTerm extends UnaryTerm<Integer, Double> {
13 protected RealToIntTerm(Term<Double> body) {
14 super(Integer.class, Double.class, body);
15 }
16
17 @Override
18 protected Integer doEvaluate(Double bodyValue) {
19 return bodyValue.isNaN() ? null : bodyValue.intValue();
20 }
21
22 @Override
23 protected Term<Integer> doSubstitute(Substitution substitution, Term<Double> substitutedBody) {
24 return new RealToIntTerm(substitutedBody);
25 }
26
27 @Override
28 public String toString() {
29 return "(%s as int)".formatted(getBody());
30 }
31}
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..2f53117a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.query.term.UnaryTerm;
11
12public class IntToRealTerm extends UnaryTerm<Double, Integer> {
13 protected IntToRealTerm(Term<Integer> body) {
14 super(Double.class, Integer.class, body);
15 }
16
17 @Override
18 protected Term<Double> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) {
19 return new IntToRealTerm(substitutedBody);
20 }
21
22 @Override
23 protected Double doEvaluate(Integer bodyValue) {
24 return bodyValue.doubleValue();
25 }
26
27 @Override
28 public String toString() {
29 return "(%s as real)".formatted(getBody());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealAddTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealAddTerm.java
new file mode 100644
index 00000000..33fc9e41
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealAddTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealAddTerm extends RealBinaryTerm {
12 public RealAddTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealAddTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return leftValue + rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s + %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealBinaryTerm.java
new file mode 100644
index 00000000..000f3623
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealBinaryTerm.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.term.BinaryTerm;
9import tools.refinery.store.query.term.Term;
10
11public abstract class RealBinaryTerm extends BinaryTerm<Double, Double, Double> {
12 protected RealBinaryTerm(Term<Double> left, Term<Double> right) {
13 super(Double.class, Double.class, Double.class, left, right);
14 }
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealDivTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealDivTerm.java
new file mode 100644
index 00000000..1e55bf42
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealDivTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealDivTerm extends RealBinaryTerm {
12 public RealDivTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealDivTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return leftValue / rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s / %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMaxTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMaxTerm.java
new file mode 100644
index 00000000..2a249496
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMaxTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealMaxTerm extends RealBinaryTerm {
12 public RealMaxTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealMaxTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return Math.max(leftValue, rightValue);
25 }
26
27 @Override
28 public String toString() {
29 return "max(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinTerm.java
new file mode 100644
index 00000000..2eb4cc1e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealMinTerm extends RealBinaryTerm {
12 public RealMinTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealMinTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return Math.min(leftValue, rightValue);
25 }
26
27 @Override
28 public String toString() {
29 return "min(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinusTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinusTerm.java
new file mode 100644
index 00000000..4afec7a1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinusTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealMinusTerm extends RealUnaryTerm {
12 public RealMinusTerm(Term<Double> body) {
13 super(body);
14 }
15
16 @Override
17 protected Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedBody) {
18 return new RealMinusTerm(substitutedBody);
19 }
20
21 @Override
22 protected Double doEvaluate(Double bodyValue) {
23 return -bodyValue;
24 }
25
26 @Override
27 public String toString() {
28 return "(-%s)".formatted(getBody());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMulTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMulTerm.java
new file mode 100644
index 00000000..ec95ac6f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMulTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealMulTerm extends RealBinaryTerm {
12 public RealMulTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealMulTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return leftValue * rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s * %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPlusTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPlusTerm.java
new file mode 100644
index 00000000..64dd2e70
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPlusTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealPlusTerm extends RealUnaryTerm {
12 public RealPlusTerm(Term<Double> body) {
13 super(body);
14 }
15
16 @Override
17 protected Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedBody) {
18 return new RealPlusTerm(substitutedBody);
19 }
20
21 @Override
22 protected Double doEvaluate(Double bodyValue) {
23 return bodyValue;
24 }
25
26 @Override
27 public String toString() {
28 return "(+%s)".formatted(getBody());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPowTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPowTerm.java
new file mode 100644
index 00000000..11c952ea
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPowTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealPowTerm extends RealBinaryTerm {
12 public RealPowTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealPowTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return Math.pow(leftValue, rightValue);
25 }
26
27 @Override
28 public String toString() {
29 return "(%s ** %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSubTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSubTerm.java
new file mode 100644
index 00000000..8cc701ed
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSubTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealSubTerm extends RealBinaryTerm {
12 public RealSubTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealSubTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return leftValue - rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s - %s)".formatted(getLeft(), getRight());
30 }
31}
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..d21048e9
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java
@@ -0,0 +1,90 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.term.StatefulAggregate;
9import tools.refinery.store.query.term.StatefulAggregator;
10
11import java.util.Map;
12import java.util.TreeMap;
13
14public final class RealSumAggregator implements StatefulAggregator<Double, Double> {
15 public static final RealSumAggregator INSTANCE = new RealSumAggregator();
16
17 private RealSumAggregator() {
18 }
19
20 @Override
21 public Class<Double> getResultType() {
22 return Double.class;
23 }
24
25 @Override
26 public Class<Double> getInputType() {
27 return Double.class;
28 }
29
30 @Override
31 public StatefulAggregate<Double, Double> createEmptyAggregate() {
32 return new Aggregate();
33 }
34
35 @Override
36 public Double getEmptyResult() {
37 return 0d;
38 }
39
40 private static class Aggregate implements StatefulAggregate<Double, Double> {
41 private final Map<Double, Integer> values;
42
43 public Aggregate() {
44 values = new TreeMap<>();
45 }
46
47 private Aggregate(Aggregate other) {
48 values = new TreeMap<>(other.values);
49 }
50
51 @Override
52 public void add(Double value) {
53 values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1);
54 }
55
56 @Override
57 public void remove(Double value) {
58 values.compute(value, (theValue, currentCount) -> {
59 if (currentCount == null || currentCount <= 0) {
60 throw new IllegalStateException("Invalid count %d for value %f".formatted(currentCount, theValue));
61 }
62 return currentCount.equals(1) ? null : currentCount - 1;
63 });
64 }
65
66 @Override
67 public Double getResult() {
68 return values.entrySet()
69 .stream()
70 .mapToDouble(entry -> entry.getKey() * entry.getValue())
71 .reduce(Double::sum)
72 .orElse(0d);
73 }
74
75 @Override
76 public boolean isEmpty() {
77 return values.isEmpty();
78 }
79
80 @Override
81 public StatefulAggregate<Double, Double> deepCopy() {
82 return new Aggregate(this);
83 }
84
85 @Override
86 public boolean contains(Double value) {
87 return values.containsKey(value);
88 }
89 }
90}
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..79220358
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java
@@ -0,0 +1,94 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.term.Aggregator;
9import tools.refinery.store.query.term.ConstantTerm;
10import tools.refinery.store.query.term.ExtremeValueAggregator;
11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.comparable.*;
13
14import java.util.Comparator;
15
16public final class RealTerms {
17 public static final Aggregator<Double, Double> REAL_SUM = RealSumAggregator.INSTANCE;
18 public static final Aggregator<Double, Double> REAL_MIN = new ExtremeValueAggregator<>(Double.class,
19 Double.POSITIVE_INFINITY);
20 public static final Aggregator<Double, Double> REAL_MAX = new ExtremeValueAggregator<>(Double.class,
21 Double.NEGATIVE_INFINITY, Comparator.reverseOrder());
22
23 private RealTerms() {
24 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
25 }
26
27 public static Term<Double> constant(Double value) {
28 return new ConstantTerm<>(Double.class, value);
29 }
30
31 public static Term<Double> plus(Term<Double> body) {
32 return new RealPlusTerm(body);
33 }
34
35 public static Term<Double> minus(Term<Double> body) {
36 return new RealMinusTerm(body);
37 }
38
39 public static Term<Double> add(Term<Double> left, Term<Double> right) {
40 return new RealAddTerm(left, right);
41 }
42
43 public static Term<Double> sub(Term<Double> left, Term<Double> right) {
44 return new RealSubTerm(left, right);
45 }
46
47 public static Term<Double> mul(Term<Double> left, Term<Double> right) {
48 return new RealMulTerm(left, right);
49 }
50
51 public static Term<Double> div(Term<Double> left, Term<Double> right) {
52 return new RealDivTerm(left, right);
53 }
54
55 public static Term<Double> pow(Term<Double> left, Term<Double> right) {
56 return new RealPowTerm(left, right);
57 }
58
59 public static Term<Double> min(Term<Double> left, Term<Double> right) {
60 return new RealMinTerm(left, right);
61 }
62
63 public static Term<Double> max(Term<Double> left, Term<Double> right) {
64 return new RealMaxTerm(left, right);
65 }
66
67 public static Term<Boolean> eq(Term<Double> left, Term<Double> right) {
68 return new EqTerm<>(Double.class, left, right);
69 }
70
71 public static Term<Boolean> notEq(Term<Double> left, Term<Double> right) {
72 return new NotEqTerm<>(Double.class, left, right);
73 }
74
75 public static Term<Boolean> less(Term<Double> left, Term<Double> right) {
76 return new LessTerm<>(Double.class, left, right);
77 }
78
79 public static Term<Boolean> lessEq(Term<Double> left, Term<Double> right) {
80 return new LessEqTerm<>(Double.class, left, right);
81 }
82
83 public static Term<Boolean> greater(Term<Double> left, Term<Double> right) {
84 return new GreaterTerm<>(Double.class, left, right);
85 }
86
87 public static Term<Boolean> greaterEq(Term<Double> left, Term<Double> right) {
88 return new GreaterEqTerm<>(Double.class, left, right);
89 }
90
91 public static Term<Double> asReal(Term<Integer> body) {
92 return new IntToRealTerm(body);
93 }
94}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealUnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealUnaryTerm.java
new file mode 100644
index 00000000..d41c4ed9
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealUnaryTerm.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.term.Term;
9import tools.refinery.store.query.term.UnaryTerm;
10
11public abstract class RealUnaryTerm extends UnaryTerm<Double, Double> {
12 protected RealUnaryTerm(Term<Double> body) {
13 super(Double.class, Double.class, body);
14 }
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityAddTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityAddTerm.java
new file mode 100644
index 00000000..68905f51
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityAddTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.representation.cardinality.UpperCardinality;
11
12public class UpperCardinalityAddTerm extends UpperCardinalityBinaryTerm {
13 protected UpperCardinalityAddTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) {
14 super(left, right);
15 }
16
17 @Override
18 protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) {
19 return leftValue.add(rightValue);
20 }
21
22 @Override
23 public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) {
24 return new UpperCardinalityAddTerm(substitutedLeft, substitutedRight);
25 }
26
27 @Override
28 public String toString() {
29 return "(%s + %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityBinaryTerm.java
new file mode 100644
index 00000000..0cf8fe44
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityBinaryTerm.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.term.BinaryTerm;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.representation.cardinality.UpperCardinality;
11
12public abstract class UpperCardinalityBinaryTerm extends BinaryTerm<UpperCardinality, UpperCardinality,
13 UpperCardinality> {
14 protected UpperCardinalityBinaryTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) {
15 super(UpperCardinality.class, UpperCardinality.class, UpperCardinality.class, left, right);
16 }
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMaxTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMaxTerm.java
new file mode 100644
index 00000000..ff75f64e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMaxTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.representation.cardinality.UpperCardinality;
11
12public class UpperCardinalityMaxTerm extends UpperCardinalityBinaryTerm {
13 protected UpperCardinalityMaxTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) {
14 super(left, right);
15 }
16
17 @Override
18 protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) {
19 return leftValue.max(rightValue);
20 }
21
22 @Override
23 public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) {
24 return new UpperCardinalityMaxTerm(substitutedLeft, substitutedRight);
25 }
26
27 @Override
28 public String toString() {
29 return "max(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMinTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMinTerm.java
new file mode 100644
index 00000000..1e89e9f4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMinTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.representation.cardinality.UpperCardinality;
11
12public class UpperCardinalityMinTerm extends UpperCardinalityBinaryTerm {
13 protected UpperCardinalityMinTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) {
14 super(left, right);
15 }
16
17 @Override
18 protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) {
19 return leftValue.min(rightValue);
20 }
21
22 @Override
23 public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) {
24 return new UpperCardinalityMinTerm(substitutedLeft, substitutedRight);
25 }
26
27 @Override
28 public String toString() {
29 return "min(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMulTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMulTerm.java
new file mode 100644
index 00000000..3b4970f4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMulTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.representation.cardinality.UpperCardinality;
11
12public class UpperCardinalityMulTerm extends UpperCardinalityBinaryTerm {
13 protected UpperCardinalityMulTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) {
14 super(left, right);
15 }
16
17 @Override
18 protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) {
19 return leftValue.multiply(rightValue);
20 }
21
22 @Override
23 public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) {
24 return new UpperCardinalityMulTerm(substitutedLeft, substitutedRight);
25 }
26
27 @Override
28 public String toString() {
29 return "(%s * %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java
new file mode 100644
index 00000000..5bbd3081
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java
@@ -0,0 +1,86 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.term.StatefulAggregate;
9import tools.refinery.store.query.term.StatefulAggregator;
10import tools.refinery.store.representation.cardinality.FiniteUpperCardinality;
11import tools.refinery.store.representation.cardinality.UnboundedUpperCardinality;
12import tools.refinery.store.representation.cardinality.UpperCardinalities;
13import tools.refinery.store.representation.cardinality.UpperCardinality;
14
15public class UpperCardinalitySumAggregator implements StatefulAggregator<UpperCardinality, UpperCardinality> {
16 public static final UpperCardinalitySumAggregator INSTANCE = new UpperCardinalitySumAggregator();
17
18 private UpperCardinalitySumAggregator() {
19 }
20
21 @Override
22 public Class<UpperCardinality> getResultType() {
23 return UpperCardinality.class;
24 }
25
26 @Override
27 public Class<UpperCardinality> getInputType() {
28 return UpperCardinality.class;
29 }
30
31 @Override
32 public StatefulAggregate<UpperCardinality, UpperCardinality> createEmptyAggregate() {
33 return new Aggregate();
34 }
35
36 private static class Aggregate implements StatefulAggregate<UpperCardinality, UpperCardinality> {
37 private int sumFiniteUpperBounds;
38 private int countUnbounded;
39
40 public Aggregate() {
41 this(0, 0);
42 }
43
44 private Aggregate(int sumFiniteUpperBounds, int countUnbounded) {
45 this.sumFiniteUpperBounds = sumFiniteUpperBounds;
46 this.countUnbounded = countUnbounded;
47 }
48
49 @Override
50 public void add(UpperCardinality value) {
51 if (value instanceof FiniteUpperCardinality finiteUpperCardinality) {
52 sumFiniteUpperBounds += finiteUpperCardinality.finiteUpperBound();
53 } else if (value instanceof UnboundedUpperCardinality) {
54 countUnbounded += 1;
55 } else {
56 throw new IllegalArgumentException("Unknown UpperCardinality: " + value);
57 }
58 }
59
60 @Override
61 public void remove(UpperCardinality value) {
62 if (value instanceof FiniteUpperCardinality finiteUpperCardinality) {
63 sumFiniteUpperBounds -= finiteUpperCardinality.finiteUpperBound();
64 } else if (value instanceof UnboundedUpperCardinality) {
65 countUnbounded -= 1;
66 } else {
67 throw new IllegalArgumentException("Unknown UpperCardinality: " + value);
68 }
69 }
70
71 @Override
72 public UpperCardinality getResult() {
73 return countUnbounded > 0 ? UpperCardinalities.UNBOUNDED : UpperCardinalities.valueOf(sumFiniteUpperBounds);
74 }
75
76 @Override
77 public boolean isEmpty() {
78 return sumFiniteUpperBounds == 0 && countUnbounded == 0;
79 }
80
81 @Override
82 public StatefulAggregate<UpperCardinality, UpperCardinality> deepCopy() {
83 return new Aggregate(sumFiniteUpperBounds, countUnbounded);
84 }
85 }
86}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTerms.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTerms.java
new file mode 100644
index 00000000..13914f2d
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTerms.java
@@ -0,0 +1,73 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.term.Aggregator;
9import tools.refinery.store.query.term.ConstantTerm;
10import tools.refinery.store.query.term.ExtremeValueAggregator;
11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.comparable.*;
13import tools.refinery.store.representation.cardinality.UpperCardinalities;
14import tools.refinery.store.representation.cardinality.UpperCardinality;
15
16import java.util.Comparator;
17
18public final class UpperCardinalityTerms {
19 public static final Aggregator<UpperCardinality, UpperCardinality> UPPER_CARDINALITY_SUM =
20 UpperCardinalitySumAggregator.INSTANCE;
21 public static final Aggregator<UpperCardinality, UpperCardinality> UPPER_CARDINALITY_MIN =
22 new ExtremeValueAggregator<>(UpperCardinality.class, UpperCardinalities.UNBOUNDED);
23 public static final Aggregator<UpperCardinality, UpperCardinality> UPPER_CARDINALITY_MAX =
24 new ExtremeValueAggregator<>(UpperCardinality.class, UpperCardinalities.ZERO, Comparator.reverseOrder());
25
26 private UpperCardinalityTerms() {
27 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
28 }
29
30 public static Term<UpperCardinality> constant(UpperCardinality value) {
31 return new ConstantTerm<>(UpperCardinality.class, value);
32 }
33
34 public static Term<UpperCardinality> add(Term<UpperCardinality> left, Term<UpperCardinality> right) {
35 return new UpperCardinalityAddTerm(left, right);
36 }
37
38 public static Term<UpperCardinality> mul(Term<UpperCardinality> left, Term<UpperCardinality> right) {
39 return new UpperCardinalityMulTerm(left, right);
40 }
41
42 public static Term<UpperCardinality> min(Term<UpperCardinality> left, Term<UpperCardinality> right) {
43 return new UpperCardinalityMinTerm(left, right);
44 }
45
46 public static Term<UpperCardinality> max(Term<UpperCardinality> left, Term<UpperCardinality> right) {
47 return new UpperCardinalityMaxTerm(left, right);
48 }
49
50 public static Term<Boolean> eq(Term<UpperCardinality> left, Term<UpperCardinality> right) {
51 return new EqTerm<>(UpperCardinality.class, left, right);
52 }
53
54 public static Term<Boolean> notEq(Term<UpperCardinality> left, Term<UpperCardinality> right) {
55 return new NotEqTerm<>(UpperCardinality.class, left, right);
56 }
57
58 public static Term<Boolean> less(Term<UpperCardinality> left, Term<UpperCardinality> right) {
59 return new LessTerm<>(UpperCardinality.class, left, right);
60 }
61
62 public static Term<Boolean> lessEq(Term<UpperCardinality> left, Term<UpperCardinality> right) {
63 return new LessEqTerm<>(UpperCardinality.class, left, right);
64 }
65
66 public static Term<Boolean> greater(Term<UpperCardinality> left, Term<UpperCardinality> right) {
67 return new GreaterTerm<>(UpperCardinality.class, left, right);
68 }
69
70 public static Term<Boolean> greaterEq(Term<UpperCardinality> left, Term<UpperCardinality> right) {
71 return new GreaterEqTerm<>(UpperCardinality.class, left, right);
72 }
73}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java
new file mode 100644
index 00000000..b568b99d
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java
@@ -0,0 +1,754 @@
1/*
2 * Copyright (c) 2021 Rodion Efremov
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
4 *
5 * SPDX-License-Identifier: MIT OR EPL-2.0
6 */
7package tools.refinery.store.query.utils;
8
9import java.util.*;
10
11/**
12 * This class implements an order statistic tree which is based on AVL-trees.
13 * <p>
14 * This class was copied into <i>Refinery</i> from
15 * <a href="https://github.com/coderodde/OrderStatisticTree/tree/546c343b9f5d868e394a079ff32691c9dbfd83e3">https://github.com/coderodde/OrderStatisticTree</a>
16 * and is available under the
17 * <a href="https://github.com/coderodde/OrderStatisticTree/blob/master/LICENSE">MIT License</a>.
18 * We also incorporated changes by <a href="https://github.com/coderodde/OrderStatisticTree/issues/3">Eugene Schava</a>
19 * and cleaned up some linter warnings.
20 *
21 * @param <T> the actual element type.
22 * @author Rodion "rodde" Efremov
23 * @version based on 1.6 (Feb 11, 2016)
24 */
25public class OrderStatisticTree<T extends Comparable<? super T>> implements Set<T> {
26
27 @Override
28 public Iterator<T> iterator() {
29 return new TreeIterator();
30 }
31
32 private final class TreeIterator implements Iterator<T> {
33
34 private Node<T> previousNode;
35 private Node<T> nextNode;
36 private int expectedModCount = modCount;
37
38 TreeIterator() {
39 if (root == null) {
40 nextNode = null;
41 } else {
42 nextNode = minimumNode(root);
43 }
44 }
45
46 @Override
47 public boolean hasNext() {
48 return nextNode != null;
49 }
50
51 @Override
52 public T next() {
53 if (nextNode == null) {
54 throw new NoSuchElementException("Iteration exceeded.");
55 }
56
57 checkConcurrentModification();
58 T datum = nextNode.key;
59 previousNode = nextNode;
60 nextNode = successorOf(nextNode);
61 return datum;
62 }
63
64 @Override
65 public void remove() {
66 if (previousNode == null) {
67 throw new IllegalStateException(
68 nextNode == null ?
69 "Not a single call to next(); nothing to remove." :
70 "Removing the same element twice."
71 );
72 }
73
74 checkConcurrentModification();
75
76 Node<T> x = deleteNode(previousNode);
77 fixAfterModification(x, false);
78
79 if (x == nextNode) {
80 nextNode = previousNode;
81 }
82
83 expectedModCount = ++modCount;
84 size--;
85 previousNode = null;
86 }
87
88 private void checkConcurrentModification() {
89 if (expectedModCount != modCount) {
90 throw new ConcurrentModificationException(
91 "The set was modified while iterating.");
92 }
93 }
94
95 private Node<T> successorOf(Node<T> node) {
96 if (node.right != null) {
97 node = node.right;
98
99 while (node.left != null) {
100 node = node.left;
101 }
102
103 return node;
104 }
105
106 Node<T> parent = node.parent;
107
108 while (parent != null && parent.right == node) {
109 node = parent;
110 parent = parent.parent;
111 }
112
113 return parent;
114 }
115 }
116
117 @Override
118 public Object[] toArray() {
119 Object[] array = new Object[size];
120 Iterator<T> iterator = iterator();
121 int index = 0;
122
123 while (iterator.hasNext()) {
124 array[index++] = iterator.next();
125 }
126
127 return array;
128 }
129
130 @Override
131 public <U> U[] toArray(U[] a) {
132 Iterator<T> iterator = iterator();
133
134 if (size > a.length) {
135 a = Arrays.copyOf(a, size);
136 }
137
138 int index = 0;
139
140 for (; index < size; ++index) {
141 @SuppressWarnings("unchecked")
142 var convertedValue = (U) iterator.next();
143 a[index] = convertedValue;
144 }
145
146 if (index < a.length) {
147 a[index] = null;
148 }
149
150 return a;
151 }
152
153 @Override
154 public boolean containsAll(Collection<?> c) {
155 for (Object element : c) {
156 if (!contains(element)) {
157 return false;
158 }
159 }
160
161 return true;
162 }
163
164 @Override
165 public boolean addAll(Collection<? extends T> c) {
166 boolean modified = false;
167
168 for (T element : c) {
169 if (add(element)) {
170 modified = true;
171 }
172 }
173
174 return modified;
175 }
176
177 @Override
178 public boolean retainAll(Collection<?> c) {
179 if (!c.getClass().equals(HashSet.class)) {
180 c = new HashSet<>(c);
181 }
182
183 Iterator<T> iterator = iterator();
184 boolean modified = false;
185
186 while (iterator.hasNext()) {
187 T element = iterator.next();
188
189 if (!c.contains(element)) {
190 iterator.remove();
191 modified = true;
192 }
193 }
194
195 return modified;
196 }
197
198 @Override
199 public boolean removeAll(Collection<?> c) {
200 boolean modified = false;
201
202 for (Object element : c) {
203 if (remove(element)) {
204 modified = true;
205 }
206 }
207
208 return modified;
209 }
210
211 private static final class Node<T> {
212 T key;
213
214 Node<T> parent;
215 Node<T> left;
216 Node<T> right;
217
218 int height;
219 int count;
220
221 Node(T key) {
222 this.key = key;
223 }
224 }
225
226 private Node<T> root;
227 private int size;
228 private int modCount;
229
230 @Override
231 public boolean add(T element) {
232 Objects.requireNonNull(element, "The input element is null.");
233
234 if (root == null) {
235 root = new Node<>(element);
236 size = 1;
237 modCount++;
238 return true;
239 }
240
241 Node<T> parent = null;
242 Node<T> node = root;
243 int cmp;
244
245 while (node != null) {
246 cmp = element.compareTo(node.key);
247
248 if (cmp == 0) {
249 // The element is already in this tree.
250 return false;
251 }
252
253 parent = node;
254
255 if (cmp < 0) {
256 node = node.left;
257 } else {
258 node = node.right;
259 }
260 }
261
262 Node<T> newnode = new Node<>(element);
263
264 if (element.compareTo(parent.key) < 0) {
265 parent.left = newnode;
266 } else {
267 parent.right = newnode;
268 }
269
270 newnode.parent = parent;
271 size++;
272 modCount++;
273 Node<T> hi = parent;
274 Node<T> lo = newnode;
275
276 while (hi != null) {
277 if (hi.left == lo) {
278 hi.count++;
279 }
280
281 lo = hi;
282 hi = hi.parent;
283 }
284
285 fixAfterModification(newnode, true);
286 return true;
287 }
288
289 @Override
290 public boolean contains(Object o) {
291 @SuppressWarnings("unchecked")
292 T element = (T) o;
293 Node<T> x = root;
294 int cmp;
295
296 while (x != null && (cmp = element.compareTo(x.key)) != 0) {
297 if (cmp < 0) {
298 x = x.left;
299 } else {
300 x = x.right;
301 }
302 }
303
304 return x != null;
305 }
306
307 @Override
308 public boolean remove(Object o) {
309 @SuppressWarnings("unchecked")
310 T element = (T) o;
311 Node<T> x = root;
312 int cmp;
313
314 while (x != null && (cmp = element.compareTo(x.key)) != 0) {
315 if (cmp < 0) {
316 x = x.left;
317 } else {
318 x = x.right;
319 }
320 }
321
322 if (x == null) {
323 return false;
324 }
325
326 x = deleteNode(x);
327 fixAfterModification(x, false);
328 size--;
329 modCount++;
330 return true;
331 }
332
333 public T get(int index) {
334 checkIndex(index);
335 Node<T> node = root;
336
337 while (true) {
338 if (index > node.count) {
339 index -= node.count + 1;
340 node = node.right;
341 } else if (index < node.count) {
342 node = node.left;
343 } else {
344 return node.key;
345 }
346 }
347 }
348
349 public int indexOf(T element) {
350 Node<T> node = root;
351
352 if (root == null) {
353 return -1;
354 }
355
356 int rank = root.count;
357 int cmp;
358
359 while (true) {
360 if ((cmp = element.compareTo(node.key)) < 0) {
361 if (node.left == null) {
362 return -1;
363 }
364
365 rank -= (node.count - node.left.count);
366 node = node.left;
367 } else if (cmp > 0) {
368 if (node.right == null) {
369 return -1;
370 }
371
372 rank += 1 + node.right.count;
373 node = node.right;
374 } else {
375 return rank;
376 }
377 }
378 }
379
380 @Override
381 public int size() {
382 return size;
383 }
384
385 @Override
386 public boolean isEmpty() {
387 return size == 0;
388 }
389
390 @Override
391 public void clear() {
392 modCount += size;
393 root = null;
394 size = 0;
395 }
396
397
398 private void checkIndex(int index) {
399 if (index < 0) {
400 throw new IndexOutOfBoundsException(
401 "The input index is negative: " + index);
402 }
403
404 if (index >= size) {
405 throw new IndexOutOfBoundsException(
406 "The input index is too large: " + index +
407 ", the size of this tree is " + size);
408 }
409 }
410
411 @SuppressWarnings("squid:S3776")
412 private Node<T> deleteNode(Node<T> node) {
413 if (node.left == null && node.right == null) {
414 // 'node' has no children.
415 Node<T> parent = node.parent;
416
417 if (parent == null) {
418 // 'node' is the root node of this tree.
419 root = null;
420 ++modCount;
421 return node;
422 }
423
424 Node<T> lo = node;
425 Node<T> hi = parent;
426
427 while (hi != null) {
428 if (hi.left == lo) {
429 hi.count--;
430 }
431
432 lo = hi;
433 hi = hi.parent;
434 }
435
436 if (node == parent.left) {
437 parent.left = null;
438 } else {
439 parent.right = null;
440 }
441
442 return node;
443 }
444
445 if (node.left != null && node.right != null) {
446 // 'node' has both children.
447 Node<T> successor = minimumNode(node.right);
448 node.key = successor.key;
449 Node<T> child = successor.right;
450 Node<T> parent = successor.parent;
451
452 if (parent.left == successor) {
453 parent.left = child;
454 } else {
455 parent.right = child;
456 }
457
458 if (child != null) {
459 child.parent = parent;
460 }
461
462 Node<T> lo = child;
463 Node<T> hi = parent;
464
465 while (hi != null) {
466 if (hi.left == lo) {
467 hi.count--;
468 }
469
470 lo = hi;
471 hi = hi.parent;
472 }
473
474 return successor;
475 }
476
477 Node<T> child;
478
479 // 'node' has only one child.
480 if (node.left != null) {
481 child = node.left;
482 } else {
483 child = node.right;
484 }
485
486 Node<T> parent = node.parent;
487 child.parent = parent;
488
489 if (parent == null) {
490 root = child;
491 return node;
492 }
493
494 if (node == parent.left) {
495 parent.left = child;
496 } else {
497 parent.right = child;
498 }
499
500 Node<T> hi = parent;
501 Node<T> lo = child;
502
503 while (hi != null) {
504 if (hi.left == lo) {
505 hi.count--;
506 }
507
508 lo = hi;
509 hi = hi.parent;
510 }
511
512 return node;
513
514 }
515
516 private Node<T> minimumNode(Node<T> node) {
517 while (node.left != null) {
518 node = node.left;
519 }
520
521 return node;
522 }
523
524 private int height(Node<T> node) {
525 return node == null ? -1 : node.height;
526 }
527
528 private Node<T> leftRotate(Node<T> node1) {
529 Node<T> node2 = node1.right;
530 node2.parent = node1.parent;
531 node1.parent = node2;
532 node1.right = node2.left;
533 node2.left = node1;
534
535 if (node1.right != null) {
536 node1.right.parent = node1;
537 }
538
539 node1.height = Math.max(height(node1.left), height(node1.right)) + 1;
540 node2.height = Math.max(height(node2.left), height(node2.right)) + 1;
541 node2.count += node1.count + 1;
542 return node2;
543 }
544
545 private Node<T> rightRotate(Node<T> node1) {
546 Node<T> node2 = node1.left;
547 node2.parent = node1.parent;
548 node1.parent = node2;
549 node1.left = node2.right;
550 node2.right = node1;
551
552 if (node1.left != null) {
553 node1.left.parent = node1;
554 }
555
556 node1.height = Math.max(height(node1.left), height(node1.right)) + 1;
557 node2.height = Math.max(height(node2.left), height(node2.right)) + 1;
558 node1.count -= node2.count + 1;
559 return node2;
560 }
561
562 private Node<T> rightLeftRotate(Node<T> node1) {
563 Node<T> node2 = node1.right;
564 node1.right = rightRotate(node2);
565 return leftRotate(node1);
566 }
567
568 private Node<T> leftRightRotate(Node<T> node1) {
569 Node<T> node2 = node1.left;
570 node1.left = leftRotate(node2);
571 return rightRotate(node1);
572 }
573
574 // Fixing an insertion: use insertionMode = true.
575 // Fixing a deletion: use insertionMode = false.
576 @SuppressWarnings("squid:S3776")
577 private void fixAfterModification(Node<T> node, boolean insertionMode) {
578 Node<T> parent = node.parent;
579 Node<T> grandParent;
580 Node<T> subTree;
581
582 while (parent != null) {
583 if (height(parent.left) == height(parent.right) + 2) {
584 grandParent = parent.parent;
585
586 if (height(parent.left.left) >= height(parent.left.right)) {
587 subTree = rightRotate(parent);
588 } else {
589 subTree = leftRightRotate(parent);
590 }
591
592 if (grandParent == null) {
593 root = subTree;
594 } else if (grandParent.left == parent) {
595 grandParent.left = subTree;
596 } else {
597 grandParent.right = subTree;
598 }
599
600 if (grandParent != null) {
601 grandParent.height = Math.max(
602 height(grandParent.left),
603 height(grandParent.right)) + 1;
604 }
605
606 if (insertionMode) {
607 // Whenever fixing after insertion, at most one rotation is
608 // required in order to maintain the balance.
609 return;
610 }
611 } else if (height(parent.right) == height(parent.left) + 2) {
612 grandParent = parent.parent;
613
614 if (height(parent.right.right) >= height(parent.right.left)) {
615 subTree = leftRotate(parent);
616 } else {
617 subTree = rightLeftRotate(parent);
618 }
619
620 if (grandParent == null) {
621 root = subTree;
622 } else if (grandParent.left == parent) {
623 grandParent.left = subTree;
624 } else {
625 grandParent.right = subTree;
626 }
627
628 if (grandParent != null) {
629 grandParent.height =
630 Math.max(height(grandParent.left),
631 height(grandParent.right)) + 1;
632 }
633
634 if (insertionMode) {
635 return;
636 }
637 }
638
639 parent.height = Math.max(height(parent.left),
640 height(parent.right)) + 1;
641 parent = parent.parent;
642 }
643 }
644
645 boolean isHealthy() {
646 if (root == null) {
647 return true;
648 }
649
650 return !containsCycles()
651 && heightsAreCorrect()
652 && isBalanced()
653 && isWellIndexed();
654 }
655
656 private boolean containsCycles() {
657 Set<Node<T>> visitedNodes = new HashSet<>();
658 return containsCycles(root, visitedNodes);
659 }
660
661 private boolean containsCycles(Node<T> current, Set<Node<T>> visitedNodes) {
662 if (current == null) {
663 return false;
664 }
665
666 if (visitedNodes.contains(current)) {
667 return true;
668 }
669
670 visitedNodes.add(current);
671
672 return containsCycles(current.left, visitedNodes)
673 || containsCycles(current.right, visitedNodes);
674 }
675
676 private boolean heightsAreCorrect() {
677 return getHeight(root) == root.height;
678 }
679
680 private int getHeight(Node<T> node) {
681 if (node == null) {
682 return -1;
683 }
684
685 int leftTreeHeight = getHeight(node.left);
686
687 if (leftTreeHeight == Integer.MIN_VALUE) {
688 return Integer.MIN_VALUE;
689 }
690
691 int rightTreeHeight = getHeight(node.right);
692
693 if (rightTreeHeight == Integer.MIN_VALUE) {
694 return Integer.MIN_VALUE;
695 }
696
697 if (node.height == Math.max(leftTreeHeight, rightTreeHeight) + 1) {
698 return node.height;
699 }
700
701 return Integer.MIN_VALUE;
702 }
703
704 private boolean isBalanced() {
705 return isBalanced(root);
706 }
707
708 private boolean isBalanced(Node<T> node) {
709 if (node == null) {
710 return true;
711 }
712
713 if (!isBalanced(node.left)) {
714 return false;
715 }
716
717 if (!isBalanced(node.right)) {
718 return false;
719 }
720
721 int leftHeight = height(node.left);
722 int rightHeight = height(node.right);
723
724 return Math.abs(leftHeight - rightHeight) < 2;
725 }
726
727 private boolean isWellIndexed() {
728 return size == count(root);
729 }
730
731 private int count(Node<T> node) {
732 if (node == null) {
733 return 0;
734 }
735
736 int leftTreeSize = count(node.left);
737
738 if (leftTreeSize == Integer.MIN_VALUE) {
739 return Integer.MIN_VALUE;
740 }
741
742 if (node.count != leftTreeSize) {
743 return Integer.MIN_VALUE;
744 }
745
746 int rightTreeSize = count(node.right);
747
748 if (rightTreeSize == Integer.MIN_VALUE) {
749 return Integer.MIN_VALUE;
750 }
751
752 return leftTreeSize + 1 + rightTreeSize;
753 }
754}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/MapBasedValuation.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/MapBasedValuation.java
new file mode 100644
index 00000000..261ceaa5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/MapBasedValuation.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.valuation;
7
8import tools.refinery.store.query.term.AnyDataVariable;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Map;
12
13record MapBasedValuation(Map<AnyDataVariable, Object> values) implements Valuation {
14 @Override
15 public <T> T getValue(DataVariable<T> variable) {
16 if (!values.containsKey(variable)) {
17 throw new IllegalArgumentException("No value for variable %s".formatted(variable));
18 }
19 var value = values.get(variable);
20 return variable.getType().cast(value);
21 }
22}
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..fc8406aa
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.valuation;
7
8import tools.refinery.store.query.term.AnyDataVariable;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Set;
12
13public record RestrictedValuation(Valuation valuation, Set<AnyDataVariable> allowedVariables) implements Valuation {
14 @Override
15 public <T> T getValue(DataVariable<T> variable) {
16 if (!allowedVariables.contains(variable)) {
17 throw new IllegalArgumentException("Variable %s is not in scope".formatted(variable));
18 }
19 return valuation.getValue(variable);
20 }
21}
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..1c14112c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.valuation;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.DataVariable;
10
11public record SubstitutedValuation(Valuation originalValuation, Substitution substitution) implements Valuation {
12 @Override
13 public <T> T getValue(DataVariable<T> variable) {
14 return originalValuation.getValue(substitution.getTypeSafeSubstitute(variable));
15 }
16}
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..1588e957
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java
@@ -0,0 +1,37 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.valuation;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.AnyDataVariable;
11import tools.refinery.store.query.term.DataVariable;
12
13import java.util.Map;
14import java.util.Set;
15
16public interface Valuation {
17 <T> T getValue(DataVariable<T> variable);
18
19 default Valuation substitute(@Nullable Substitution substitution) {
20 if (substitution == null) {
21 return this;
22 }
23 return new SubstitutedValuation(this, substitution);
24 }
25
26 default Valuation restrict(Set<? extends AnyDataVariable> allowedVariables) {
27 return new RestrictedValuation(this, Set.copyOf(allowedVariables));
28 }
29
30 static ValuationBuilder builder() {
31 return new ValuationBuilder();
32 }
33
34 static Valuation empty() {
35 return new MapBasedValuation(Map.of());
36 }
37}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/ValuationBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/ValuationBuilder.java
new file mode 100644
index 00000000..7337dbc3
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/ValuationBuilder.java
@@ -0,0 +1,40 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.valuation;
7
8import tools.refinery.store.query.term.AnyDataVariable;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Collections;
12import java.util.HashMap;
13import java.util.Map;
14
15public class ValuationBuilder {
16 private final Map<AnyDataVariable, Object> values = new HashMap<>();
17
18 ValuationBuilder() {
19 }
20
21 public <T> ValuationBuilder put(DataVariable<T> variable, T value) {
22 return putChecked(variable, value);
23 }
24
25 public ValuationBuilder putChecked(AnyDataVariable variable, Object value) {
26 if (value != null && !variable.getType().isInstance(value)) {
27 throw new IllegalArgumentException("Value %s is not an instance of %s"
28 .formatted(value, variable.getType().getName()));
29 }
30 if (values.containsKey(variable)) {
31 throw new IllegalArgumentException("Already has value for variable %s".formatted(variable));
32 }
33 values.put(variable, value);
34 return this;
35 }
36
37 public Valuation build() {
38 return new MapBasedValuation(Collections.unmodifiableMap(values));
39 }
40}
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
new file mode 100644
index 00000000..fd37604e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java
@@ -0,0 +1,110 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.dnf.FunctionalDependency;
10import tools.refinery.store.query.term.Parameter;
11import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.tuple.Tuple;
13import tools.refinery.store.tuple.Tuple1;
14
15import java.util.Arrays;
16import java.util.List;
17import java.util.Objects;
18import java.util.Set;
19import java.util.stream.Collectors;
20import java.util.stream.IntStream;
21
22public abstract class AbstractFunctionView<T> extends SymbolView<T> {
23 private final List<Parameter> parameters;
24
25 protected AbstractFunctionView(Symbol<T> symbol, String name, Parameter outParameter) {
26 super(symbol, name);
27 parameters = createParameters(symbol.arity(), outParameter);
28 }
29
30 @Override
31 public Set<FunctionalDependency<Integer>> getFunctionalDependencies() {
32 var arity = getSymbol().arity();
33 var forEach = IntStream.range(0, arity).boxed().collect(Collectors.toUnmodifiableSet());
34 var unique = Set.of(arity);
35 return Set.of(new FunctionalDependency<>(forEach, unique));
36 }
37
38 @Override
39 public Set<ViewImplication> getImpliedRelationViews() {
40 var symbol = getSymbol();
41 var impliedIndices = IntStream.range(0, symbol.arity()).boxed().toList();
42 var keysView = new KeyOnlyView<>(symbol);
43 return Set.of(new ViewImplication(this, keysView, impliedIndices));
44 }
45
46 @Override
47 protected boolean doFilter(Tuple key, T value) {
48 return true;
49 }
50
51 protected Object forwardMapValue(T value) {
52 return value;
53 }
54
55 protected boolean valueEquals(T value, Object otherForwardMappedValue) {
56 return Objects.equals(otherForwardMappedValue, forwardMapValue(value));
57 }
58
59 @Override
60 public Object[] forwardMap(Tuple key, T value) {
61 int size = key.getSize();
62 Object[] result = new Object[size + 1];
63 for (int i = 0; i < size; i++) {
64 result[i] = Tuple.of(key.get(i));
65 }
66 result[key.getSize()] = forwardMapValue(value);
67 return result;
68 }
69
70 @Override
71 public boolean get(Model model, Object[] tuple) {
72 int[] content = new int[tuple.length - 1];
73 for (int i = 0; i < tuple.length - 1; i++) {
74 if (!(tuple[i] instanceof Tuple1 wrapper)) {
75 return false;
76 }
77 content[i] = wrapper.value0();
78 }
79 Tuple key = Tuple.of(content);
80 var valueInTuple = tuple[tuple.length - 1];
81 T valueInMap = model.getInterpretation(getSymbol()).get(key);
82 return valueEquals(valueInMap, valueInTuple);
83 }
84
85 @Override
86 public List<Parameter> getParameters() {
87 return parameters;
88 }
89
90 @Override
91 public boolean equals(Object o) {
92 if (this == o) return true;
93 if (o == null || getClass() != o.getClass()) return false;
94 if (!super.equals(o)) return false;
95 AbstractFunctionView<?> that = (AbstractFunctionView<?>) o;
96 return Objects.equals(parameters, that.parameters);
97 }
98
99 @Override
100 public int hashCode() {
101 return Objects.hash(super.hashCode(), parameters);
102 }
103
104 private static List<Parameter> createParameters(int symbolArity, Parameter outParameter) {
105 var parameters = new Parameter[symbolArity + 1];
106 Arrays.fill(parameters, Parameter.NODE_OUT);
107 parameters[symbolArity] = outParameter;
108 return List.of(parameters);
109 }
110}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java
new file mode 100644
index 00000000..90b27ebb
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.dnf.FunctionalDependency;
10import tools.refinery.store.representation.AnySymbol;
11import tools.refinery.store.query.Constraint;
12
13import java.util.Set;
14
15public sealed interface AnySymbolView extends Constraint permits SymbolView {
16 AnySymbol getSymbol();
17
18 String getViewName();
19
20 default Set<FunctionalDependency<Integer>> getFunctionalDependencies() {
21 return Set.of();
22 }
23
24 default Set<ViewImplication> getImpliedRelationViews() {
25 return Set.of();
26 }
27
28 boolean get(Model model, Object[] tuple);
29
30 Iterable<Object[]> getAll(Model model);
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java
new file mode 100644
index 00000000..abae6e5c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java
@@ -0,0 +1,73 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.tuple.Tuple;
9import tools.refinery.store.representation.Symbol;
10
11import java.util.Objects;
12import java.util.function.BiPredicate;
13import java.util.function.Predicate;
14
15public class FilteredView<T> extends TuplePreservingView<T> {
16 private final BiPredicate<Tuple, T> predicate;
17
18 public FilteredView(Symbol<T> symbol, String name, BiPredicate<Tuple, T> predicate) {
19 super(symbol, name);
20 this.predicate = predicate;
21 }
22
23 public FilteredView(Symbol<T> symbol, BiPredicate<Tuple, T> predicate) {
24 super(symbol);
25 this.predicate = predicate;
26 }
27
28 public FilteredView(Symbol<T> symbol, String name, Predicate<T> predicate) {
29 this(symbol, name, (k, v) -> predicate.test(v));
30 validateDefaultValue(predicate);
31 }
32
33 public FilteredView(Symbol<T> symbol, Predicate<T> predicate) {
34 this(symbol, (k, v) -> predicate.test(v));
35 validateDefaultValue(predicate);
36 }
37
38 @Override
39 protected boolean doFilter(Tuple key, T value) {
40 return this.predicate.test(key, value);
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 FilteredView<?> that = (FilteredView<?>) o;
49 return Objects.equals(predicate, that.predicate);
50 }
51
52 @Override
53 public int hashCode() {
54 return Objects.hash(super.hashCode(), predicate);
55 }
56
57 private void validateDefaultValue(Predicate<T> predicate) {
58 var defaultValue = getSymbol().defaultValue();
59 boolean matchesDefaultValue = false;
60 try {
61 matchesDefaultValue = predicate.test(defaultValue);
62 } catch (NullPointerException e) {
63 if (defaultValue != null) {
64 throw e;
65 }
66 // The predicate doesn't need to handle the default value if it is null.
67 }
68 if (matchesDefaultValue) {
69 throw new IllegalArgumentException("Tuples with default value %s cannot be enumerated in %s"
70 .formatted(defaultValue, getSymbol()));
71 }
72 }
73}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenView.java
new file mode 100644
index 00000000..c312330e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenView.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.representation.Symbol;
9import tools.refinery.store.representation.TruthValue;
10import tools.refinery.store.tuple.Tuple;
11
12public class ForbiddenView extends TuplePreservingView<TruthValue> {
13 public ForbiddenView(Symbol<TruthValue> symbol) {
14 super(symbol, "forbidden");
15 }
16
17 @Override
18 protected boolean doFilter(Tuple key, TruthValue value) {
19 return !value.may();
20 }
21}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java
new file mode 100644
index 00000000..74a5be07
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java
@@ -0,0 +1,36 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.query.term.*;
9import tools.refinery.store.representation.Symbol;
10
11import java.util.ArrayList;
12import java.util.List;
13
14public final class FunctionView<T> extends AbstractFunctionView<T> {
15 public FunctionView(Symbol<T> symbol, String name) {
16 super(symbol, name, new Parameter(symbol.valueType(), ParameterDirection.OUT));
17 }
18
19 public FunctionView(Symbol<T> symbol) {
20 this(symbol, "function");
21 }
22
23 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, List<NodeVariable> arguments) {
24 return targetVariable -> {
25 var placeholderVariable = Variable.of(getSymbol().valueType());
26 var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1);
27 argumentsWithPlaceholder.addAll(arguments);
28 argumentsWithPlaceholder.add(placeholderVariable);
29 return aggregateBy(placeholderVariable, aggregator, argumentsWithPlaceholder).toLiteral(targetVariable);
30 };
31 }
32
33 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, NodeVariable... arguments) {
34 return aggregate(aggregator, List.of(arguments));
35 }
36}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyView.java
new file mode 100644
index 00000000..f0e4a61e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyView.java
@@ -0,0 +1,41 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.representation.Symbol;
9import tools.refinery.store.tuple.Tuple;
10
11import java.util.Objects;
12
13public final class KeyOnlyView<T> extends TuplePreservingView<T> {
14 public static final String VIEW_NAME = "key";
15
16 private final T defaultValue;
17
18 public KeyOnlyView(Symbol<T> symbol) {
19 super(symbol, VIEW_NAME);
20 defaultValue = symbol.defaultValue();
21 }
22
23 @Override
24 protected boolean doFilter(Tuple key, T value) {
25 return true;
26 }
27
28 @Override
29 public boolean equals(Object o) {
30 if (this == o) return true;
31 if (o == null || getClass() != o.getClass()) return false;
32 if (!super.equals(o)) return false;
33 KeyOnlyView<?> that = (KeyOnlyView<?>) o;
34 return Objects.equals(defaultValue, that.defaultValue);
35 }
36
37 @Override
38 public int hashCode() {
39 return Objects.hash(super.hashCode(), defaultValue);
40 }
41}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayView.java
new file mode 100644
index 00000000..c322e220
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayView.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.representation.Symbol;
9import tools.refinery.store.representation.TruthValue;
10import tools.refinery.store.tuple.Tuple;
11
12public class MayView extends TuplePreservingView<TruthValue> {
13 public MayView(Symbol<TruthValue> symbol) {
14 super(symbol, "may");
15 }
16
17 @Override
18 protected boolean doFilter(Tuple key, TruthValue value) {
19 return value.may();
20 }
21}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustView.java
new file mode 100644
index 00000000..65bb4e4c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustView.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.representation.Symbol;
9import tools.refinery.store.representation.TruthValue;
10import tools.refinery.store.tuple.Tuple;
11
12public class MustView extends TuplePreservingView<TruthValue> {
13 public MustView(Symbol<TruthValue> symbol) {
14 super(symbol, "must");
15 }
16
17 @Override
18 protected boolean doFilter(Tuple key, TruthValue value) {
19 return value.must();
20 }
21}
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
new file mode 100644
index 00000000..fcf11506
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/NodeFunctionView.java
@@ -0,0 +1,20 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.query.term.Parameter;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple1;
11
12public final class NodeFunctionView extends AbstractFunctionView<Tuple1> {
13 public NodeFunctionView(Symbol<Tuple1> symbol, String name) {
14 super(symbol, name, Parameter.NODE_OUT);
15 }
16
17 public NodeFunctionView(Symbol<Tuple1> symbol) {
18 this(symbol, "function");
19 }
20}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/SymbolView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/SymbolView.java
new file mode 100644
index 00000000..cd8bd56b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/SymbolView.java
@@ -0,0 +1,85 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.map.CursorAsIterator;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple;
12
13import java.util.Objects;
14import java.util.UUID;
15
16/**
17 * Represents a view of a {@link Symbol} that can be queried.
18 *
19 * @param <T>
20 * @author Oszkar Semerath
21 */
22public abstract non-sealed class SymbolView<T> implements AnySymbolView {
23 private final Symbol<T> symbol;
24 private final String viewName;
25
26 protected SymbolView(Symbol<T> symbol, String viewName) {
27 this.symbol = symbol;
28 this.viewName = viewName;
29 }
30
31 protected SymbolView(Symbol<T> representation) {
32 this(representation, UUID.randomUUID().toString());
33 }
34
35 @Override
36 public Symbol<T> getSymbol() {
37 return symbol;
38 }
39
40 @Override
41 public String getViewName() {
42 return viewName;
43 }
44
45 @Override
46 public String name() {
47 return symbol.name() + "#" + viewName;
48 }
49
50 public final boolean filter(Tuple key, T value) {
51 return !Objects.equals(symbol.defaultValue(), value) && doFilter(key, value);
52 }
53
54 protected abstract boolean doFilter(Tuple key, T value);
55
56 public abstract Object[] forwardMap(Tuple key, T value);
57
58 @Override
59 public Iterable<Object[]> getAll(Model model) {
60 return (() -> new CursorAsIterator<>(model.getInterpretation(symbol).getAll(), this::forwardMap, this::filter));
61 }
62
63 @Override
64 public String toString() {
65 return name();
66 }
67
68 @Override
69 public String toReferenceString() {
70 return "@RelationView(\"%s\") %s".formatted(viewName, symbol.name());
71 }
72
73 @Override
74 public boolean equals(Object o) {
75 if (this == o) return true;
76 if (o == null || getClass() != o.getClass()) return false;
77 SymbolView<?> that = (SymbolView<?>) o;
78 return Objects.equals(symbol, that.symbol) && Objects.equals(viewName, that.viewName);
79 }
80
81 @Override
82 public int hashCode() {
83 return Objects.hash(getClass(), symbol, viewName);
84 }
85}
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
new file mode 100644
index 00000000..6bc5a708
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java
@@ -0,0 +1,82 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.term.Parameter;
10import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple;
12import tools.refinery.store.tuple.Tuple1;
13
14import java.util.Arrays;
15import java.util.List;
16import java.util.Objects;
17
18public abstract class TuplePreservingView<T> extends SymbolView<T> {
19 private final List<Parameter> parameters;
20
21 protected TuplePreservingView(Symbol<T> symbol, String name) {
22 super(symbol, name);
23 this.parameters = createParameters(symbol.arity());
24 }
25
26 protected TuplePreservingView(Symbol<T> symbol) {
27 super(symbol);
28 this.parameters = createParameters(symbol.arity());
29 }
30
31 public Object[] forwardMap(Tuple key) {
32 Object[] result = new Object[key.getSize()];
33 for (int i = 0; i < key.getSize(); i++) {
34 result[i] = Tuple.of(key.get(i));
35 }
36 return result;
37 }
38
39 @Override
40 public Object[] forwardMap(Tuple key, T value) {
41 return forwardMap(key);
42 }
43
44 @Override
45 public boolean get(Model model, Object[] tuple) {
46 int[] content = new int[tuple.length];
47 for (int i = 0; i < tuple.length; i++) {
48 if (!(tuple[i] instanceof Tuple1 wrapper)) {
49 return false;
50 }
51 content[i] = wrapper.value0();
52 }
53 Tuple key = Tuple.of(content);
54 T value = model.getInterpretation(getSymbol()).get(key);
55 return filter(key, value);
56 }
57
58 @Override
59 public List<Parameter> getParameters() {
60 return parameters;
61 }
62
63 @Override
64 public boolean equals(Object o) {
65 if (this == o) return true;
66 if (o == null || getClass() != o.getClass()) return false;
67 if (!super.equals(o)) return false;
68 TuplePreservingView<?> that = (TuplePreservingView<?>) o;
69 return Objects.equals(parameters, that.parameters);
70 }
71
72 @Override
73 public int hashCode() {
74 return Objects.hash(super.hashCode(), parameters);
75 }
76
77 private static List<Parameter> createParameters(int arity) {
78 var parameters = new Parameter[arity];
79 Arrays.fill(parameters, Parameter.NODE_OUT);
80 return List.of(parameters);
81 }
82}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ViewImplication.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ViewImplication.java
new file mode 100644
index 00000000..fc2db9f2
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ViewImplication.java
@@ -0,0 +1,23 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import java.util.List;
9
10public record ViewImplication(AnySymbolView implyingView, AnySymbolView impliedView, List<Integer> impliedIndices) {
11 public ViewImplication {
12 if (impliedIndices.size() != impliedView.arity()) {
13 throw new IllegalArgumentException("Expected %d implied indices for %s, but %d are provided"
14 .formatted(impliedView.arity(), impliedView, impliedIndices.size()));
15 }
16 for (var index : impliedIndices) {
17 if (impliedView.invalidIndex(index)) {
18 throw new IllegalArgumentException("%d is not a valid index for %s".formatted(index,
19 implyingView));
20 }
21 }
22 }
23}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java
new file mode 100644
index 00000000..e17496e3
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java
@@ -0,0 +1,210 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import org.junit.jupiter.api.Test;
9import org.junit.jupiter.params.ParameterizedTest;
10import org.junit.jupiter.params.provider.CsvSource;
11import tools.refinery.store.query.literal.BooleanLiteral;
12import tools.refinery.store.query.term.NodeVariable;
13import tools.refinery.store.query.term.ParameterDirection;
14import tools.refinery.store.query.term.Variable;
15import tools.refinery.store.query.term.bool.BoolTerms;
16import tools.refinery.store.query.view.KeyOnlyView;
17import tools.refinery.store.query.view.SymbolView;
18import tools.refinery.store.representation.Symbol;
19
20import java.util.List;
21
22import static org.hamcrest.MatcherAssert.assertThat;
23import static tools.refinery.store.query.literal.Literals.assume;
24import static tools.refinery.store.query.literal.Literals.not;
25import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
26
27class DnfBuilderLiteralEliminationTest {
28 private final Symbol<Boolean> friend = Symbol.of("friend", 2);
29 private final SymbolView<Boolean> friendView = new KeyOnlyView<>(friend);
30 private final NodeVariable p = Variable.of("p");
31 private final NodeVariable q = Variable.of("q");
32 private final Dnf trueDnf = Dnf.builder().parameter(p, ParameterDirection.IN).clause().build();
33 private final Dnf falseDnf = Dnf.builder().parameter(p).build();
34
35 @Test
36 void eliminateTrueTest() {
37 var actual = Dnf.builder()
38 .parameters(p, q)
39 .clause(BooleanLiteral.TRUE, friendView.call(p, q))
40 .build();
41 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
42
43 assertThat(actual, structurallyEqualTo(expected));
44 }
45
46 @Test
47 void eliminateTrueAssumptionTest() {
48 var actual = Dnf.builder()
49 .parameters(p, q)
50 .clause(assume(BoolTerms.constant(true)), friendView.call(p, q))
51 .build();
52 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
53
54 assertThat(actual, structurallyEqualTo(expected));
55 }
56
57 @Test
58 void eliminateFalseTest() {
59 var actual = Dnf.builder()
60 .parameters(p, q)
61 .clause(friendView.call(p, q))
62 .clause(friendView.call(q, p), BooleanLiteral.FALSE)
63 .build();
64 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
65
66 assertThat(actual, structurallyEqualTo(expected));
67 }
68
69 @ParameterizedTest
70 @CsvSource(value = {
71 "false",
72 "null"
73 }, nullValues = "null")
74 void eliminateFalseAssumptionTest(Boolean value) {
75 var actual = Dnf.builder()
76 .parameters(p, q)
77 .clause(friendView.call(p, q))
78 .clause(friendView.call(q, p), assume(BoolTerms.constant(value)))
79 .build();
80 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
81
82 assertThat(actual, structurallyEqualTo(expected));
83 }
84
85 @Test
86 void alwaysTrueTest() {
87 var actual = Dnf.builder()
88 .parameters(List.of(p, q), ParameterDirection.IN)
89 .clause(friendView.call(p, q))
90 .clause(BooleanLiteral.TRUE)
91 .build();
92 var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build();
93
94 assertThat(actual, structurallyEqualTo(expected));
95 }
96
97 @Test
98 void alwaysFalseTest() {
99 var actual = Dnf.builder()
100 .parameters(p, q)
101 .clause(friendView.call(p, q), BooleanLiteral.FALSE)
102 .build();
103 var expected = Dnf.builder().parameters(p, q).build();
104
105 assertThat(actual, structurallyEqualTo(expected));
106 }
107
108 @Test
109 void eliminateTrueDnfTest() {
110 var actual = Dnf.builder()
111 .parameters(p, q)
112 .clause(trueDnf.call(q), friendView.call(p, q))
113 .build();
114 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
115
116 assertThat(actual, structurallyEqualTo(expected));
117 }
118
119 @Test
120 void eliminateFalseDnfTest() {
121 var actual = Dnf.builder()
122 .parameters(p, q)
123 .clause(friendView.call(p, q))
124 .clause(friendView.call(q, p), falseDnf.call(q))
125 .build();
126 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
127
128 assertThat(actual, structurallyEqualTo(expected));
129 }
130
131 @Test
132 void alwaysTrueDnfTest() {
133 var actual = Dnf.builder()
134 .parameters(List.of(p, q), ParameterDirection.IN)
135 .clause(friendView.call(p, q))
136 .clause(trueDnf.call(q))
137 .build();
138 var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build();
139
140 assertThat(actual, structurallyEqualTo(expected));
141 }
142
143 @Test
144 void alwaysFalseDnfTest() {
145 var actual = Dnf.builder()
146 .parameters(p, q)
147 .clause(friendView.call(p, q), falseDnf.call(q))
148 .build();
149 var expected = Dnf.builder().parameters(p, q).build();
150
151 assertThat(actual, structurallyEqualTo(expected));
152 }
153
154 @Test
155 void eliminateNotFalseDnfTest() {
156 var actual = Dnf.builder()
157 .parameters(p, q)
158 .clause(not(falseDnf.call(q)), friendView.call(p, q))
159 .build();
160 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
161
162 assertThat(actual, structurallyEqualTo(expected));
163 }
164
165 @Test
166 void eliminateNotTrueDnfTest() {
167 var actual = Dnf.builder()
168 .parameters(p, q)
169 .clause(friendView.call(p, q))
170 .clause(friendView.call(q, p), not(trueDnf.call(q)))
171 .build();
172 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
173
174 assertThat(actual, structurallyEqualTo(expected));
175 }
176
177 @Test
178 void alwaysNotFalseDnfTest() {
179 var actual = Dnf.builder()
180 .parameters(List.of(p, q), ParameterDirection.IN)
181 .clause(friendView.call(p, q))
182 .clause(not(falseDnf.call(q)))
183 .build();
184 var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build();
185
186 assertThat(actual, structurallyEqualTo(expected));
187 }
188
189 @Test
190 void alwaysNotTrueDnfTest() {
191 var actual = Dnf.builder()
192 .parameters(p, q)
193 .clause(friendView.call(p, q), not(trueDnf.call(q)))
194 .build();
195 var expected = Dnf.builder().parameters(p, q).build();
196
197 assertThat(actual, structurallyEqualTo(expected));
198 }
199
200 @Test
201 void removeDuplicateTest() {
202 var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of(
203 friendView.call(p, q),
204 friendView.call(p, q)
205 )));
206 var expected = Dnf.of(builder -> builder.clause((p, q) -> List.of(friendView.call(p, q))));
207
208 assertThat(actual, structurallyEqualTo(expected));
209 }
210}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java
new file mode 100644
index 00000000..fc40c7b3
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java
@@ -0,0 +1,325 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.term.ParameterDirection;
10import tools.refinery.store.query.term.Variable;
11import tools.refinery.store.query.view.KeyOnlyView;
12import tools.refinery.store.query.view.SymbolView;
13import tools.refinery.store.representation.Symbol;
14
15import java.util.List;
16
17import static org.hamcrest.MatcherAssert.assertThat;
18import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
19
20class DnfBuilderVariableUnificationTest {
21 private final Symbol<Boolean> friend = Symbol.of("friend", 2);
22 private final Symbol<Boolean> children = Symbol.of("children", 2);
23 private final SymbolView<Boolean> friendView = new KeyOnlyView<>(friend);
24 private final SymbolView<Boolean> childrenView = new KeyOnlyView<>(children);
25
26 @Test
27 void equalToParameterTest() {
28 var actual = Dnf.of(builder -> {
29 var p = builder.parameter("p");
30 builder.clause(q -> List.of(
31 friendView.call(p, q),
32 p.isEquivalent(q)
33 ));
34 });
35
36 var expectedP = Variable.of("p");
37 assertThat(actual, structurallyEqualTo(
38 List.of(new SymbolicParameter(expectedP, ParameterDirection.OUT)),
39 List.of(
40 List.of(friendView.call(expectedP, expectedP))
41 )
42 ));
43 }
44
45 @Test
46 void equalToParameterReverseTest() {
47 var actual = Dnf.of(builder -> {
48 var p = builder.parameter("p");
49 builder.clause(q -> List.of(
50 friendView.call(p, q),
51 q.isEquivalent(p)
52 ));
53 });
54
55 var expectedP = Variable.of("p");
56 assertThat(actual, structurallyEqualTo(
57 List.of(new SymbolicParameter(expectedP, ParameterDirection.OUT)),
58 List.of(
59 List.of(friendView.call(expectedP, expectedP))
60 )
61 ));
62 }
63
64 @Test
65 void equalQuantifiedTest() {
66 var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of(
67 friendView.call(p, q),
68 p.isEquivalent(q)
69 )));
70
71 var expectedP = Variable.of("p");
72 assertThat(actual, structurallyEqualTo(
73 List.of(),
74 List.of(
75 List.of(friendView.call(expectedP, expectedP))
76 )
77 ));
78 }
79
80 @Test
81 void equalQuantifiedTransitiveTest() {
82 var actual = Dnf.of(builder -> builder.clause((p, q, r) -> List.of(
83 friendView.call(p, q),
84 p.isEquivalent(q),
85 childrenView.call(p, r),
86 q.isEquivalent(r)
87 )));
88
89 var expectedP = Variable.of("p");
90 assertThat(actual, structurallyEqualTo(
91 List.of(),
92 List.of(
93 List.of(friendView.call(expectedP, expectedP), childrenView.call(expectedP, expectedP))
94 )
95 ));
96 }
97
98 @Test
99 void equalQuantifiedTransitiveRemoveDuplicateTest() {
100 var actual = Dnf.of(builder -> builder.clause((p, q, r) -> List.of(
101 friendView.call(p, q),
102 p.isEquivalent(q),
103 friendView.call(p, r),
104 q.isEquivalent(r)
105 )));
106
107 var expectedP = Variable.of("p");
108 assertThat(actual, structurallyEqualTo(
109 List.of(),
110 List.of(
111 List.of(friendView.call(expectedP, expectedP))
112 )
113 ));
114 }
115
116 @Test
117 void parametersEqualTest() {
118 var actual = Dnf.of(builder -> {
119 var p = builder.parameter("p");
120 var q = builder.parameter("q");
121 builder.clause(
122 friendView.call(p, q),
123 p.isEquivalent(q)
124 );
125 });
126
127 var expectedP = Variable.of("p");
128 var expectedQ = Variable.of("q");
129 assertThat(actual, structurallyEqualTo(
130 List.of(
131 new SymbolicParameter(expectedP, ParameterDirection.OUT),
132 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
133 ),
134 List.of(
135 List.of(friendView.call(expectedP, expectedP), expectedQ.isEquivalent(expectedP))
136 )
137 ));
138 }
139
140 @Test
141 void parametersEqualTransitiveTest() {
142 var actual = Dnf.of(builder -> {
143 var p = builder.parameter("p");
144 var q = builder.parameter("q");
145 var r = builder.parameter("r");
146 builder.clause(
147 friendView.call(p, q),
148 childrenView.call(p, r),
149 p.isEquivalent(q),
150 r.isEquivalent(q)
151 );
152 });
153
154 var expectedP = Variable.of("p");
155 var expectedQ = Variable.of("q");
156 var expectedR = Variable.of("r");
157 assertThat(actual, structurallyEqualTo(
158 List.of(
159 new SymbolicParameter(expectedP, ParameterDirection.OUT),
160 new SymbolicParameter(expectedQ, ParameterDirection.OUT),
161 new SymbolicParameter(expectedR, ParameterDirection.OUT)
162 ),
163 List.of(
164 List.of(
165 friendView.call(expectedP, expectedP),
166 expectedQ.isEquivalent(expectedP),
167 expectedR.isEquivalent(expectedP),
168 childrenView.call(expectedP, expectedP)
169 )
170 )
171 ));
172 }
173
174 @Test
175 void parameterAndQuantifiedEqualsTest() {
176 var actual = Dnf.of(builder -> {
177 var p = builder.parameter("p");
178 var q = builder.parameter("q");
179 builder.clause((r) -> List.of(
180 friendView.call(p, r),
181 p.isEquivalent(r),
182 childrenView.call(q, r),
183 q.isEquivalent(r)
184 ));
185 });
186
187
188 var expectedP = Variable.of("p");
189 var expectedQ = Variable.of("q");
190 assertThat(actual, structurallyEqualTo(
191 List.of(
192 new SymbolicParameter(expectedP, ParameterDirection.OUT),
193 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
194 ),
195 List.of(
196 List.of(
197 friendView.call(expectedP, expectedP),
198 expectedQ.isEquivalent(expectedP),
199 childrenView.call(expectedP, expectedP)
200 )
201 )
202 ));
203 }
204
205 @Test
206 void parameterAndQuantifiedEqualsReverseFirstTest() {
207 var actual = Dnf.of(builder -> {
208 var p = builder.parameter("p");
209 var q = builder.parameter("q");
210 builder.clause((r) -> List.of(
211 friendView.call(p, r),
212 r.isEquivalent(p),
213 childrenView.call(q, r),
214 q.isEquivalent(r)
215 ));
216 });
217
218 var expectedP = Variable.of("p");
219 var expectedQ = Variable.of("q");
220 assertThat(actual, structurallyEqualTo(
221 List.of(
222 new SymbolicParameter(expectedP, ParameterDirection.OUT),
223 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
224 ),
225 List.of(
226 List.of(
227 friendView.call(expectedP, expectedP),
228 expectedQ.isEquivalent(expectedP),
229 childrenView.call(expectedP, expectedP)
230 )
231 )
232 ));
233 }
234
235 @Test
236 void parameterAndQuantifiedEqualsReverseSecondTest() {
237 var actual = Dnf.of(builder -> {
238 var p = builder.parameter("p");
239 var q = builder.parameter("q");
240 builder.clause((r) -> List.of(
241 friendView.call(p, r),
242 p.isEquivalent(r),
243 childrenView.call(q, r),
244 r.isEquivalent(q)
245 ));
246 });
247
248 var expectedP = Variable.of("p");
249 var expectedQ = Variable.of("q");
250 assertThat(actual, structurallyEqualTo(
251 List.of(
252 new SymbolicParameter(expectedP, ParameterDirection.OUT),
253 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
254 ),
255 List.of(
256 List.of(
257 friendView.call(expectedP, expectedP),
258 expectedQ.isEquivalent(expectedP),
259 childrenView.call(expectedP, expectedP)
260 )
261 )
262 ));
263 }
264
265 @Test
266 void parameterAndQuantifiedEqualsReverseBoth() {
267 var actual = Dnf.of(builder -> {
268 var p = builder.parameter("p");
269 var q = builder.parameter("q");
270 builder.clause((r) -> List.of(
271 friendView.call(p, r),
272 p.isEquivalent(r),
273 childrenView.call(q, r),
274 r.isEquivalent(q)
275 ));
276 });
277
278 var expectedP = Variable.of("p");
279 var expectedQ = Variable.of("q");
280 assertThat(actual, structurallyEqualTo(
281 List.of(
282 new SymbolicParameter(expectedP, ParameterDirection.OUT),
283 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
284 ),
285 List.of(
286 List.of(
287 friendView.call(expectedP, expectedP),
288 expectedQ.isEquivalent(expectedP),
289 childrenView.call(expectedP, expectedP)
290 )
291 )
292 ));
293 }
294
295 @Test
296 void parameterAndTwoQuantifiedEqualsTest() {
297 var actual = Dnf.of(builder -> {
298 var p = builder.parameter("p");
299 var q = builder.parameter("q");
300 builder.clause((r, s) -> List.of(
301 r.isEquivalent(s),
302 friendView.call(p, r),
303 p.isEquivalent(r),
304 childrenView.call(q, s),
305 q.isEquivalent(s)
306 ));
307 });
308
309 var expectedP = Variable.of("p");
310 var expectedQ = Variable.of("q");
311 assertThat(actual, structurallyEqualTo(
312 List.of(
313 new SymbolicParameter(expectedP, ParameterDirection.OUT),
314 new SymbolicParameter(expectedQ, ParameterDirection.OUT)
315 ),
316 List.of(
317 List.of(
318 friendView.call(expectedP, expectedP),
319 expectedQ.isEquivalent(expectedP),
320 childrenView.call(expectedP, expectedP)
321 )
322 )
323 ));
324 }
325}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java
new file mode 100644
index 00000000..d75d7f17
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java
@@ -0,0 +1,157 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.term.NodeVariable;
10import tools.refinery.store.query.term.ParameterDirection;
11import tools.refinery.store.query.term.Variable;
12import tools.refinery.store.query.view.AnySymbolView;
13import tools.refinery.store.query.view.KeyOnlyView;
14import tools.refinery.store.representation.Symbol;
15
16import static org.hamcrest.MatcherAssert.assertThat;
17import static org.hamcrest.Matchers.is;
18import static tools.refinery.store.query.literal.Literals.not;
19
20class DnfToDefinitionStringTest {
21 private static final Symbol<Boolean> person = Symbol.of("person", 1);
22 private static final Symbol<Boolean> friend = Symbol.of("friend", 2);
23 private static final AnySymbolView personView = new KeyOnlyView<>(person);
24 private static final AnySymbolView friendView = new KeyOnlyView<>(friend);
25 private static final NodeVariable p = Variable.of("p");
26 private static final NodeVariable q = Variable.of("q");
27
28 @Test
29 void noClausesTest() {
30 var dnf = Dnf.builder("Example").parameter(p).build();
31
32 assertThat(dnf.toDefinitionString(), is("""
33 pred Example(p) <->
34 <no clauses>.
35 """));
36 }
37
38 @Test
39 void noParametersTest() {
40 var dnf = Dnf.builder("Example").build();
41
42 assertThat(dnf.toDefinitionString(), is("""
43 pred Example() <->
44 <no clauses>.
45 """));
46 }
47
48 @Test
49 void emptyClauseTest() {
50 var dnf = Dnf.builder("Example").parameter(p, ParameterDirection.IN).clause().build();
51
52 assertThat(dnf.toDefinitionString(), is("""
53 pred Example(@In p) <->
54 <empty>.
55 """));
56 }
57
58 @Test
59 void relationViewPositiveTest() {
60 var dnf = Dnf.builder("Example").parameter(p).clause(friendView.call(p, q)).build();
61
62 assertThat(dnf.toDefinitionString(), is("""
63 pred Example(p) <->
64 @RelationView("key") friend(p, q).
65 """));
66 }
67
68 @Test
69 void relationViewNegativeTest() {
70 var dnf = Dnf.builder("Example")
71 .parameter(p, ParameterDirection.IN)
72 .clause(not(friendView.call(p, q)))
73 .build();
74
75 assertThat(dnf.toDefinitionString(), is("""
76 pred Example(@In p) <->
77 !(@RelationView("key") friend(p, q)).
78 """));
79 }
80
81 @Test
82 void relationViewTransitiveTest() {
83 var dnf = Dnf.builder("Example").parameter(p).clause(friendView.callTransitive(p, q)).build();
84
85 assertThat(dnf.toDefinitionString(), is("""
86 pred Example(p) <->
87 @RelationView("key") friend+(p, q).
88 """));
89 }
90
91 @Test
92 void multipleParametersTest() {
93 var dnf = Dnf.builder("Example").parameters(p, q).clause(friendView.call(p, q)).build();
94
95 assertThat(dnf.toDefinitionString(), is("""
96 pred Example(p, q) <->
97 @RelationView("key") friend(p, q).
98 """));
99 }
100
101 @Test
102 void multipleLiteralsTest() {
103 var dnf = Dnf.builder("Example")
104 .parameter(p)
105 .clause(
106 personView.call(p),
107 personView.call(q),
108 friendView.call(p, q)
109 )
110 .build();
111
112 assertThat(dnf.toDefinitionString(), is("""
113 pred Example(p) <->
114 @RelationView("key") person(p),
115 @RelationView("key") person(q),
116 @RelationView("key") friend(p, q).
117 """));
118 }
119
120 @Test
121 void multipleClausesTest() {
122 var dnf = Dnf.builder("Example")
123 .parameter(p)
124 .clause(friendView.call(p, q))
125 .clause(friendView.call(q, p))
126 .build();
127
128 assertThat(dnf.toDefinitionString(), is("""
129 pred Example(p) <->
130 @RelationView("key") friend(p, q)
131 ;
132 @RelationView("key") friend(q, p).
133 """));
134 }
135
136 @Test
137 void dnfTest() {
138 var r = Variable.of("r");
139 var s = Variable.of("s");
140 var called = Dnf.builder("Called").parameters(r, s).clause(friendView.call(r, s)).build();
141 var dnf = Dnf.builder("Example")
142 .parameter(p)
143 .clause(
144 personView.call(p),
145 personView.call(q),
146 not(called.call(p, q))
147 )
148 .build();
149
150 assertThat(dnf.toDefinitionString(), is("""
151 pred Example(p) <->
152 @RelationView("key") person(p),
153 @RelationView("key") person(q),
154 !(@Dnf Called(p, q)).
155 """));
156 }
157}
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..e22dbb21
--- /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 */
6package tools.refinery.store.query.dnf;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.term.NodeVariable;
10import tools.refinery.store.query.term.ParameterDirection;
11import tools.refinery.store.query.term.Variable;
12import tools.refinery.store.query.view.AnySymbolView;
13import tools.refinery.store.query.view.KeyOnlyView;
14import tools.refinery.store.representation.Symbol;
15
16import java.util.List;
17
18import static org.hamcrest.MatcherAssert.assertThat;
19import static org.junit.jupiter.api.Assertions.assertThrows;
20import static tools.refinery.store.query.literal.Literals.not;
21import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
22
23class TopologicalSortTest {
24 private static final Symbol<Boolean> friend = Symbol.of("friend", 2);
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..c52d26b2
--- /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 */
6package tools.refinery.store.query.dnf;
7
8import org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.Arguments;
10import org.junit.jupiter.params.provider.MethodSource;
11import tools.refinery.store.query.literal.BooleanLiteral;
12import tools.refinery.store.query.literal.Literal;
13import tools.refinery.store.query.term.DataVariable;
14import tools.refinery.store.query.term.NodeVariable;
15import tools.refinery.store.query.term.ParameterDirection;
16import tools.refinery.store.query.term.Variable;
17import tools.refinery.store.query.view.AnySymbolView;
18import tools.refinery.store.query.view.FunctionView;
19import tools.refinery.store.query.view.KeyOnlyView;
20import tools.refinery.store.representation.Symbol;
21
22import java.util.ArrayList;
23import java.util.List;
24import java.util.stream.Stream;
25
26import static org.hamcrest.MatcherAssert.assertThat;
27import static org.hamcrest.Matchers.hasItem;
28import static org.hamcrest.Matchers.not;
29import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
30import static org.junit.jupiter.api.Assertions.assertThrows;
31import static tools.refinery.store.query.literal.Literals.assume;
32import static tools.refinery.store.query.literal.Literals.not;
33import static tools.refinery.store.query.term.int_.IntTerms.*;
34
35class VariableDirectionTest {
36 private static final Symbol<Boolean> person = Symbol.of("Person", 1);
37 private static final Symbol<Boolean> friend = Symbol.of("friend", 2);
38 private static final Symbol<Integer> age = Symbol.of("age", 1, Integer.class);
39 private static final AnySymbolView personView = new KeyOnlyView<>(person);
40 private static final AnySymbolView friendView = new KeyOnlyView<>(friend);
41 private static final FunctionView<Integer> 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(INT_SUM, p))),
153 Arguments.of(not(dnfWithOutput.call(p, q))),
154 Arguments.of(y.assign(dnfWithOutput.count(p, q))),
155 Arguments.of(y.assign(dnfWithOutputToAggregate.aggregateBy(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.aggregateBy(z, INT_SUM, p, q, z))),
196 Arguments.of(y.assign(dnfWithInputToAggregate.aggregateBy(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.aggregateBy(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(INT_SUM, q))),
419 Arguments.of(dnfWithDataOutput.call(x, q)),
420 Arguments.of(x.assign(dnfWithOutput.count(q))),
421 Arguments.of(x.assign(dnfWithOutputToAggregate.aggregateBy(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..35910e08
--- /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 */
6package tools.refinery.store.query.literal;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.Constraint;
10import tools.refinery.store.query.dnf.Dnf;
11import tools.refinery.store.query.term.*;
12
13import java.util.List;
14import java.util.Set;
15
16import static org.hamcrest.MatcherAssert.assertThat;
17import static org.hamcrest.Matchers.containsInAnyOrder;
18import static org.hamcrest.Matchers.empty;
19import static org.junit.jupiter.api.Assertions.assertAll;
20import static org.junit.jupiter.api.Assertions.assertThrows;
21import static tools.refinery.store.query.literal.Literals.not;
22import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM;
23import static tools.refinery.store.query.term.int_.IntTerms.constant;
24
25class 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.aggregateBy(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.aggregateBy(y, INT_SUM, p, z);
60 assertThrows(IllegalArgumentException.class, () -> x.assign(aggregation));
61 }
62
63 @Test
64 void circularAggregationVariableTest() {
65 var aggregation = fakeConstraint.aggregateBy(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.aggregateBy(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.aggregateBy(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 */
6package tools.refinery.store.query.literal;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.Constraint;
10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.Parameter;
12import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.term.Variable;
14
15import java.util.List;
16import java.util.Set;
17
18import static org.hamcrest.MatcherAssert.assertThat;
19import static org.hamcrest.Matchers.containsInAnyOrder;
20import static org.hamcrest.Matchers.empty;
21import static org.junit.jupiter.api.Assertions.assertAll;
22import static tools.refinery.store.query.literal.Literals.not;
23
24class 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-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java
new file mode 100644
index 00000000..1cbc101a
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java
@@ -0,0 +1,97 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import org.junit.jupiter.api.Assertions;
9import org.junit.jupiter.params.ParameterizedTest;
10import org.junit.jupiter.params.provider.Arguments;
11import org.junit.jupiter.params.provider.MethodSource;
12import tools.refinery.store.query.dnf.Dnf;
13import tools.refinery.store.query.equality.LiteralEqualityHelper;
14import tools.refinery.store.query.substitution.Substitution;
15import tools.refinery.store.query.term.bool.BoolTerms;
16import tools.refinery.store.query.term.int_.IntTerms;
17import tools.refinery.store.query.term.real.RealTerms;
18import tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms;
19import tools.refinery.store.representation.cardinality.UpperCardinality;
20
21import java.util.List;
22import java.util.stream.Stream;
23
24class TermSubstitutionTest {
25 private final static DataVariable<Integer> intA = Variable.of("intA", Integer.class);
26 private final static DataVariable<Integer> intB = Variable.of("intB", Integer.class);
27 private final static DataVariable<Double> realA = Variable.of("realA", Double.class);
28 private final static DataVariable<Double> realB = Variable.of("realB", Double.class);
29 private final static DataVariable<Boolean> boolA = Variable.of("boolA", Boolean.class);
30 private final static DataVariable<Boolean> boolB = Variable.of("boolB", Boolean.class);
31 private final static DataVariable<UpperCardinality> upperCardinalityA = Variable.of("upperCardinalityA",
32 UpperCardinality.class);
33 private final static DataVariable<UpperCardinality> upperCardinalityB = Variable.of("upperCardinalityB",
34 UpperCardinality.class);
35 private final static Substitution substitution = Substitution.builder()
36 .put(intA, intB)
37 .put(intB, intA)
38 .put(realA, realB)
39 .put(realB, realA)
40 .put(boolA, boolB)
41 .put(boolB, boolA)
42 .put(upperCardinalityA, upperCardinalityB)
43 .put(upperCardinalityB, upperCardinalityA)
44 .build();
45
46 @ParameterizedTest
47 @MethodSource
48 void substitutionTest(AnyTerm term) {
49 var substitutedTerm1 = term.substitute(substitution);
50 Assertions.assertNotEquals(term, substitutedTerm1, "Original term is not equal to substituted term");
51 var helper = new LiteralEqualityHelper(Dnf::equals, List.of(), List.of());
52 Assertions.assertTrue(term.equalsWithSubstitution(helper, substitutedTerm1), "Terms are equal by helper");
53 // The {@link #substitution} is its own inverse.
54 var substitutedTerm2 = substitutedTerm1.substitute(substitution);
55 Assertions.assertEquals(term, substitutedTerm2, "Original term is not equal to back-substituted term");
56 }
57
58 static Stream<Arguments> substitutionTest() {
59 return Stream.of(
60 Arguments.of(IntTerms.plus(intA)),
61 Arguments.of(IntTerms.minus(intA)),
62 Arguments.of(IntTerms.add(intA, intB)),
63 Arguments.of(IntTerms.sub(intA, intB)),
64 Arguments.of(IntTerms.mul(intA, intB)),
65 Arguments.of(IntTerms.div(intA, intB)),
66 Arguments.of(IntTerms.pow(intA, intB)),
67 Arguments.of(IntTerms.min(intA, intB)),
68 Arguments.of(IntTerms.max(intA, intB)),
69 Arguments.of(IntTerms.eq(intA, intB)),
70 Arguments.of(IntTerms.notEq(intA, intB)),
71 Arguments.of(IntTerms.less(intA, intB)),
72 Arguments.of(IntTerms.lessEq(intA, intB)),
73 Arguments.of(IntTerms.greater(intA, intB)),
74 Arguments.of(IntTerms.greaterEq(intA, intB)),
75 Arguments.of(IntTerms.asInt(realA)),
76 Arguments.of(RealTerms.plus(realA)),
77 Arguments.of(RealTerms.minus(realA)),
78 Arguments.of(RealTerms.add(realA, realB)),
79 Arguments.of(RealTerms.sub(realA, realB)),
80 Arguments.of(RealTerms.mul(realA, realB)),
81 Arguments.of(RealTerms.div(realA, realB)),
82 Arguments.of(RealTerms.pow(realA, realB)),
83 Arguments.of(RealTerms.min(realA, realB)),
84 Arguments.of(RealTerms.max(realA, realB)),
85 Arguments.of(RealTerms.asReal(intA)),
86 Arguments.of(BoolTerms.not(boolA)),
87 Arguments.of(BoolTerms.and(boolA, boolB)),
88 Arguments.of(BoolTerms.or(boolA, boolB)),
89 Arguments.of(BoolTerms.xor(boolA, boolB)),
90 Arguments.of(RealTerms.eq(realA, realB)),
91 Arguments.of(UpperCardinalityTerms.add(upperCardinalityA, upperCardinalityB)),
92 Arguments.of(UpperCardinalityTerms.mul(upperCardinalityA, upperCardinalityB)),
93 Arguments.of(UpperCardinalityTerms.min(upperCardinalityA, upperCardinalityB)),
94 Arguments.of(UpperCardinalityTerms.max(upperCardinalityA, upperCardinalityB))
95 );
96 }
97}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/bool/BoolTermsEvaluateTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/bool/BoolTermsEvaluateTest.java
new file mode 100644
index 00000000..beff705e
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/bool/BoolTermsEvaluateTest.java
@@ -0,0 +1,75 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.bool;
7
8import org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.CsvSource;
10import tools.refinery.store.query.valuation.Valuation;
11
12import static org.hamcrest.MatcherAssert.assertThat;
13import static org.hamcrest.Matchers.is;
14
15class BoolTermsEvaluateTest {
16 @ParameterizedTest(name = "!{0} == {1}")
17 @CsvSource(value = {
18 "false, true",
19 "true, false",
20 "null, null"
21 }, nullValues = "null")
22 void notTest(Boolean a, Boolean result) {
23 var term = BoolTerms.not(BoolTerms.constant(a));
24 assertThat(term.getType(), is(Boolean.class));
25 assertThat(term.evaluate(Valuation.empty()), is(result));
26 }
27
28 @ParameterizedTest(name = "{0} && {1} == {2}")
29 @CsvSource(value = {
30 "false, false, false",
31 "false, true, false",
32 "true, false, false",
33 "true, true, true",
34 "false, null, null",
35 "null, false, null",
36 "null, null, null"
37 }, nullValues = "null")
38 void andTest(Boolean a, Boolean b, Boolean result) {
39 var term = BoolTerms.and(BoolTerms.constant(a), BoolTerms.constant(b));
40 assertThat(term.getType(), is(Boolean.class));
41 assertThat(term.evaluate(Valuation.empty()), is(result));
42 }
43
44 @ParameterizedTest(name = "{0} || {1} == {2}")
45 @CsvSource(value = {
46 "false, false, false",
47 "false, true, true",
48 "true, false, true",
49 "true, true, true",
50 "true, null, null",
51 "null, true, null",
52 "null, null, null"
53 }, nullValues = "null")
54 void orTest(Boolean a, Boolean b, Boolean result) {
55 var term = BoolTerms.or(BoolTerms.constant(a), BoolTerms.constant(b));
56 assertThat(term.getType(), is(Boolean.class));
57 assertThat(term.evaluate(Valuation.empty()), is(result));
58 }
59
60 @ParameterizedTest(name = "{0} ^^ {1} == {2}")
61 @CsvSource(value = {
62 "false, false, false",
63 "false, true, true",
64 "true, false, true",
65 "true, true, false",
66 "false, null, null",
67 "null, false, null",
68 "null, null, null"
69 }, nullValues = "null")
70 void xorTest(Boolean a, Boolean b, Boolean result) {
71 var term = BoolTerms.xor(BoolTerms.constant(a), BoolTerms.constant(b));
72 assertThat(term.getType(), is(Boolean.class));
73 assertThat(term.evaluate(Valuation.empty()), is(result));
74 }
75}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/int_/IntTermsEvaluateTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/int_/IntTermsEvaluateTest.java
new file mode 100644
index 00000000..abe50d75
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/int_/IntTermsEvaluateTest.java
@@ -0,0 +1,259 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.Arguments;
10import org.junit.jupiter.params.provider.CsvSource;
11import org.junit.jupiter.params.provider.MethodSource;
12import tools.refinery.store.query.term.real.RealTerms;
13import tools.refinery.store.query.valuation.Valuation;
14
15import java.util.stream.Stream;
16
17import static org.hamcrest.Matchers.is;
18import static org.hamcrest.MatcherAssert.assertThat;
19
20class IntTermsEvaluateTest {
21 @ParameterizedTest(name = "+{0} == {1}")
22 @CsvSource(value = {
23 "2, 2",
24 "null, null"
25 }, nullValues = "null")
26 void plusTest(Integer a, Integer result) {
27 var term = IntTerms.plus(IntTerms.constant(a));
28 assertThat(term.getType(), is(Integer.class));
29 assertThat(term.evaluate(Valuation.empty()), is(result));
30 }
31
32 @ParameterizedTest(name = "-{0} == {1}")
33 @CsvSource(value = {
34 "2, -2",
35 "null, null"
36 }, nullValues = "null")
37 void minusTest(Integer a, Integer result) {
38 var term = IntTerms.minus(IntTerms.constant(a));
39 assertThat(term.getType(), is(Integer.class));
40 assertThat(term.evaluate(Valuation.empty()), is(result));
41 }
42
43 @ParameterizedTest(name = "{0} + {1} == {2}")
44 @CsvSource(value = {
45 "1, 2, 3",
46 "null, 2, null",
47 "1, null, null",
48 "null, null, null"
49 }, nullValues = "null")
50 void addTest(Integer a, Integer b, Integer result) {
51 var term = IntTerms.add(IntTerms.constant(a), IntTerms.constant(b));
52 assertThat(term.getType(), is(Integer.class));
53 assertThat(term.evaluate(Valuation.empty()), is(result));
54 }
55
56 @ParameterizedTest(name = "{0} - {1} == {2}")
57 @CsvSource(value = {
58 "1, 3, -2",
59 "null, 3, null",
60 "1, null, null",
61 "null, null, null"
62 }, nullValues = "null")
63 void subTest(Integer a, Integer b, Integer result) {
64 var term = IntTerms.sub(IntTerms.constant(a), IntTerms.constant(b));
65 assertThat(term.getType(), is(Integer.class));
66 assertThat(term.evaluate(Valuation.empty()), is(result));
67 }
68
69 @ParameterizedTest(name = "{0} * {1} == {2}")
70 @CsvSource(value = {
71 "2, 3, 6",
72 "null, 3, null",
73 "2, null, null",
74 "null, null, null"
75 }, nullValues = "null")
76 void mulTest(Integer a, Integer b, Integer result) {
77 var term = IntTerms.mul(IntTerms.constant(a), IntTerms.constant(b));
78 assertThat(term.getType(), is(Integer.class));
79 assertThat(term.evaluate(Valuation.empty()), is(result));
80 }
81
82 @ParameterizedTest(name = "{0} * {1} == {2}")
83 @CsvSource(value = {
84 "6, 3, 2",
85 "7, 3, 2",
86 "6, 0, null",
87 "null, 3, null",
88 "6, null, null",
89 "null, null, null"
90 }, nullValues = "null")
91 void divTest(Integer a, Integer b, Integer result) {
92 var term = IntTerms.div(IntTerms.constant(a), IntTerms.constant(b));
93 assertThat(term.getType(), is(Integer.class));
94 assertThat(term.evaluate(Valuation.empty()), is(result));
95 }
96
97 @ParameterizedTest(name = "{0} ** {1} == {2}")
98 @CsvSource(value = {
99 "1, 0, 1",
100 "1, 3, 1",
101 "1, -3, null",
102 "2, 0, 1",
103 "2, 2, 4",
104 "2, 3, 8",
105 "2, 4, 16",
106 "2, 5, 32",
107 "2, 6, 64",
108 "2, -3, null",
109 "null, 3, null",
110 "2, null, null",
111 "null, null, null"
112 }, nullValues = "null")
113 void powTest(Integer a, Integer b, Integer result) {
114 var term = IntTerms.pow(IntTerms.constant(a), IntTerms.constant(b));
115 assertThat(term.getType(), is(Integer.class));
116 assertThat(term.evaluate(Valuation.empty()), is(result));
117 }
118
119 @ParameterizedTest(name = "min({0}, {1}) == {2}")
120 @CsvSource(value = {
121 "1, 2, 1",
122 "2, 1, 1",
123 "null, 2, null",
124 "1, null, null",
125 "null, null, null"
126 }, nullValues = "null")
127 void minTest(Integer a, Integer b, Integer result) {
128 var term = IntTerms.min(IntTerms.constant(a), IntTerms.constant(b));
129 assertThat(term.getType(), is(Integer.class));
130 assertThat(term.evaluate(Valuation.empty()), is(result));
131 }
132
133 @ParameterizedTest(name = "max({0}, {1}) == {2}")
134 @CsvSource(value = {
135 "1, 2, 2",
136 "2, 1, 2",
137 "null, 2, null",
138 "1, null, null",
139 "null, null, null"
140 }, nullValues = "null")
141 void maxTest(Integer a, Integer b, Integer result) {
142 var term = IntTerms.max(IntTerms.constant(a), IntTerms.constant(b));
143 assertThat(term.getType(), is(Integer.class));
144 assertThat(term.evaluate(Valuation.empty()), is(result));
145 }
146
147 @ParameterizedTest(name = "({0} == {1}) == {2}")
148 @CsvSource(value = {
149 "1, 1, true",
150 "1, 2, false",
151 "null, 1, null",
152 "1, null, null",
153 "null, null, null"
154 }, nullValues = "null")
155 void eqTest(Integer a, Integer b, Boolean result) {
156 var term = IntTerms.eq(IntTerms.constant(a), IntTerms.constant(b));
157 assertThat(term.getType(), is(Boolean.class));
158 assertThat(term.evaluate(Valuation.empty()), is(result));
159 }
160
161 @ParameterizedTest(name = "({0} != {1}) == {2}")
162 @CsvSource(value = {
163 "1, 1, false",
164 "1, 2, true",
165 "null, 1, null",
166 "1, null, null",
167 "null, null, null"
168 }, nullValues = "null")
169 void notEqTest(Integer a, Integer b, Boolean result) {
170 var term = IntTerms.notEq(IntTerms.constant(a), IntTerms.constant(b));
171 assertThat(term.getType(), is(Boolean.class));
172 assertThat(term.evaluate(Valuation.empty()), is(result));
173 }
174
175 @ParameterizedTest(name = "({0} < {1}) == {2}")
176 @CsvSource(value = {
177 "1, -2, false",
178 "1, 1, false",
179 "1, 2, true",
180 "null, 1, null",
181 "1, null, null",
182 "null, null, null"
183 }, nullValues = "null")
184 void lessTest(Integer a, Integer b, Boolean result) {
185 var term = IntTerms.less(IntTerms.constant(a), IntTerms.constant(b));
186 assertThat(term.getType(), is(Boolean.class));
187 assertThat(term.evaluate(Valuation.empty()), is(result));
188 }
189
190 @ParameterizedTest(name = "({0} <= {1}) == {2}")
191 @CsvSource(value = {
192 "1, -2, false",
193 "1, 1, true",
194 "1, 2, true",
195 "null, 1, null",
196 "1, null, null",
197 "null, null, null"
198 }, nullValues = "null")
199 void lessEqTest(Integer a, Integer b, Boolean result) {
200 var term = IntTerms.lessEq(IntTerms.constant(a), IntTerms.constant(b));
201 assertThat(term.getType(), is(Boolean.class));
202 assertThat(term.evaluate(Valuation.empty()), is(result));
203 }
204
205 @ParameterizedTest(name = "({0} > {1}) == {2}")
206 @CsvSource(value = {
207 "1, -2, true",
208 "1, 1, false",
209 "1, 2, false",
210 "null, 1, null",
211 "1, null, null",
212 "null, null, null"
213 }, nullValues = "null")
214 void greaterTest(Integer a, Integer b, Boolean result) {
215 var term = IntTerms.greater(IntTerms.constant(a), IntTerms.constant(b));
216 assertThat(term.getType(), is(Boolean.class));
217 assertThat(term.evaluate(Valuation.empty()), is(result));
218 }
219
220 @ParameterizedTest(name = "({0} >= {1}) == {2}")
221 @CsvSource(value = {
222 "1, -2, true",
223 "1, 1, true",
224 "1, 2, false",
225 "null, 1, null",
226 "1, null, null",
227 "null, null, null"
228 }, nullValues = "null")
229 void greaterEqTest(Integer a, Integer b, Boolean result) {
230 var term = IntTerms.greaterEq(IntTerms.constant(a), IntTerms.constant(b));
231 assertThat(term.getType(), is(Boolean.class));
232 assertThat(term.evaluate(Valuation.empty()), is(result));
233 }
234
235 @ParameterizedTest(name = "{0} as int == {1}")
236 @MethodSource
237 void asIntTest(Double a, Integer result) {
238 var term = IntTerms.asInt(RealTerms.constant(a));
239 assertThat(term.getType(), is(Integer.class));
240 assertThat(term.evaluate(Valuation.empty()), is(result));
241 }
242
243 static Stream<Arguments> asIntTest() {
244 return Stream.of(
245 Arguments.of(2.0, 2),
246 Arguments.of(2.1, 2),
247 Arguments.of(2.9, 2),
248 Arguments.of(-2.0, -2),
249 Arguments.of(-2.1, -2),
250 Arguments.of(-2.9, -2),
251 Arguments.of(0.0, 0),
252 Arguments.of(-0.0, 0),
253 Arguments.of(Double.POSITIVE_INFINITY, Integer.MAX_VALUE),
254 Arguments.of(Double.NEGATIVE_INFINITY, Integer.MIN_VALUE),
255 Arguments.of(Double.NaN, null),
256 Arguments.of(null, null)
257 );
258 }
259}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/real/RealTermEvaluateTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/real/RealTermEvaluateTest.java
new file mode 100644
index 00000000..6a8eebf1
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/real/RealTermEvaluateTest.java
@@ -0,0 +1,238 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import org.hamcrest.Matcher;
9import org.junit.jupiter.params.ParameterizedTest;
10import org.junit.jupiter.params.provider.CsvSource;
11import tools.refinery.store.query.term.int_.IntTerms;
12import tools.refinery.store.query.valuation.Valuation;
13
14import static org.hamcrest.MatcherAssert.assertThat;
15import static org.hamcrest.Matchers.*;
16
17class RealTermEvaluateTest {
18 public static final double TOLERANCE = 1e-6;
19
20 private static Matcher<Double> closeToOrNull(Double expected) {
21 return expected == null ? nullValue(Double.class) : closeTo(expected, TOLERANCE);
22 }
23
24 @ParameterizedTest(name = "+{0} == {1}")
25 @CsvSource(value = {
26 "2.5, 2.5",
27 "null, null"
28 }, nullValues = "null")
29 void plusTest(Double a, Double result) {
30 var term = RealTerms.plus(RealTerms.constant(a));
31 assertThat(term.getType(), is(Double.class));
32 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
33 }
34
35 @ParameterizedTest(name = "-{0} == {1}")
36 @CsvSource(value = {
37 "2.5, -2.5",
38 "null, null"
39 }, nullValues = "null")
40 void minusTest(Double a, Double result) {
41 var term = RealTerms.minus(RealTerms.constant(a));
42 assertThat(term.getType(), is(Double.class));
43 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
44 }
45
46 @ParameterizedTest(name = "{0} + {1} == {2}")
47 @CsvSource(value = {
48 "1.2, 2.3, 3.5",
49 "null, 2.3, null",
50 "1.2, null, null",
51 "null, null, null"
52 }, nullValues = "null")
53 void addTest(Double a, Double b, Double result) {
54 var term = RealTerms.add(RealTerms.constant(a), RealTerms.constant(b));
55 assertThat(term.getType(), is(Double.class));
56 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
57 }
58
59 @ParameterizedTest(name = "{0} - {1} == {2}")
60 @CsvSource(value = {
61 "1.2, 3.4, -2.2",
62 "null, 3.4, null",
63 "1.2, null, null",
64 "null, null, null"
65 }, nullValues = "null")
66 void subTest(Double a, Double b, Double result) {
67 var term = RealTerms.sub(RealTerms.constant(a), RealTerms.constant(b));
68 assertThat(term.getType(), is(Double.class));
69 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
70 }
71
72 @ParameterizedTest(name = "{0} * {1} == {2}")
73 @CsvSource(value = {
74 "2.3, 3.4, 7.82",
75 "null, 3.4, null",
76 "2.3, null, null",
77 "null, null, null"
78 }, nullValues = "null")
79 void mulTest(Double a, Double b, Double result) {
80 var term = RealTerms.mul(RealTerms.constant(a), RealTerms.constant(b));
81 assertThat(term.getType(), is(Double.class));
82 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
83 }
84
85 @ParameterizedTest(name = "{0} * {1} == {2}")
86 @CsvSource(value = {
87 "7.82, 3.4, 2.3",
88 "null, 3.4, null",
89 "7.82, null, null",
90 "null, null, null"
91 }, nullValues = "null")
92 void divTest(Double a, Double b, Double result) {
93 var term = RealTerms.div(RealTerms.constant(a), RealTerms.constant(b));
94 assertThat(term.getType(), is(Double.class));
95 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
96 }
97
98 @ParameterizedTest(name = "{0} ** {1} == {2}")
99 @CsvSource(value = {
100 "2.0, 6.0, 64.0",
101 "null, 6.0, null",
102 "2.0, null, null",
103 "null, null, null"
104 }, nullValues = "null")
105 void powTest(Double a, Double b, Double result) {
106 var term = RealTerms.pow(RealTerms.constant(a), RealTerms.constant(b));
107 assertThat(term.getType(), is(Double.class));
108 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
109 }
110
111 @ParameterizedTest(name = "min({0}, {1}) == {2}")
112 @CsvSource(value = {
113 "1.5, 2.7, 1.5",
114 "2.7, 1.5, 1.5",
115 "null, 2.7, null",
116 "1.5, null, null",
117 "null, null, null"
118 }, nullValues = "null")
119 void minTest(Double a, Double b, Double result) {
120 var term = RealTerms.min(RealTerms.constant(a), RealTerms.constant(b));
121 assertThat(term.getType(), is(Double.class));
122 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
123 }
124
125 @ParameterizedTest(name = "max({0}, {1}) == {2}")
126 @CsvSource(value = {
127 "1.5, 2.7, 2.7",
128 "2.7, 1.7, 2.7",
129 "null, 2.7, null",
130 "1.5, null, null",
131 "null, null, null"
132 }, nullValues = "null")
133 void maxTest(Double a, Double b, Double result) {
134 var term = RealTerms.max(RealTerms.constant(a), RealTerms.constant(b));
135 assertThat(term.getType(), is(Double.class));
136 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
137 }
138
139 @ParameterizedTest(name = "({0} == {1}) == {2}")
140 @CsvSource(value = {
141 "1.5, 1.5, true",
142 "1.5, 2.7, false",
143 "null, 1.5, null",
144 "1.5, null, null",
145 "null, null, null"
146 }, nullValues = "null")
147 void eqTest(Double a, Double b, Boolean result) {
148 var term = RealTerms.eq(RealTerms.constant(a), RealTerms.constant(b));
149 assertThat(term.getType(), is(Boolean.class));
150 assertThat(term.evaluate(Valuation.empty()), is(result));
151 }
152
153 @ParameterizedTest(name = "({0} != {1}) == {2}")
154 @CsvSource(value = {
155 "1.5, 1.5, false",
156 "1.5, 2.7, true",
157 "null, 1.5, null",
158 "1.5, null, null",
159 "null, null, null"
160 }, nullValues = "null")
161 void notEqTest(Double a, Double b, Boolean result) {
162 var term = RealTerms.notEq(RealTerms.constant(a), RealTerms.constant(b));
163 assertThat(term.getType(), is(Boolean.class));
164 assertThat(term.evaluate(Valuation.empty()), is(result));
165 }
166
167 @ParameterizedTest(name = "({0} < {1}) == {2}")
168 @CsvSource(value = {
169 "1.5, -2.7, false",
170 "1.5, 1.5, false",
171 "1.5, 2.7, true",
172 "null, 1.5, null",
173 "1.5, null, null",
174 "null, null, null"
175 }, nullValues = "null")
176 void lessTest(Double a, Double b, Boolean result) {
177 var term = RealTerms.less(RealTerms.constant(a), RealTerms.constant(b));
178 assertThat(term.getType(), is(Boolean.class));
179 assertThat(term.evaluate(Valuation.empty()), is(result));
180 }
181
182 @ParameterizedTest(name = "({0} <= {1}) == {2}")
183 @CsvSource(value = {
184 "1.5, -2.7, false",
185 "1.5, 1.5, true",
186 "1.5, 2.7, true",
187 "null, 1.5, null",
188 "1.5, null, null",
189 "null, null, null"
190 }, nullValues = "null")
191 void lessEqTest(Double a, Double b, Boolean result) {
192 var term = RealTerms.lessEq(RealTerms.constant(a), RealTerms.constant(b));
193 assertThat(term.getType(), is(Boolean.class));
194 assertThat(term.evaluate(Valuation.empty()), is(result));
195 }
196
197 @ParameterizedTest(name = "({0} > {1}) == {2}")
198 @CsvSource(value = {
199 "1.5, -2.7, true",
200 "1.5, 1.5, false",
201 "1.5, 2.7, false",
202 "null, 1.5, null",
203 "1.5, null, null",
204 "null, null, null"
205 }, nullValues = "null")
206 void greaterTest(Double a, Double b, Boolean result) {
207 var term = RealTerms.greater(RealTerms.constant(a), RealTerms.constant(b));
208 assertThat(term.getType(), is(Boolean.class));
209 assertThat(term.evaluate(Valuation.empty()), is(result));
210 }
211
212 @ParameterizedTest(name = "({0} >= {1}) == {2}")
213 @CsvSource(value = {
214 "1.5, -2.7, true",
215 "1.5, 1.5, true",
216 "1.5, 2.7, false",
217 "null, 1.5, null",
218 "1.5, null, null",
219 "null, null, null"
220 }, nullValues = "null")
221 void greaterEqTest(Double a, Double b, Boolean result) {
222 var term = RealTerms.greaterEq(RealTerms.constant(a), RealTerms.constant(b));
223 assertThat(term.getType(), is(Boolean.class));
224 assertThat(term.evaluate(Valuation.empty()), is(result));
225 }
226
227 @ParameterizedTest(name = "{0} as real == {1}")
228 @CsvSource(value = {
229 "0, 0.0",
230 "5, 5.0",
231 "null, null"
232 }, nullValues = "null")
233 void asRealTest(Integer a, Double result) {
234 var term = RealTerms.asReal(IntTerms.constant(a));
235 assertThat(term.getType(), is(Double.class));
236 assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result));
237 }
238}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java
new file mode 100644
index 00000000..31baf36e
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java
@@ -0,0 +1,56 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.Arguments;
10import org.junit.jupiter.params.provider.MethodSource;
11import tools.refinery.store.representation.cardinality.UpperCardinalities;
12import tools.refinery.store.representation.cardinality.UpperCardinality;
13
14import java.util.List;
15import java.util.stream.Stream;
16
17import static org.hamcrest.MatcherAssert.assertThat;
18import static org.hamcrest.Matchers.is;
19
20class UpperCardinalitySumAggregatorStreamTest {
21 @ParameterizedTest
22 @MethodSource
23 void testStream(List<UpperCardinality> list, UpperCardinality expected) {
24 var result = UpperCardinalitySumAggregator.INSTANCE.aggregateStream(list.stream());
25 assertThat(result, is(expected));
26 }
27
28 static Stream<Arguments> testStream() {
29 return Stream.of(
30 Arguments.of(List.of(), UpperCardinalities.ZERO),
31 Arguments.of(List.of(UpperCardinality.of(3)), UpperCardinality.of(3)),
32 Arguments.of(
33 List.of(
34 UpperCardinality.of(2),
35 UpperCardinality.of(3)
36 ),
37 UpperCardinality.of(5)
38 ),
39 Arguments.of(List.of(UpperCardinalities.UNBOUNDED), UpperCardinalities.UNBOUNDED),
40 Arguments.of(
41 List.of(
42 UpperCardinalities.UNBOUNDED,
43 UpperCardinalities.UNBOUNDED
44 ),
45 UpperCardinalities.UNBOUNDED
46 ),
47 Arguments.of(
48 List.of(
49 UpperCardinalities.UNBOUNDED,
50 UpperCardinality.of(3)
51 ),
52 UpperCardinalities.UNBOUNDED
53 )
54 );
55 }
56}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorTest.java
new file mode 100644
index 00000000..780cd0ab
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorTest.java
@@ -0,0 +1,80 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.query.term.StatefulAggregate;
11import tools.refinery.store.representation.cardinality.UpperCardinalities;
12import tools.refinery.store.representation.cardinality.UpperCardinality;
13
14import static org.hamcrest.MatcherAssert.assertThat;
15import static org.hamcrest.Matchers.is;
16
17class UpperCardinalitySumAggregatorTest {
18 private StatefulAggregate<UpperCardinality, UpperCardinality> accumulator;
19
20 @BeforeEach
21 void beforeEach() {
22 accumulator = UpperCardinalitySumAggregator.INSTANCE.createEmptyAggregate();
23 }
24
25 @Test
26 void emptyAggregationTest() {
27 assertThat(accumulator.getResult(), is(UpperCardinality.of(0)));
28 }
29
30 @Test
31 void singleBoundedTest() {
32 accumulator.add(UpperCardinality.of(3));
33 assertThat(accumulator.getResult(), is(UpperCardinality.of(3)));
34 }
35
36 @Test
37 void multipleBoundedTest() {
38 accumulator.add(UpperCardinality.of(2));
39 accumulator.add(UpperCardinality.of(3));
40 assertThat(accumulator.getResult(), is(UpperCardinality.of(5)));
41 }
42
43 @Test
44 void singleUnboundedTest() {
45 accumulator.add(UpperCardinalities.UNBOUNDED);
46 assertThat(accumulator.getResult(), is(UpperCardinalities.UNBOUNDED));
47 }
48
49 @Test
50 void multipleUnboundedTest() {
51 accumulator.add(UpperCardinalities.UNBOUNDED);
52 accumulator.add(UpperCardinalities.UNBOUNDED);
53 assertThat(accumulator.getResult(), is(UpperCardinalities.UNBOUNDED));
54 }
55
56 @Test
57 void removeBoundedTest() {
58 accumulator.add(UpperCardinality.of(2));
59 accumulator.add(UpperCardinality.of(3));
60 accumulator.remove(UpperCardinality.of(2));
61 assertThat(accumulator.getResult(), is(UpperCardinality.of(3)));
62 }
63
64 @Test
65 void removeAllUnboundedTest() {
66 accumulator.add(UpperCardinalities.UNBOUNDED);
67 accumulator.add(UpperCardinality.of(3));
68 accumulator.remove(UpperCardinalities.UNBOUNDED);
69 assertThat(accumulator.getResult(), is(UpperCardinality.of(3)));
70 }
71
72 @Test
73 void removeSomeUnboundedTest() {
74 accumulator.add(UpperCardinalities.UNBOUNDED);
75 accumulator.add(UpperCardinalities.UNBOUNDED);
76 accumulator.add(UpperCardinality.of(3));
77 accumulator.remove(UpperCardinalities.UNBOUNDED);
78 assertThat(accumulator.getResult(), is(UpperCardinalities.UNBOUNDED));
79 }
80}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java
new file mode 100644
index 00000000..9d0f3bde
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java
@@ -0,0 +1,104 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.Arguments;
10import org.junit.jupiter.params.provider.MethodSource;
11import tools.refinery.store.query.valuation.Valuation;
12import tools.refinery.store.representation.cardinality.UpperCardinalities;
13import tools.refinery.store.representation.cardinality.UpperCardinality;
14
15import java.util.stream.Stream;
16
17import static org.hamcrest.MatcherAssert.assertThat;
18import static org.hamcrest.Matchers.is;
19
20class UpperCardinalityTermsEvaluateTest {
21 @ParameterizedTest(name = "min({0}, {1}) == {2}")
22 @MethodSource
23 void minTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
24 var term = UpperCardinalityTerms.min(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b));
25 assertThat(term.getType(), is(UpperCardinality.class));
26 assertThat(term.evaluate(Valuation.empty()), is(expected));
27 }
28
29 static Stream<Arguments> minTest() {
30 return Stream.of(
31 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)),
32 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(0)),
33 Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(0)),
34 Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinality.of(0)),
35 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinality.of(0)),
36 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
37 Arguments.of(UpperCardinality.of(1), null, null),
38 Arguments.of(null, UpperCardinality.of(1), null),
39 Arguments.of(null, null, null)
40 );
41 }
42
43 @ParameterizedTest(name = "max({0}, {1}) == {2}")
44 @MethodSource
45 void maxTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
46 var term = UpperCardinalityTerms.max(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b));
47 assertThat(term.getType(), is(UpperCardinality.class));
48 assertThat(term.evaluate(Valuation.empty()), is(expected));
49 }
50
51 static Stream<Arguments> maxTest() {
52 return Stream.of(
53 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)),
54 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(1)),
55 Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(1)),
56 Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
57 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinalities.UNBOUNDED),
58 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
59 Arguments.of(UpperCardinality.of(1), null, null),
60 Arguments.of(null, UpperCardinality.of(1), null),
61 Arguments.of(null, null, null)
62 );
63 }
64
65 @ParameterizedTest(name = "{0} + {1} == {2}")
66 @MethodSource
67 void addTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
68 var term = UpperCardinalityTerms.add(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b));
69 assertThat(term.getType(), is(UpperCardinality.class));
70 assertThat(term.evaluate(Valuation.empty()), is(expected));
71 }
72
73 static Stream<Arguments> addTest() {
74 return Stream.of(
75 Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(5)),
76 Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
77 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED),
78 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
79 Arguments.of(UpperCardinality.of(1), null, null),
80 Arguments.of(null, UpperCardinality.of(1), null),
81 Arguments.of(null, null, null)
82 );
83 }
84
85 @ParameterizedTest(name = "{0} * {1} == {2}")
86 @MethodSource
87 void mulTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
88 var term = UpperCardinalityTerms.mul(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b));
89 assertThat(term.getType(), is(UpperCardinality.class));
90 assertThat(term.evaluate(Valuation.empty()), is(expected));
91 }
92
93 static Stream<Arguments> mulTest() {
94 return Stream.of(
95 Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(6)),
96 Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
97 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED),
98 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
99 Arguments.of(UpperCardinality.of(1), null, null),
100 Arguments.of(null, UpperCardinality.of(1), null),
101 Arguments.of(null, null, null)
102 );
103 }
104}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToRawTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToRawTest.java
new file mode 100644
index 00000000..d447e99c
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToRawTest.java
@@ -0,0 +1,159 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.tests;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.SymbolicParameter;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.query.view.KeyOnlyView;
16import tools.refinery.store.representation.Symbol;
17
18import java.util.List;
19
20import static org.hamcrest.CoreMatchers.containsString;
21import static org.hamcrest.MatcherAssert.assertThat;
22import static org.hamcrest.Matchers.allOf;
23import static org.junit.jupiter.api.Assertions.assertThrows;
24import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
25
26class StructurallyEqualToRawTest {
27 private static final Symbol<Boolean> person = Symbol.of("Person", 1);
28 private static final Symbol<Boolean> friend = Symbol.of("friend", 2);
29 private static final AnySymbolView personView = new KeyOnlyView<>(person);
30 private static final AnySymbolView friendView = new KeyOnlyView<>(friend);
31 private static final NodeVariable p = Variable.of("p");
32 private static final NodeVariable q = Variable.of("q");
33
34 @Test
35 void flatEqualsTest() {
36 var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build();
37
38 assertThat(actual, structurallyEqualTo(
39 List.of(new SymbolicParameter(q, ParameterDirection.OUT)),
40 List.of(List.of(personView.call(q)))
41 ));
42 }
43
44 @Test
45 void flatNotEqualsTest() {
46 var actual = Dnf.builder("Actual").parameters(p).clause(friendView.call(p, q)).build();
47
48 var assertion = structurallyEqualTo(
49 List.of(new SymbolicParameter(q, ParameterDirection.OUT)),
50 List.of(List.of(friendView.call(q, q)))
51 );
52 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
53 }
54
55 @Test
56 void deepEqualsTest() {
57 var actual = Dnf.builder("Actual").parameters(q).clause(
58 Dnf.builder("Actual2").parameters(p).clause(personView.call(p)).build().call(q)
59 ).build();
60
61 assertThat(actual, structurallyEqualTo(
62 List.of(new SymbolicParameter(q, ParameterDirection.OUT)),
63 List.of(
64 List.of(
65 Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q)
66 )
67 )
68 ));
69 }
70
71 @Test
72 void deepNotEqualsTest() {
73 var actual = Dnf.builder("Actual").parameter(q).clause(
74 Dnf.builder("Actual2").parameters(p).clause(friendView.call(p, q)).build().call(q)
75 ).build();
76
77 var assertion = structurallyEqualTo(
78 List.of(new SymbolicParameter(q, ParameterDirection.OUT)),
79 List.of(
80 List.of(
81 Dnf.builder("Expected2")
82 .parameters(p)
83 .clause(friendView.call(p, p))
84 .build()
85 .call(q)
86 )
87 )
88 );
89 var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
90 assertThat(error.getMessage(), allOf(containsString("Expected2"), containsString("Actual2")));
91 }
92
93 @Test
94 void parameterListLengthMismatchTest() {
95 var actual = Dnf.builder("Actual").parameters(p, q).clause(
96 friendView.call(p, q)
97 ).build();
98
99 var assertion = structurallyEqualTo(
100 List.of(new SymbolicParameter(p, ParameterDirection.OUT)),
101 List.of(List.of(friendView.call(p, p)))
102 );
103
104 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
105 }
106
107 @Test
108 void parameterDirectionMismatchTest() {
109 var actual = Dnf.builder("Actual").parameter(p, ParameterDirection.IN).clause(
110 personView.call(p)
111 ).build();
112
113 var assertion = structurallyEqualTo(
114 List.of(new SymbolicParameter(p, ParameterDirection.OUT)),
115 List.of(List.of(personView.call(p)))
116 );
117
118 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
119 }
120
121 @Test
122 void clauseCountMismatchTest() {
123 var actual = Dnf.builder("Actual").parameters(p, q).clause(
124 friendView.call(p, q)
125 ).build();
126
127 var assertion = structurallyEqualTo(
128 List.of(
129 new SymbolicParameter(p, ParameterDirection.OUT),
130 new SymbolicParameter(q, ParameterDirection.OUT)
131 ),
132 List.of(
133 List.of(friendView.call(p, q)),
134 List.of(friendView.call(q, p))
135 )
136 );
137
138 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
139 }
140
141 @Test
142 void literalCountMismatchTest() {
143 var actual = Dnf.builder("Actual").parameters(p, q).clause(
144 friendView.call(p, q)
145 ).build();
146
147 var assertion = structurallyEqualTo(
148 List.of(
149 new SymbolicParameter(p, ParameterDirection.OUT),
150 new SymbolicParameter(q, ParameterDirection.OUT)
151 ),
152 List.of(
153 List.of(friendView.call(p, q), friendView.call(q, p))
154 )
155 );
156
157 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
158 }
159}
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..f716b805
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java
@@ -0,0 +1,127 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.tests;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.query.view.AnySymbolView;
14import tools.refinery.store.query.view.KeyOnlyView;
15import tools.refinery.store.representation.Symbol;
16
17import static org.hamcrest.CoreMatchers.containsString;
18import static org.hamcrest.MatcherAssert.assertThat;
19import static org.junit.jupiter.api.Assertions.assertThrows;
20import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
21
22class StructurallyEqualToTest {
23 private static final Symbol<Boolean> person = Symbol.of("Person", 1);
24 private static final Symbol<Boolean> friend = Symbol.of("friend", 2);
25 private static final AnySymbolView personView = new KeyOnlyView<>(person);
26 private static final AnySymbolView friendView = new KeyOnlyView<>(friend);
27 private static final NodeVariable p = Variable.of("p");
28 private static final NodeVariable q = Variable.of("q");
29
30 @Test
31 void flatEqualsTest() {
32 var expected = Dnf.builder("Expected").parameters(q).clause(personView.call(q)).build();
33 var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build();
34
35 assertThat(actual, structurallyEqualTo(expected));
36 }
37
38 @Test
39 void flatNotEqualsTest() {
40 var expected = Dnf.builder("Expected").parameters(q).clause(friendView.call(q, q)).build();
41 var actual = Dnf.builder("Actual").parameters(p).clause(friendView.call(p, q)).build();
42
43 var assertion = structurallyEqualTo(expected);
44 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
45 }
46
47 @Test
48 void deepEqualsTest() {
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 expected = Dnf.builder("Expected").parameters(q).clause(
62 Dnf.builder("Expected2").parameters(p).clause(friendView.call(p, p)).build().call(q)
63 ).build();
64 var actual = Dnf.builder("Actual").parameter(q).clause(
65 Dnf.builder("Actual2").parameters(p).clause(friendView.call(p, q)).build().call(q)
66 ).build();
67
68 var assertion = structurallyEqualTo(expected);
69 var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
70 assertThat(error.getMessage(), containsString(" called from Expected/1 "));
71 }
72
73 @Test
74 void parameterListLengthMismatchTest() {
75 var expected = Dnf.builder("Expected").parameter(p).clause(
76 friendView.call(p, p)
77 ).build();
78 var actual = Dnf.builder("Actual").parameters(p, q).clause(
79 friendView.call(p, q)
80 ).build();
81
82 var assertion = structurallyEqualTo(expected);
83 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
84 }
85
86 @Test
87 void parameterDirectionMismatchTest() {
88 var expected = Dnf.builder("Expected").parameter(p, ParameterDirection.OUT).clause(
89 personView.call(p)
90 ).build();
91 var actual = Dnf.builder("Actual").parameter(p, ParameterDirection.IN).clause(
92 personView.call(p)
93 ).build();
94
95 var assertion = structurallyEqualTo(expected);
96 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
97 }
98
99 @Test
100 void clauseCountMismatchTest() {
101 var expected = Dnf.builder("Expected")
102 .parameters(p, q)
103 .clause(friendView.call(p, q))
104 .clause(friendView.call(q, p))
105 .build();
106 var actual = Dnf.builder("Actual").parameters(p, q).clause(
107 friendView.call(p, q)
108 ).build();
109
110 var assertion = structurallyEqualTo(expected);
111 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
112 }
113
114 @Test
115 void literalCountMismatchTest() {
116 var expected = Dnf.builder("Expected").parameters(p, q).clause(
117 friendView.call(p, q),
118 friendView.call(q, p)
119 ).build();
120 var actual = Dnf.builder("Actual").parameters(p, q).clause(
121 friendView.call(p, q)
122 ).build();
123
124 var assertion = structurallyEqualTo(expected);
125 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
126 }
127}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java
new file mode 100644
index 00000000..cbb48603
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java
@@ -0,0 +1,634 @@
1/*
2 * Copyright (c) 2021 Rodion Efremov
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
4 *
5 * SPDX-License-Identifier: MIT OR EPL-2.0
6 */
7package tools.refinery.store.query.utils;
8
9import org.junit.jupiter.api.BeforeEach;
10import org.junit.jupiter.api.Test;
11import org.junit.jupiter.params.ParameterizedTest;
12import org.junit.jupiter.params.provider.Arguments;
13import org.junit.jupiter.params.provider.MethodSource;
14
15import java.util.*;
16import java.util.stream.Stream;
17
18import static org.junit.jupiter.api.Assertions.*;
19
20/**
21 * Tests for an order statistic tree which is based on AVL-trees.
22 * <p>
23 * This class was copied into <i>Refinery</i> from
24 * <a href="https://github.com/coderodde/OrderStatisticTree/tree/546c343b9f5d868e394a079ff32691c9dbfd83e3">https://github.com/coderodde/OrderStatisticTree</a>
25 * and is available under the
26 * <a href="https://github.com/coderodde/OrderStatisticTree/blob/master/LICENSE">MIT License</a>.
27 * We also migrated the code to Junit 5, cleaned up some linter warnings, and made the tests deterministic by fixing
28 * the random seeds.
29 *
30 * @author Rodion "rodde" Efremov
31 * @version based on 1.6 (Feb 11, 2016)
32 */
33class OrderStatisticTreeTest {
34 private final OrderStatisticTree<Integer> tree = new OrderStatisticTree<>();
35
36 private final TreeSet<Integer> set = new TreeSet<>();
37
38 @BeforeEach
39 void before() {
40 tree.clear();
41 set.clear();
42 }
43
44 @Test
45 void testAdd() {
46 assertEquals(set.isEmpty(), tree.isEmpty());
47
48 for (int i = 10; i < 30; i += 2) {
49 assertTrue(tree.isHealthy());
50 assertEquals(set.contains(i), tree.contains(i));
51 assertEquals(set.add(i), tree.add(i));
52 assertEquals(set.add(i), tree.add(i));
53 assertEquals(set.contains(i), tree.contains(i));
54 assertTrue(tree.isHealthy());
55 }
56
57 assertEquals(set.isEmpty(), tree.isEmpty());
58 }
59
60 @Test
61 void testAddAll() {
62 for (int i = 0; i < 10; ++i) {
63 assertEquals(set.add(i), tree.add(i));
64 }
65
66 Collection<Integer> coll = Arrays.asList(10, 9, 7, 11, 12);
67
68 assertEquals(set.addAll(coll), tree.addAll(coll));
69 assertEquals(set.size(), tree.size());
70
71 for (int i = -10; i < 20; ++i) {
72 assertEquals(set.contains(i), tree.contains(i));
73 }
74 }
75
76 @Test
77 void testClear() {
78 for (int i = 0; i < 2000; ++i) {
79 set.add(i);
80 tree.add(i);
81 }
82
83 assertEquals(set.size(), tree.size());
84 set.clear();
85 tree.clear();
86 // We expect {@code tree.size()} to always be 0, but we also test for it.
87 //noinspection ConstantValue
88 assertEquals(0, tree.size());
89 }
90
91 @Test
92 void testContains() {
93 for (int i = 100; i < 200; i += 3) {
94 assertTrue(tree.isHealthy());
95 assertEquals(set.add(i), tree.add(i));
96 assertTrue(tree.isHealthy());
97 }
98
99 assertEquals(set.size(), tree.size());
100
101 for (int i = 0; i < 300; ++i) {
102 assertEquals(set.contains(i), tree.contains(i));
103 }
104 }
105
106 @Test
107 void testContainsAll() {
108 for (int i = 0; i < 50; ++i) {
109 set.add(i);
110 tree.add(i);
111 }
112
113 Collection<Integer> coll = new HashSet<>();
114
115 for (int i = 10; i < 20; ++i) {
116 coll.add(i);
117 }
118
119 assertEquals(set.containsAll(coll), tree.containsAll(coll));
120 coll.add(100);
121 assertEquals(set.containsAll(coll), tree.containsAll(coll));
122 }
123
124 @Test
125 void testRemove() {
126 for (int i = 0; i < 200; ++i) {
127 assertEquals(set.add(i), tree.add(i));
128 }
129
130 for (int i = 50; i < 150; i += 2) {
131 assertEquals(set.remove(i), tree.remove(i));
132 assertTrue(tree.isHealthy());
133 }
134
135 for (int i = -100; i < 300; ++i) {
136 assertEquals(set.contains(i), tree.contains(i));
137 }
138 }
139
140 @Test
141 void testRemoveLast() {
142 tree.add(1);
143 tree.remove(1);
144 assertEquals(0, tree.size());
145 }
146
147 @Test
148 void testRemoveAll() {
149 for (int i = 0; i < 40; ++i) {
150 set.add(i);
151 tree.add(i);
152 }
153
154 Collection<Integer> coll = new HashSet<>();
155
156 for (int i = 10; i < 20; ++i) {
157 coll.add(i);
158 }
159
160 assertEquals(set.removeAll(coll), tree.removeAll(coll));
161
162 for (int i = -10; i < 50; ++i) {
163 assertEquals(set.contains(i), tree.contains(i));
164 }
165
166 assertEquals(set.removeAll(coll), tree.removeAll(coll));
167
168 for (int i = -10; i < 50; ++i) {
169 assertEquals(set.contains(i), tree.contains(i));
170 }
171 }
172
173 @Test
174 void testSize() {
175 for (int i = 0; i < 200; ++i) {
176 assertEquals(set.size(), tree.size());
177 assertEquals(set.add(i), tree.add(i));
178 assertEquals(set.size(), tree.size());
179 }
180 }
181
182 @Test
183 void testIndexOf() {
184 for (int i = 0; i < 100; ++i) {
185 assertTrue(tree.add(i * 2));
186 }
187
188 for (int i = 0; i < 100; ++i) {
189 assertEquals(i, tree.indexOf(2 * i));
190 }
191
192 for (int i = 100; i < 150; ++i) {
193 assertEquals(-1, tree.indexOf(2 * i));
194 }
195 }
196
197 @Test
198 void testEmpty() {
199 assertEquals(set.isEmpty(), tree.isEmpty());
200 set.add(0);
201 tree.add(0);
202 assertEquals(set.isEmpty(), tree.isEmpty());
203 }
204
205 @Test
206 void testEmptyTreeGetThrowsOnNegativeIndex() {
207 assertThrows(IndexOutOfBoundsException.class, () -> tree.get(-1));
208 }
209
210 @Test
211 void testEmptyTreeSelectThrowsOnTooLargeIndex() {
212 assertThrows(IndexOutOfBoundsException.class, () -> tree.get(0));
213 }
214
215 @Test
216 void testSelectThrowsOnNegativeIndex() {
217 for (int i = 0; i < 5; ++i) {
218 tree.add(i);
219 }
220
221 assertThrows(IndexOutOfBoundsException.class, () -> tree.get(-1));
222 }
223
224 @Test
225 void testSelectThrowsOnTooLargeIndex() {
226 for (int i = 0; i < 5; ++i) {
227 tree.add(i);
228 }
229
230 assertThrows(IndexOutOfBoundsException.class, () -> tree.get(5));
231 }
232
233 @Test
234 void testGet() {
235 for (int i = 0; i < 100; i += 3) {
236 tree.add(i);
237 }
238
239 for (int i = 0; i < tree.size(); ++i) {
240 assertEquals(Integer.valueOf(3 * i), tree.get(i));
241 }
242 }
243
244 @Test
245 void findBug() {
246 tree.add(0);
247 assertTrue(tree.isHealthy());
248
249 tree.add(-1);
250 tree.remove(-1);
251 assertTrue(tree.isHealthy());
252
253 tree.add(1);
254 tree.remove(1);
255 assertTrue(tree.isHealthy());
256
257 tree.add(-1);
258 tree.add(1);
259 tree.remove(0);
260 assertTrue(tree.isHealthy());
261
262 tree.clear();
263 tree.add(0);
264 tree.add(-1);
265 tree.add(10);
266 tree.add(5);
267 tree.add(15);
268 tree.add(11);
269 tree.add(30);
270 tree.add(7);
271
272 tree.remove(-1);
273
274 assertTrue(tree.isHealthy());
275 }
276
277 @ParameterizedTest(name = "seed = {0}")
278 @MethodSource("seedSource")
279 void tryReproduceTheCounterBug(long seed) {
280 Random random = new Random(seed);
281 List<Integer> list = new ArrayList<>();
282
283 for (int i = 0; i < 10; ++i) {
284 int number = random.nextInt(1000);
285 list.add(number);
286 tree.add(number);
287 assertTrue(tree.isHealthy());
288 }
289
290 for (Integer i : list) {
291 tree.remove(i);
292 boolean healthy = tree.isHealthy();
293 assertTrue(healthy);
294 }
295 }
296
297 @Test
298 void testEmptyIterator() {
299 var iterator = tree.iterator();
300 assertThrows(NoSuchElementException.class, iterator::next);
301 }
302
303 @Test
304 void testIteratorThrowsOnDoubleRemove() {
305 for (int i = 10; i < 20; ++i) {
306 set.add(i);
307 tree.add(i);
308 }
309
310 Iterator<Integer> iterator1 = set.iterator();
311 Iterator<Integer> iterator2 = tree.iterator();
312
313 for (int i = 0; i < 3; ++i) {
314 assertEquals(iterator1.next(), iterator2.next());
315 }
316
317 iterator1.remove();
318 iterator2.remove();
319
320 assertThrows(IllegalStateException.class, iterator1::remove);
321 assertThrows(IllegalStateException.class, iterator2::remove);
322 }
323
324 @Test
325 void testIterator() {
326 for (int i = 0; i < 5; ++i) {
327 tree.add(i);
328 set.add(i);
329 }
330
331 Iterator<Integer> iterator1 = set.iterator();
332 Iterator<Integer> iterator2 = tree.iterator();
333
334 for (int i = 0; i < 5; ++i) {
335 assertEquals(iterator1.hasNext(), iterator2.hasNext());
336 assertEquals(iterator1.next(), iterator2.next());
337 }
338
339 assertEquals(iterator1.hasNext(), iterator2.hasNext());
340
341 assertThrows(NoSuchElementException.class, iterator1::next);
342 assertThrows(NoSuchElementException.class, iterator2::next);
343 }
344
345 @Test
346 void testRemoveBeforeNextThrowsEmpty() {
347 var setIterator = set.iterator();
348 assertThrows(IllegalStateException.class, setIterator::remove);
349
350 var treeIterator = tree.iterator();
351 assertThrows(IllegalStateException.class, treeIterator::remove);
352 }
353
354 @Test
355 void testRemoveThrowsWithoutNext() {
356 for (int i = 0; i < 10; ++i) {
357 tree.add(i);
358 set.add(i);
359 }
360
361 Iterator<Integer> iterator1 = set.iterator();
362 Iterator<Integer> iterator2 = tree.iterator();
363
364 for (int i = 0; i < 4; ++i) {
365 assertEquals(iterator1.hasNext(), iterator2.hasNext());
366 assertEquals(iterator1.next(), iterator2.next());
367 }
368
369 iterator1.remove();
370 iterator2.remove();
371
372 assertThrows(IllegalStateException.class, iterator1::remove);
373 assertThrows(IllegalStateException.class, iterator2::remove);
374 }
375
376 @Test
377 void testRetainAll() {
378 for (int i = 0; i < 100; ++i) {
379 set.add(i);
380 tree.add(i);
381 }
382
383 Collection<Integer> coll = Arrays.asList(26, 29, 25);
384
385 assertEquals(set.retainAll(coll), tree.retainAll(coll));
386 assertEquals(set.size(), tree.size());
387
388 assertTrue(set.containsAll(tree));
389 assertTrue(tree.containsAll(set));
390 }
391
392 @Test
393 void testIteratorRemove() {
394 for (int i = 10; i < 16; ++i) {
395 assertEquals(set.add(i), tree.add(i));
396 }
397
398 Iterator<Integer> iterator1 = set.iterator();
399 Iterator<Integer> iterator2 = tree.iterator();
400
401 assertEquals(iterator1.hasNext(), iterator2.hasNext());
402 assertEquals(iterator1.next(), iterator2.next());
403
404 assertEquals(iterator1.hasNext(), iterator2.hasNext());
405 assertEquals(iterator1.next(), iterator2.next());
406
407 iterator1.remove(); // remove 11
408 iterator2.remove();
409
410 assertEquals(iterator1.hasNext(), iterator2.hasNext());
411 assertEquals(iterator1.next(), iterator2.next());
412
413 assertEquals(iterator1.hasNext(), iterator2.hasNext());
414 assertEquals(iterator1.next(), iterator2.next());
415
416 iterator1.remove(); // remove 13
417 iterator2.remove();
418
419 assertEquals(set.size(), tree.size());
420
421 for (int i = 10; i < 16; ++i) {
422 assertEquals(set.contains(i), tree.contains(i));
423 }
424 }
425
426 @ParameterizedTest(name = "seed = {0}")
427 @MethodSource("seedSource")
428 void testIteratorBruteForce(long seed) {
429 for (int i = 0; i < 1000; ++i) {
430 assertEquals(set.add(i), tree.add(i));
431 }
432
433 Iterator<Integer> iterator1 = set.iterator();
434 Iterator<Integer> iterator2 = tree.iterator();
435
436 Random random = new Random(seed);
437
438 while (true) {
439 if (!iterator1.hasNext()) {
440 assertFalse(iterator2.hasNext());
441 break;
442 }
443
444 boolean toRemove = random.nextBoolean();
445
446 if (toRemove) {
447 boolean thrown = false;
448
449 try {
450 iterator1.remove();
451 } catch (IllegalStateException ex) {
452 thrown = true;
453 }
454
455 if (thrown) {
456 assertThrows(IllegalStateException.class, iterator2::remove);
457 } else {
458 iterator2.remove();
459 }
460 } else {
461 assertEquals(iterator1.hasNext(), iterator2.hasNext());
462
463 if (iterator1.hasNext()) {
464 assertEquals(iterator1.next(), iterator2.next());
465 } else {
466 break;
467 }
468 }
469 }
470
471 assertEquals(set.size(), tree.size());
472 assertTrue(tree.isHealthy());
473 assertTrue(set.containsAll(tree));
474 assertTrue(tree.containsAll(set));
475 }
476
477 @Test
478 void testIteratorConcurrentModification() {
479 for (int i = 0; i < 100; ++i) {
480 set.add(i);
481 tree.add(i);
482 }
483
484 Iterator<Integer> iterator1 = set.iterator();
485 Iterator<Integer> iterator2 = tree.iterator();
486
487 set.remove(10);
488 tree.remove(10);
489
490 assertEquals(iterator1.hasNext(), iterator2.hasNext());
491
492 boolean thrown = false;
493
494 try {
495 iterator1.next();
496 } catch (ConcurrentModificationException ex) {
497 thrown = true;
498 }
499
500 if (thrown) {
501 assertThrows(ConcurrentModificationException.class, iterator2::next);
502 } else {
503 iterator2.next();
504 }
505 }
506
507 @Test
508 void testIteratorConcurrentRemove() {
509 for (int i = 10; i < 20; ++i) {
510 set.add(i);
511 tree.add(i);
512 }
513
514 Iterator<Integer> iterator1 = set.iterator();
515 Iterator<Integer> iterator2 = tree.iterator();
516
517 for (int i = 0; i < 4; ++i) {
518 iterator1.next();
519 iterator2.next();
520 }
521
522 // None of them contains 2, should not change the modification count.
523 set.remove(2);
524 tree.remove(2);
525
526 iterator1.remove();
527 iterator2.remove();
528
529 iterator1.next();
530 iterator2.next();
531
532 set.remove(12);
533 tree.remove(12);
534
535 // Both of them should throw.
536 assertThrows(ConcurrentModificationException.class, iterator1::remove);
537 assertThrows(ConcurrentModificationException.class, iterator2::remove);
538 }
539
540 @Test
541 void testConcurrentOrIllegalStateOnRemove() {
542 for (int i = 0; i < 10; ++i) {
543 set.add(i);
544 tree.add(i);
545 }
546
547 Iterator<Integer> iterator1 = set.iterator();
548 Iterator<Integer> iterator2 = tree.iterator();
549
550 set.add(100);
551 tree.add(100);
552
553 assertThrows(IllegalStateException.class, iterator1::remove);
554 assertThrows(IllegalStateException.class, iterator2::remove);
555 }
556
557 @Test
558 void testConcurrentIterators() {
559 for (int i = 0; i < 10; ++i) {
560 set.add(i);
561 tree.add(i);
562 }
563
564 Iterator<Integer> iterator1a = set.iterator();
565 Iterator<Integer> iterator1b = set.iterator();
566 Iterator<Integer> iterator2a = tree.iterator();
567 Iterator<Integer> iterator2b = tree.iterator();
568
569 for (int i = 0; i < 3; ++i) {
570 iterator1a.next();
571 iterator2a.next();
572 }
573
574 iterator1a.remove();
575 iterator2a.remove();
576
577 assertEquals(iterator1b.hasNext(), iterator2b.hasNext());
578
579 assertThrows(ConcurrentModificationException.class, iterator1b::next);
580 assertThrows(ConcurrentModificationException.class, iterator2b::next);
581 }
582
583 @ParameterizedTest(name = "seed = {0}")
584 @MethodSource("seedSource")
585 void testToArray(long seed) {
586 Random r = new Random(seed);
587
588 for (int i = 0; i < 50; ++i) {
589 int num = r.nextInt();
590 set.add(num);
591 tree.add(num);
592 }
593
594 assertArrayEquals(set.toArray(), tree.toArray());
595 }
596
597 @Test
598 void testToArrayGeneric() {
599 for (int i = 0; i < 100; ++i) {
600 set.add(i);
601 tree.add(i);
602 }
603
604 Integer[] array1before = new Integer[99];
605 Integer[] array2before = new Integer[99];
606
607 Integer[] array1after = set.toArray(array1before);
608 Integer[] array2after = tree.toArray(array2before);
609
610 assertNotSame(array1before, array1after);
611 assertNotSame(array2before, array2after);
612 assertArrayEquals(array1after, array2after);
613
614 set.remove(1);
615 tree.remove(1);
616
617 array1after = set.toArray(array1before);
618 array2after = tree.toArray(array2before);
619
620 assertSame(array1before, array1after);
621 assertSame(array2before, array2after);
622 assertArrayEquals(array1after, array2after);
623 }
624
625 static Stream<Arguments> seedSource() {
626 return Stream.of(
627 Arguments.of(0L),
628 Arguments.of(1L),
629 Arguments.of(2L),
630 Arguments.of(3L),
631 Arguments.of(4L)
632 );
633 }
634}
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..6a3301b3
--- /dev/null
+++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java
@@ -0,0 +1,68 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.tests;
7
8import org.hamcrest.Description;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.SymbolicParameter;
11import tools.refinery.store.query.equality.DeepDnfEqualityChecker;
12import tools.refinery.store.query.literal.Literal;
13
14import java.util.List;
15
16class MismatchDescribingDnfEqualityChecker extends DeepDnfEqualityChecker {
17 private final Description description;
18 private boolean raw;
19 private boolean needsDescription = true;
20
21 MismatchDescribingDnfEqualityChecker(Description description) {
22 this.description = description;
23 }
24
25 public boolean needsDescription() {
26 return needsDescription;
27 }
28
29 @Override
30 public boolean dnfEqualRaw(List<SymbolicParameter> symbolicParameters, List<? extends List<? extends Literal>> clauses, Dnf other) {
31 try {
32 raw = true;
33 boolean result = super.dnfEqualRaw(symbolicParameters, clauses, other);
34 if (!result && needsDescription) {
35 description.appendText("was ").appendText(other.toDefinitionString());
36 }
37 return false;
38 } finally {
39 raw = false;
40 }
41 }
42
43 @Override
44 protected boolean doCheckEqual(Pair pair) {
45 boolean result = super.doCheckEqual(pair);
46 if (!result && needsDescription) {
47 describeMismatch(pair);
48 // Only describe the first found (innermost) mismatch.
49 needsDescription = false;
50 }
51 return result;
52 }
53
54 private void describeMismatch(Pair pair) {
55 var inProgress = getInProgress();
56 int size = inProgress.size();
57 if (size <= 1 && !raw) {
58 description.appendText("was ").appendText(pair.right().toDefinitionString());
59 return;
60 }
61 var last = inProgress.get(size - 1);
62 description.appendText("expected ").appendText(last.left().toDefinitionString());
63 for (int i = size - 2; i >= 0; i--) {
64 description.appendText(" called from ").appendText(inProgress.get(i).left().toString());
65 }
66 description.appendText(" was not structurally equal to ").appendText(last.right().toDefinitionString());
67 }
68}
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..cd449a6a
--- /dev/null
+++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java
@@ -0,0 +1,46 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.tests;
7
8import org.hamcrest.Matcher;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.SymbolicParameter;
11import tools.refinery.store.query.literal.Literal;
12
13import java.util.List;
14
15public final class QueryMatchers {
16 private QueryMatchers() {
17 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
18 }
19
20 /**
21 * Compare two {@link Dnf} instances up to renaming of variables.
22 *
23 * @param expected The expected {@link Dnf} instance.
24 * @return A Hamcrest matcher for equality up to renaming of variables.
25 */
26 public static Matcher<Dnf> structurallyEqualTo(Dnf expected) {
27 return new StructurallyEqualTo(expected);
28 }
29
30 /**
31 * Compare a {@link Dnf} instance to another predicate in DNF form without constructing it.
32 * <p>
33 * This matcher should be used instead of {@link #structurallyEqualTo(Dnf)} when the validation and
34 * pre-processing associated with the {@link Dnf} constructor, i.e., validation of parameter directions,
35 * topological sorting of literals, and the reduction of trivial predicates is not desired. In particular, this
36 * matcher can be used to test for exact order of literal after pre-processing.
37 *
38 * @param expectedSymbolicParameters The expected list of symbolic parameters.
39 * @param expectedLiterals The expected clauses. Each clause is represented by a list of literals.
40 * @return A Hamcrest matcher for equality up to renaming of variables.
41 */
42 public static Matcher<Dnf> structurallyEqualTo(List<SymbolicParameter> expectedSymbolicParameters,
43 List<? extends List<? extends Literal>> expectedLiterals) {
44 return new StructurallyEqualToRaw(expectedSymbolicParameters, expectedLiterals);
45 }
46}
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..86149141
--- /dev/null
+++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java
@@ -0,0 +1,41 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.tests;
7
8import org.hamcrest.Description;
9import org.hamcrest.TypeSafeMatcher;
10import tools.refinery.store.query.dnf.Dnf;
11import tools.refinery.store.query.equality.DeepDnfEqualityChecker;
12
13public class StructurallyEqualTo extends TypeSafeMatcher<Dnf> {
14 private final Dnf expected;
15
16 public StructurallyEqualTo(Dnf expected) {
17 this.expected = expected;
18 }
19
20 @Override
21 protected boolean matchesSafely(Dnf item) {
22 var checker = new DeepDnfEqualityChecker();
23 return checker.dnfEqual(expected, item);
24 }
25
26 @Override
27 protected void describeMismatchSafely(Dnf item, Description mismatchDescription) {
28 var describingChecker = new MismatchDescribingDnfEqualityChecker(mismatchDescription);
29 if (describingChecker.dnfEqual(expected, item)) {
30 throw new IllegalStateException("Mismatched Dnf was matching on repeated comparison");
31 }
32 if (describingChecker.needsDescription()) {
33 super.describeMismatchSafely(item, mismatchDescription);
34 }
35 }
36
37 @Override
38 public void describeTo(Description description) {
39 description.appendText("structurally equal to ").appendText(expected.toDefinitionString());
40 }
41}
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualToRaw.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualToRaw.java
new file mode 100644
index 00000000..2f8c2944
--- /dev/null
+++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualToRaw.java
@@ -0,0 +1,51 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.tests;
7
8import org.hamcrest.Description;
9import org.hamcrest.TypeSafeMatcher;
10import tools.refinery.store.query.dnf.Dnf;
11import tools.refinery.store.query.dnf.SymbolicParameter;
12import tools.refinery.store.query.equality.DeepDnfEqualityChecker;
13import tools.refinery.store.query.literal.Literal;
14
15import java.util.List;
16
17public class StructurallyEqualToRaw extends TypeSafeMatcher<Dnf> {
18 private final List<SymbolicParameter> expectedSymbolicParameters;
19 private final List<? extends List<? extends Literal>> expectedClauses;
20
21 public StructurallyEqualToRaw(List<SymbolicParameter> expectedSymbolicParameters,
22 List<? extends List<? extends Literal>> expectedClauses) {
23 this.expectedSymbolicParameters = expectedSymbolicParameters;
24 this.expectedClauses = expectedClauses;
25 }
26
27 @Override
28 protected boolean matchesSafely(Dnf item) {
29 var checker = new DeepDnfEqualityChecker();
30 return checker.dnfEqualRaw(expectedSymbolicParameters, expectedClauses, item);
31 }
32
33 @Override
34 protected void describeMismatchSafely(Dnf item, Description mismatchDescription) {
35 var describingChecker = new MismatchDescribingDnfEqualityChecker(mismatchDescription);
36 if (describingChecker.dnfEqualRaw(expectedSymbolicParameters, expectedClauses, item)) {
37 throw new IllegalStateException("Mismatched Dnf was matching on repeated comparison");
38 }
39 if (describingChecker.needsDescription()) {
40 super.describeMismatchSafely(item, mismatchDescription);
41 }
42 }
43
44 @Override
45 public void describeTo(Description description) {
46 description.appendText("structurally equal to ")
47 .appendValueList("(", ", ", ")", expectedSymbolicParameters)
48 .appendText(" <-> ")
49 .appendValueList("", ", ", ".", expectedClauses);
50 }
51}